diff --git a/DEPS b/DEPS
index d838752..e23d339 100644
--- a/DEPS
+++ b/DEPS
@@ -245,15 +245,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': '3f95fd2ed8c4d12315a2f73484e297f69aa37d2e',
+  'skia_revision': '44a83926207668ad8de5eae74d68a54812ca55ad',
   # 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': 'ceb9223e1ab1df795d5f3e69f9fae92bb8328e92',
+  'v8_revision': 'f5e412a1cd82fb606b79a587f1c4bda7f9445701',
   # 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': 'e7413adff5a3f91d34bbf3f2bb0b3be848091cd7',
+  'angle_revision': '543f5750f70e014dc9c2217fb2fcf1c6b63badd5',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -312,7 +312,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': '8f3d60691b27c6868c3b719b63d76667eef36366',
+  'catapult_revision': 'd6ef4a8af654ca24f8adca2a892f6f7857d57d2f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -360,7 +360,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': 'a2241d402e05dac272c0b3b7d26fd8a2cbd90ae7',
+  'dawn_revision': '3649af2486cd1c805f364a54cdf127179330024a',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -408,7 +408,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.
-  'libunwind_revision':    'd81cd6236cd771e78d7e1a1807404ef3f1d21820',
+  'libunwind_revision':    '4ead61094cab5ac7a90198fbe182596c4775183e',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -427,7 +427,7 @@
   'libcxx_revision':       '79a2e924d96e2fc1e4b937c42efd08898fa472d7',
 
   # GN CIPD package version.
-  'gn_version': 'git_revision:2e56c317bd8e2bf152cfa2ead6ac5fa476fe28b4',
+  'gn_version': 'git_revision:18df6af86191edab1e47c84d56e608da414d446b',
 }
 
 # Only these hosts are allowed for dependencies in this DEPS file.
@@ -1427,7 +1427,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '9c674c8ed6844fb88b545a8df9282a9405f8a072',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'cefb3e0ec3a0580c996f801e854fe02963c03d5c',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1648,7 +1648,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'c843f8d63c8c17acfbb7d48e09059a581ba779b9',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '9754a43403e0b955546aff0e7d07d6f70def4043',
+    Var('webrtc_git') + '/src.git' + '@' + '977234879de72cb9df9b191adc9c7520c5c51c35',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1691,6 +1691,8 @@
       'dep_type': 'cipd',
       'condition': 'checkout_win',
   },
+  # TODO(crbug.com/1280002): Remove this entry once the autoroller has been
+  # updated to roll the mac_amd64 version.
   'src/tools/skia_goldctl/mac': {
       'packages': [
         {
@@ -1702,11 +1704,33 @@
       'condition': 'checkout_mac',
   },
 
+  'src/tools/skia_goldctl/mac_amd64': {
+      'packages': [
+        {
+          'package': 'skia/tools/goldctl/mac-amd64',
+          'version': 'zLP4FDegN-yg3uvzWUdf4zNRVHDwHd0VLm86FkZCP_MC',
+        },
+      ],
+      'dep_type': 'cipd',
+      'condition': 'checkout_mac',
+  },
+
+  'src/tools/skia_goldctl/mac_arm64': {
+      'packages': [
+        {
+          'package': 'skia/tools/goldctl/mac-arm64',
+          'version': 'FOo-HR3_OFTL0pn9TIm0H93TmtXgEfjSazYb3xAtx68C',
+        },
+      ],
+      'dep_type': 'cipd',
+      'condition': 'checkout_mac',
+  },
+
   'src/v8':
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@3f843f82c4b67159e309b74ba45690d27f3eac5c',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@6b2cb689b53fd43f282594b87295a1ebcf675b0b',
     'condition': 'checkout_src_internal',
   },
 
@@ -3396,7 +3420,7 @@
       'packages': [
           {
               'package': 'chromium/third_party/android_deps/libs/org_jetbrains_kotlin_kotlin_stdlib',
-              'version': 'version:2@1.6.0.cr0',
+              'version': 'version:2@1.6.10.cr0',
           },
       ],
       'condition': 'checkout_android',
@@ -3407,7 +3431,7 @@
       'packages': [
           {
               'package': 'chromium/third_party/android_deps/libs/org_jetbrains_kotlin_kotlin_stdlib_common',
-              'version': 'version:2@1.6.0.cr0',
+              'version': 'version:2@1.6.10.cr0',
           },
       ],
       'condition': 'checkout_android',
diff --git a/WATCHLISTS b/WATCHLISTS
index 7758604..d5e3604f 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -1485,8 +1485,9 @@
         'ui/gl/gl_.*egl.*|'\
         'ui/gl/gl_.*ozone.*'
     },
-    'ozone_scenic': {
-      'filepath': 'ui/ozone/platform/scenic',
+    'ozone_fuchsia': {
+      'filepath': 'ui/ozone/platform/flatland/|'\
+                  'ui/ozone/platform/scenic/'
     },
     'page_info' : {
       'filepath': 'chrome/browser/ui/page_info/'\
@@ -2670,10 +2671,11 @@
     'origin_trials': ['chasej+watch@chromium.org',
                       'iclelland+watch@chromium.org'],
     'ozone': ['ozone-reviews@chromium.org'],
-    'ozone_scenic': ['dworsham@google.com',
-                     'fuchsia-reviews@chromium.org',
-                     'rjkroege@chromium.org',
-                     'spang+watch@chromium.org'],
+    'ozone_fuchsia': ['dworsham@google.com',
+                      'emircan@google.com',
+                      'fuchsia-reviews@chromium.org',
+                      'rjkroege@chromium.org',
+                      'spang+watch@chromium.org'],
     'page_info' : ['permissions-reviews@chromium.org',
                    'olesiamarukhno+watch@google.com'],
     'page_load_metrics' : ['bmcquade+watch@chromium.org',
diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
index 87a96f4..aa21f9d 100644
--- a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
+++ b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
@@ -7,6 +7,7 @@
 import android.Manifest;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.res.Resources;
 import android.os.Build;
 import android.os.Looper;
 import android.os.Process;
@@ -54,6 +55,7 @@
 import org.chromium.build.BuildConfig;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.net.NetworkChangeNotifier;
+import org.chromium.ui.base.DeviceFormFactor;
 import org.chromium.ui.base.ResourceBundle;
 
 /**
@@ -194,6 +196,28 @@
 
             // NOTE: Finished writing Java resources. From this point on, it's safe to use them.
 
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && mIsPostedFromBackgroundThread) {
+                // Try to work around the problem we're seeing with resources on Android 12. When
+                // WebView is being initialized from a background thread, it's possible that the
+                // asset path updated by WebViewFactory is no longer present by the time we get
+                // here due to something on the UI thread having caused a resource update in the
+                // app in the meantime, because WebViewFactory does not add the path persistently.
+                // So, we can try to add them again using the "better" method in WebViewDelegate.
+
+                // However, we only want to try this if the resources are actually missing, because
+                // in the past we've seen this cause apps that were working to *start* crashing.
+                // The first resource that gets accessed in startup happens during the
+                // AwBrowserProcess.start() call when trying to determine if the device is a tablet,
+                // and that's the most common place for us to crash. So, try calling that same
+                // method and see if it throws - if so then we're unlikely to make the situation
+                // any worse by trying to fix the path.
+                try {
+                    DeviceFormFactor.isTablet();
+                } catch (Resources.NotFoundException e) {
+                    mFactory.addWebViewAssetPath(context);
+                }
+            }
+
             AwBrowserProcess.configureChildProcessLauncher();
 
             // finishVariationsInitLocked() must precede native initialization so the seed is
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 64481a5..eab3df6 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -541,6 +541,8 @@
     "ime/mode_indicator_observer.h",
     "in_session_auth/auth_dialog_contents_view.cc",
     "in_session_auth/auth_dialog_contents_view.h",
+    "in_session_auth/authentication_dialog.cc",
+    "in_session_auth/authentication_dialog.h",
     "in_session_auth/in_session_auth_dialog.cc",
     "in_session_auth/in_session_auth_dialog.h",
     "in_session_auth/in_session_auth_dialog_controller_impl.cc",
@@ -2354,6 +2356,7 @@
     "highlighter/highlighter_gesture_util_unittest.cc",
     "host/ash_window_tree_host_platform_unittest.cc",
     "ime/ime_controller_impl_unittest.cc",
+    "in_session_auth/authentication_dialog_unittest.cc",
     "in_session_auth/in_session_auth_dialog_controller_impl_unittest.cc",
     "in_session_auth/mock_in_session_auth_dialog_client.cc",
     "in_session_auth/mock_in_session_auth_dialog_client.h",
diff --git a/ash/app_list/app_list_controller_impl.cc b/ash/app_list/app_list_controller_impl.cc
index 8719724..a013d75 100644
--- a/ash/app_list/app_list_controller_impl.cc
+++ b/ash/app_list/app_list_controller_impl.cc
@@ -859,8 +859,12 @@
 }
 
 void AppListControllerImpl::OnWallpaperColorsChanged() {
-  if (IsVisible(last_visible_display_id_))
-    fullscreen_presenter_->GetView()->OnWallpaperColorsChanged();
+  // Clamshell ProductivityLauncher doesn't use wallpaper prominent color.
+  if (IsVisible(last_visible_display_id_) && !ShouldShowAppListBubble()) {
+    AppListView* app_list_view = fullscreen_presenter_->GetView();
+    DCHECK(app_list_view);
+    app_list_view->OnWallpaperColorsChanged();
+  }
 }
 
 void AppListControllerImpl::OnWallpaperPreviewStarted() {
diff --git a/ash/app_list/app_list_controller_impl_unittest.cc b/ash/app_list/app_list_controller_impl_unittest.cc
index 057bde9..5cecc6a3 100644
--- a/ash/app_list/app_list_controller_impl_unittest.cc
+++ b/ash/app_list/app_list_controller_impl_unittest.cc
@@ -1281,6 +1281,15 @@
   EXPECT_FALSE(controller->bubble_presenter_for_test()->IsShowing());
 }
 
+TEST_F(AppListControllerImplAppListBubbleTest,
+       WallpaperColorChangeDoesNotCrash) {
+  auto* controller = Shell::Get()->app_list_controller();
+  controller->ShowAppList();
+  // Simulate synced wallpaper update while bubble is open.
+  controller->OnWallpaperColorsChanged();
+  // No crash.
+}
+
 class AppListControllerWithAssistantTest : public AppListControllerImplTest {
  public:
   AppListControllerWithAssistantTest()
diff --git a/ash/app_list/views/apps_container_view.cc b/ash/app_list/views/apps_container_view.cc
index 305f29d..790b567 100644
--- a/ash/app_list/views/apps_container_view.cc
+++ b/ash/app_list/views/apps_container_view.cc
@@ -135,8 +135,10 @@
 // The vertical spacing between recent apps and continue section view.
 constexpr int kRecentAppsTopMargin = 16;
 
-// The vertical spacing above and below the separator.
-constexpr int kSeparatorVerticalInset = 16;
+// The vertical spacing above and below the separator when using kRegular/kDense
+// AppListConfigType.
+constexpr int kRegularSeparatorVerticalInset = 16;
+constexpr int kDenseSeparatorVerticalInset = 8;
 
 // The width of the separator.
 constexpr int kSeparatorWidth = 240;
@@ -285,8 +287,10 @@
         ColorProvider::ContentLayerType::kSeparatorColor));
     separator_->SetPreferredSize(
         gfx::Size(kSeparatorWidth, views::Separator::kThickness));
+    // Initially set the vertical inset to kRegularSeparatorVerticalInset. The
+    // value will be updated in `AppsContainerView::UpdateAppListConfig()`
     separator_->SetProperty(views::kMarginsKey,
-                            gfx::Insets(kSeparatorVerticalInset, 0));
+                            gfx::Insets(kRegularSeparatorVerticalInset, 0));
     separator_->SetPaintToLayer();
     separator_->layer()->SetFillsBoundsOpaquely(false);
     separator_->SetProperty(views::kCrossAxisAlignmentKey,
@@ -315,6 +319,18 @@
     return recent_apps_ && recent_apps_->GetVisible();
   }
 
+  void UpdateAppListConfig(AppListConfig* config) {
+    if (recent_apps_)
+      recent_apps_->UpdateAppListConfig(config);
+
+    const int separator_vertical_inset =
+        config->type() == AppListConfigType::kRegular
+            ? kRegularSeparatorVerticalInset
+            : kDenseSeparatorVerticalInset;
+    separator_->SetProperty(views::kMarginsKey,
+                            gfx::Insets(separator_vertical_inset, 0));
+  }
+
   ContinueSectionView* continue_section() { return continue_section_; }
   RecentAppsView* recent_apps() { return recent_apps_; }
   views::View* separator() { return separator_; }
@@ -494,8 +510,8 @@
 
   apps_grid_view()->UpdateAppListConfig(app_list_config_.get());
   app_list_folder_view()->UpdateAppListConfig(app_list_config_.get());
-  if (GetRecentApps())
-    GetRecentApps()->UpdateAppListConfig(app_list_config_.get());
+  if (continue_container_)
+    continue_container_->UpdateAppListConfig(app_list_config_.get());
 }
 
 void AppsContainerView::OnActiveAppListModelsChanged(
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index 582ad69..f5f1ef2 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -2938,9 +2938,12 @@
       <message name="IDS_ASH_LOGIN_FINGERPRINT_UNLOCK_PIN_OR_PASSWORD_REQUIRED" desc="Text shown when fingerprint has been disabled because it has been too long since the user last used the device">
         PIN or password required for more security
       </message>
-      <message name="IDS_ASH_LOGIN_FINGERPRINT_UNLOCK_DISABLED_FROM_ATTEMPTS" desc="Text shown in the user pod to tell user that fingerprint unlock has reached maximum attempt">
+      <message name="IDS_ASH_LOGIN_FINGERPRINT_UNLOCK_DISABLED_FROM_ATTEMPTS_OLD" desc="Text shown in the user pod to tell user that fingerprint unlock has reached maximum attempt">
         Too many attempts
       </message>
+      <message name="IDS_ASH_LOGIN_FINGERPRINT_UNLOCK_DISABLED_FROM_ATTEMPTS" desc="Label for the Fingerprint icon on user's lock screen after too many failed attempts to authenticate a fingerprint.">
+        Too many fingerprint attempts
+      </message>
       <message name="IDS_ASH_LOGIN_FINGERPRINT_UNLOCK_ACCESSIBLE_AUTH_SUCCESS" desc="Accessibility text read by chromevox when the user successfully authenticated with fingerprint on the lock screen">
         Unlocking with fingerprint
       </message>
@@ -4342,14 +4345,9 @@
       <message name="IDS_AUTH_FACTOR_LABEL_CLICK_TO_ENTER" desc="Label on user's lock screen when a user is successfully authenticated and may click on arrow icon to unlock/use the Chromebook.">
         Tap or click to enter
       </message>
-
-      <!-- Fingerprint strings shown on the lock screen -->
       <message name="IDS_AUTH_FACTOR_LABEL_UNLOCKED" desc="Label on user's lock screen when a Chromebook is successfully unlocked.">
         Unlocked
       </message>
-      <message name="IDS_FINGERPRINT_LABEL_NOT_AUTHENTICATED" desc="Label for the Fingerprint icon on user's lock screen after too many failed attempts to authenticate a fingerprint.">
-        Too many fingerprint attempts
-      </message>
 
       <!-- Launcher: Continue Section -->
       <message name="IDS_ASH_LAUNCHER_CONTINUE_SECTION_LABEL" desc="Label for the continue section of the launcher, which shows recent files and apps that the user can continue using.">
diff --git a/ash/ash_strings_grd/IDS_ASH_LOGIN_FINGERPRINT_UNLOCK_DISABLED_FROM_ATTEMPTS.png.sha1 b/ash/ash_strings_grd/IDS_ASH_LOGIN_FINGERPRINT_UNLOCK_DISABLED_FROM_ATTEMPTS.png.sha1
new file mode 100644
index 0000000..07167116
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_LOGIN_FINGERPRINT_UNLOCK_DISABLED_FROM_ATTEMPTS.png.sha1
@@ -0,0 +1 @@
+834ab29efa299cbc263dc04d6de461c3e4ff384c
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_LOGIN_FINGERPRINT_UNLOCK_DISABLED_FROM_ATTEMPTS_OLD.png.sha1 b/ash/ash_strings_grd/IDS_ASH_LOGIN_FINGERPRINT_UNLOCK_DISABLED_FROM_ATTEMPTS_OLD.png.sha1
new file mode 100644
index 0000000..4558338e
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_LOGIN_FINGERPRINT_UNLOCK_DISABLED_FROM_ATTEMPTS_OLD.png.sha1
@@ -0,0 +1 @@
+8d9e7b62efed878aaea4d62b0f9f04992f861dca
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_FINGERPRINT_LABEL_NOT_AUTHENTICATED.png.sha1 b/ash/ash_strings_grd/IDS_FINGERPRINT_LABEL_NOT_AUTHENTICATED.png.sha1
deleted file mode 100644
index f8c7178..0000000
--- a/ash/ash_strings_grd/IDS_FINGERPRINT_LABEL_NOT_AUTHENTICATED.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-10d180a982c5f66ec725a9efff9a98a7367f933a
\ No newline at end of file
diff --git a/ash/components/fwupd/firmware_update_manager.cc b/ash/components/fwupd/firmware_update_manager.cc
index d22f33f3..b56e3b9 100644
--- a/ash/components/fwupd/firmware_update_manager.cc
+++ b/ash/components/fwupd/firmware_update_manager.cc
@@ -346,6 +346,12 @@
 void FirmwareUpdateManager::BindInterface(
     mojo::PendingReceiver<firmware_update::mojom::UpdateProvider>
         pending_receiver) {
+  // Clear any bound receiver, since this service is a singleton and is bound
+  // to the firmware updater UI it's possible that the app can be closed and
+  // reopened multiple times resulting in multiple attempts to bind to this
+  // receiver.
+  receiver_.reset();
+
   receiver_.Bind(std::move(pending_receiver));
 }
 
diff --git a/ash/in_session_auth/authentication_dialog.cc b/ash/in_session_auth/authentication_dialog.cc
new file mode 100644
index 0000000..2de13c86
--- /dev/null
+++ b/ash/in_session_auth/authentication_dialog.cc
@@ -0,0 +1,142 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/in_session_auth/authentication_dialog.h"
+#include <memory>
+#include "ash/public/cpp/shelf_config.h"
+#include "ash/strings/grit/ash_strings.h"
+#include "base/bind.h"
+#include "base/time/time.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/ui_base_types.h"
+#include "ui/display/screen.h"
+#include "ui/gfx/color_palette.h"
+#include "ui/gfx/geometry/insets.h"
+#include "ui/views/controls/button/label_button.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/controls/textfield/textfield.h"
+#include "ui/views/layout/flex_layout.h"
+#include "ui/views/layout/layout_provider.h"
+#include "ui/views/layout/layout_types.h"
+#include "ui/views/view_class_properties.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+
+namespace {
+
+void AddMargins(views::View* view) {
+  const auto* layout_provider = views::LayoutProvider::Get();
+  const int horizontal_spacing = layout_provider->GetDistanceMetric(
+      views::DISTANCE_RELATED_CONTROL_HORIZONTAL);
+  const int vertical_spacing = layout_provider->GetDistanceMetric(
+      views::DISTANCE_RELATED_CONTROL_VERTICAL);
+
+  view->SetProperty(views::kMarginsKey,
+                    gfx::Insets(vertical_spacing, horizontal_spacing));
+}
+
+void ConfigurePasswordField(views::Textfield* password_field) {
+  const auto password_field_name =
+      l10n_util::GetStringUTF16(IDS_ASH_LOGIN_POD_PASSWORD_PLACEHOLDER);
+  password_field->SetAccessibleName(password_field_name);
+  password_field->SetReadOnly(false);
+  password_field->SetTextInputType(ui::TextInputType::TEXT_INPUT_TYPE_PASSWORD);
+  password_field->SetPlaceholderText(password_field_name);
+  AddMargins(password_field);
+}
+
+void ConfigureInvalidPasswordLabel(views::Label* invalid_password_label) {
+  invalid_password_label->SetProperty(views::kCrossAxisAlignmentKey,
+                                      views::LayoutAlignment::kStart);
+  invalid_password_label->SetEnabledColor(SK_ColorRED);
+  AddMargins(invalid_password_label);
+}
+
+void CenterWidgetOnPrimaryDisplay(views::Widget* widget) {
+  auto bounds = display::Screen::GetScreen()->GetPrimaryDisplay().work_area();
+  bounds.ClampToCenteredSize(widget->GetContentsView()->GetPreferredSize());
+  widget->SetBounds(bounds);
+}
+
+}  // namespace
+
+// static
+AuthenticationDialog* AuthenticationDialog::Show(
+    OnSubmitCallback submit_callback) {
+  auto* authentication_dialog =
+      new AuthenticationDialog(std::move(submit_callback));
+  auto* widget = DialogDelegateView::CreateDialogWidget(authentication_dialog,
+                                                        /*context=*/nullptr,
+                                                        /*parent=*/nullptr);
+  CenterWidgetOnPrimaryDisplay(widget);
+  widget->Show();
+  authentication_dialog->Init();
+  return authentication_dialog;
+}
+
+AuthenticationDialog::~AuthenticationDialog() = default;
+
+void AuthenticationDialog::Init() {
+  ConfigureOkButton();
+  password_field_->RequestFocus();
+}
+
+void AuthenticationDialog::NotifyResult(Result result,
+                                        const std::u16string& token,
+                                        base::TimeDelta timeout) {
+  std::move(on_submit_).Run(result, token, timeout);
+}
+
+void AuthenticationDialog::CancelAuthAttempt() {
+  NotifyResult(Result::kAborted, u"", base::Seconds(0));
+}
+
+void AuthenticationDialog::OnSubmit() {
+  // TODO(crbug.com/1271551): Call appropriate backends to get token
+  // and notify interested parties with |AuthenticationDialog::NotifyResult|
+  // For now, we always assume the given password is invalid
+  password_field_->SetInvalid(true);
+  password_field_->SelectAll(false);
+  invalid_password_label_->SetText(
+      l10n_util::GetStringUTF16(IDS_ASH_LOGIN_ERROR_AUTHENTICATING));
+}
+
+void AuthenticationDialog::ConfigureChildViews() {
+  ConfigurePasswordField(password_field_);
+  ConfigureInvalidPasswordLabel(invalid_password_label_);
+}
+
+void AuthenticationDialog::ConfigureOkButton() {
+  views::LabelButton* ok_button = GetOkButton();
+  ok_button->SetText(
+      l10n_util::GetStringUTF16(IDS_ASH_LOGIN_SUBMIT_BUTTON_ACCESSIBLE_NAME));
+  ok_button->SetCallback(base::BindRepeating(&AuthenticationDialog::OnSubmit,
+                                             base::Unretained(this)));
+}
+
+AuthenticationDialog::AuthenticationDialog(OnSubmitCallback submit_callback)
+    : password_field_(AddChildView(std::make_unique<views::Textfield>())),
+      invalid_password_label_(AddChildView(std::make_unique<views::Label>())),
+      on_submit_(std::move(submit_callback)) {
+  // Dialog setup
+  set_fixed_width(views::LayoutProvider::Get()->GetDistanceMetric(
+      views::DistanceMetric::DISTANCE_BUBBLE_PREFERRED_WIDTH));
+  SetTitle(l10n_util::GetStringUTF16(IDS_ASH_IN_SESSION_AUTH_TITLE));
+  SetModalType(ui::MODAL_TYPE_SYSTEM);
+
+  // Callback setup
+  SetCancelCallback(base::BindOnce(&AuthenticationDialog::CancelAuthAttempt,
+                                   base::Unretained(this)));
+  SetCloseCallback(base::BindOnce(&AuthenticationDialog::CancelAuthAttempt,
+                                  base::Unretained(this)));
+
+  SetLayoutManager(std::make_unique<views::FlexLayout>())
+      ->SetOrientation(views::LayoutOrientation::kVertical)
+      .SetCollapseMargins(true);
+
+  ConfigureChildViews();
+}
+
+}  // namespace ash
diff --git a/ash/in_session_auth/authentication_dialog.h b/ash/in_session_auth/authentication_dialog.h
new file mode 100644
index 0000000..44d8ac03
--- /dev/null
+++ b/ash/in_session_auth/authentication_dialog.h
@@ -0,0 +1,68 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_IN_SESSION_AUTH_AUTHENTICATION_DIALOG_H_
+#define ASH_IN_SESSION_AUTH_AUTHENTICATION_DIALOG_H_
+
+#include "ash/ash_export.h"
+#include "base/time/time.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/window/dialog_delegate.h"
+
+namespace views {
+class Textfield;
+}
+
+namespace ash {
+
+// To be used for in-session authentication. Currently, only password
+// is supported, however, there are plans to enrich this dialog to eventually
+// support all configured forms of authentication on the system.
+class ASH_EXPORT AuthenticationDialog : public views::DialogDelegateView {
+ public:
+  enum class Result { kSuccess, kAborted };
+
+  // Callback used to notify invokers of the dialog of success/failure.
+  // |timeout| here is the length of time the token retrieved from
+  // backends is valid for
+  using OnSubmitCallback = base::OnceCallback<void(Result result,
+                                                   const std::u16string& token,
+                                                   base::TimeDelta timeout)>;
+
+  // Creates and displays a new instance of a widget that hosts the
+  // AuthenticationDialog, returning a pointer to it.
+  // |submit_callback| is called whenever the "Submit" button is clicked
+  static AuthenticationDialog* Show(OnSubmitCallback submit_callback);
+
+  ~AuthenticationDialog() override;
+
+  // Called post widget initialization. For now, this configures the Ok button
+  // with custom behavior needed to handle retry of password entry. Also focuses
+  // the text input field.
+  void Init();
+
+ private:
+  explicit AuthenticationDialog(OnSubmitCallback submit_callback);
+
+  void NotifyResult(Result result,
+                    const std::u16string& token,
+                    base::TimeDelta timeout);
+
+  void ConfigureOkButton();
+
+  void CancelAuthAttempt();
+
+  void OnSubmit();
+
+  void ConfigureChildViews();
+
+  views::Textfield* password_field_;
+  views::Label* invalid_password_label_;
+
+  OnSubmitCallback on_submit_;
+};
+
+}  // namespace ash
+
+#endif  // ASH_IN_SESSION_AUTH_AUTHENTICATION_DIALOG_H_
diff --git a/ash/in_session_auth/authentication_dialog_unittest.cc b/ash/in_session_auth/authentication_dialog_unittest.cc
new file mode 100644
index 0000000..ea523f6
--- /dev/null
+++ b/ash/in_session_auth/authentication_dialog_unittest.cc
@@ -0,0 +1,45 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/in_session_auth/authentication_dialog.h"
+
+#include "ash/test/ash_test_base.h"
+#include "base/test/bind.h"
+#include "ui/views/controls/textfield/textfield.h"
+
+namespace ash {
+namespace {
+
+class AuthenticationDialogTest : public AshTestBase {
+ public:
+  void SetUp() override {
+    AshTestBase::SetUp();
+    dialog_ = AuthenticationDialog::Show(base::BindLambdaForTesting(
+        [&](AuthenticationDialog::Result result, const std::u16string& token,
+            base::TimeDelta timeout) {
+          result_ = result;
+          called_ = true;
+        }));
+  }
+
+ protected:
+  bool called_ = false;
+  AuthenticationDialog::Result result_;
+  AuthenticationDialog* dialog_;
+};
+
+TEST_F(AuthenticationDialogTest, CallbackCalledOnCancel) {
+  dialog_->Cancel();
+  EXPECT_TRUE(called_);
+  EXPECT_EQ(result_, AuthenticationDialog::Result::kAborted);
+}
+
+TEST_F(AuthenticationDialogTest, CallbackCalledOnClose) {
+  dialog_->Close();
+  EXPECT_TRUE(called_);
+  EXPECT_EQ(result_, AuthenticationDialog::Result::kAborted);
+}
+
+}  // namespace
+}  // namespace ash
\ No newline at end of file
diff --git a/ash/login/ui/fingerprint_auth_factor_model.cc b/ash/login/ui/fingerprint_auth_factor_model.cc
index b31ecb85..43add99 100644
--- a/ash/login/ui/fingerprint_auth_factor_model.cc
+++ b/ash/login/ui/fingerprint_auth_factor_model.cc
@@ -93,7 +93,7 @@
     case FingerprintState::AVAILABLE_WITH_TOUCH_SENSOR_WARNING:
       return IDS_ASH_LOGIN_FINGERPRINT_UNLOCK_TOUCH_SENSOR;
     case FingerprintState::DISABLED_FROM_ATTEMPTS:
-      return IDS_FINGERPRINT_LABEL_NOT_AUTHENTICATED;
+      return IDS_ASH_LOGIN_FINGERPRINT_UNLOCK_DISABLED_FROM_ATTEMPTS;
     case FingerprintState::DISABLED_FROM_TIMEOUT:
       return can_use_pin_ ? IDS_AUTH_FACTOR_LABEL_PASSWORD_OR_PIN_REQUIRED
                           : IDS_AUTH_FACTOR_LABEL_PASSWORD_REQUIRED;
diff --git a/ash/login/ui/login_auth_user_view.cc b/ash/login/ui/login_auth_user_view.cc
index 60f5eed..a0c57d9 100644
--- a/ash/login/ui/login_auth_user_view.cc
+++ b/ash/login/ui/login_auth_user_view.cc
@@ -259,7 +259,7 @@
         case FingerprintState::AVAILABLE_WITH_TOUCH_SENSOR_WARNING:
           return IDS_ASH_LOGIN_FINGERPRINT_UNLOCK_TOUCH_SENSOR;
         case FingerprintState::DISABLED_FROM_ATTEMPTS:
-          return IDS_ASH_LOGIN_FINGERPRINT_UNLOCK_DISABLED_FROM_ATTEMPTS;
+          return IDS_ASH_LOGIN_FINGERPRINT_UNLOCK_DISABLED_FROM_ATTEMPTS_OLD;
         case FingerprintState::DISABLED_FROM_TIMEOUT:
           if (can_use_pin)
             return IDS_ASH_LOGIN_FINGERPRINT_UNLOCK_PIN_OR_PASSWORD_REQUIRED;
diff --git a/ash/policy/policy_recommendation_restorer_unittest.cc b/ash/policy/policy_recommendation_restorer_unittest.cc
index 52c0bd6..cb7074d 100644
--- a/ash/policy/policy_recommendation_restorer_unittest.cc
+++ b/ash/policy/policy_recommendation_restorer_unittest.cc
@@ -32,6 +32,7 @@
             /*managed_prefs=*/new TestingPrefStore,
             /*supervised_user_prefs=*/new TestingPrefStore,
             /*extension_prefs=*/new TestingPrefStore,
+            /*standalone_browser_prefs=*/new TestingPrefStore,
             /*user_prefs=*/new TestingPrefStore,
             recommended_prefs_,
             new user_prefs::PrefRegistrySyncable,
diff --git a/ash/shelf/drag_window_from_shelf_controller_unittest.cc b/ash/shelf/drag_window_from_shelf_controller_unittest.cc
index 8f67a464..fe64b2d 100644
--- a/ash/shelf/drag_window_from_shelf_controller_unittest.cc
+++ b/ash/shelf/drag_window_from_shelf_controller_unittest.cc
@@ -36,6 +36,7 @@
 #include "ui/aura/client/window_parenting_client.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
+#include "ui/compositor/test/test_utils.h"
 #include "ui/gfx/geometry/point_f.h"
 #include "ui/views/widget/widget.h"
 #include "ui/wm/core/transient_window_manager.h"
@@ -107,17 +108,17 @@
   void CancelDrag() { window_drag_controller_->CancelDrag(); }
   void WaitForHomeLauncherAnimationToFinish() {
     // Wait until home launcher animation finishes.
-    while (GetAppListTestHelper()
-               ->GetAppListView()
-               ->GetWidget()
-               ->GetLayer()
-               ->GetAnimator()
-               ->is_animating()) {
-      base::RunLoop run_loop;
-      base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
-          FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(200));
-      run_loop.Run();
-    }
+    ui::Layer* layer =
+        GetAppListTestHelper()->GetAppListView()->GetWidget()->GetLayer();
+    ui::Compositor* compositor = layer->GetCompositor();
+
+    while (layer->GetAnimator()->is_animating())
+      EXPECT_TRUE(ui::WaitForNextFrameToBePresented(compositor));
+
+    // Ensure there is one more frame presented after animation finishes
+    // to allow animation throughput data is passed from cc to ui.
+    ignore_result(
+        ui::WaitForNextFrameToBePresented(compositor, base::Milliseconds(200)));
   }
 
   SplitViewController* split_view_controller() {
@@ -1393,4 +1394,4 @@
   EXPECT_FALSE(transient_child_win1->IsVisible());
   EXPECT_FALSE(transient_child_win2->IsVisible());
 }
-}  // namespace ash
\ No newline at end of file
+}  // namespace ash
diff --git a/ash/system/power/power_prefs_unittest.cc b/ash/system/power/power_prefs_unittest.cc
index 6f65319..5081c2e 100644
--- a/ash/system/power/power_prefs_unittest.cc
+++ b/ash/system/power/power_prefs_unittest.cc
@@ -220,6 +220,7 @@
     auto pref_value_store = std::make_unique<PrefValueStore>(
         managed_pref_store_.get() /* managed_prefs */,
         nullptr /* supervised_user_prefs */, nullptr /* extension_prefs */,
+        nullptr /* standalone_browser_prefs */,
         nullptr /* command_line_prefs */, user_pref_store_.get(),
         nullptr /* recommended_prefs */, pref_registry_->defaults().get(),
         pref_notifier.get());
diff --git a/ash/wallpaper/wallpaper_controller_impl.cc b/ash/wallpaper/wallpaper_controller_impl.cc
index 3683768..6c6a4e3 100644
--- a/ash/wallpaper/wallpaper_controller_impl.cc
+++ b/ash/wallpaper/wallpaper_controller_impl.cc
@@ -2623,7 +2623,8 @@
 
     for (size_t i = 0; i < variants.size(); i++) {
       ImageDownloader::Get()->Download(
-          GURL(variants.at(i).url), NO_TRAFFIC_ANNOTATION_YET,
+          GURL(variants.at(i).url.spec() + GetBackdropWallpaperSuffix()),
+          NO_TRAFFIC_ANNOTATION_YET,
           base::BindOnce(
               &WallpaperControllerImpl::OnOnlineWallpaperVariantDownloaded,
               set_wallpaper_weak_factory_.GetWeakPtr(), params, on_done,
diff --git a/ash/webui/diagnostics_ui/backend/BUILD.gn b/ash/webui/diagnostics_ui/backend/BUILD.gn
index 0de4aa0..99abf73 100644
--- a/ash/webui/diagnostics_ui/backend/BUILD.gn
+++ b/ash/webui/diagnostics_ui/backend/BUILD.gn
@@ -47,6 +47,7 @@
   ]
 
   deps = [
+    "//ash",
     "//ash/constants:constants",
     "//ash/public/cpp",
     "//ash/webui/diagnostics_ui/mojom",
@@ -65,6 +66,8 @@
     "//services/device/public/mojom",
     "//ui/base",
     "//ui/base/ime/ash",
+    "//ui/chromeos/events",
+    "//ui/events/devices",
     "//ui/events/ozone",
     "//ui/events/ozone/evdev:event_device_info",
     "//ui/events/ozone/layout",
@@ -118,9 +121,11 @@
     "//components/sync_preferences:test_support",
     "//content/test:test_support",
     "//dbus",
+    "//device/udev_linux:test_support",
     "//services/data_decoder/public/cpp:test_support",
     "//services/device/public/cpp:test_support",
     "//testing/gtest",
+    "//ui/chromeos/events",
     "//ui/events/ozone",
     "//ui/events/ozone/evdev:event_device_info_test_utils",
     "//ui/gfx",
diff --git a/ash/webui/diagnostics_ui/backend/DEPS b/ash/webui/diagnostics_ui/backend/DEPS
new file mode 100644
index 0000000..bb33d0a
--- /dev/null
+++ b/ash/webui/diagnostics_ui/backend/DEPS
@@ -0,0 +1,5 @@
+specific_include_rules = {
+  "input_data_provider_unittest\.cc": [
+    "+device/udev_linux/fake_udev_loader.h",
+  ]
+}
diff --git a/ash/webui/diagnostics_ui/backend/input_data_provider.cc b/ash/webui/diagnostics_ui/backend/input_data_provider.cc
index 3e93f9b..04f2b76 100644
--- a/ash/webui/diagnostics_ui/backend/input_data_provider.cc
+++ b/ash/webui/diagnostics_ui/backend/input_data_provider.cc
@@ -20,6 +20,8 @@
 #include "base/strings/string_util.h"
 #include "chromeos/system/statistics_provider.h"
 #include "ui/base/ime/ash/input_method_manager.h"
+#include "ui/events/devices/device_util_linux.h"
+#include "ui/events/devices/input_device.h"
 #include "ui/events/event_constants.h"
 #include "ui/events/keycodes/dom/keycode_converter.h"
 #include "ui/events/ozone/evdev/event_device_info.h"
@@ -33,27 +35,27 @@
   const std::string base_name_prefix = "event";
 
   std::string base_name = path.BaseName().value();
-  DCHECK(base::StartsWith(base_name, base_name_prefix));
+  if (!base::StartsWith(base_name, base_name_prefix))
+    return false;
   base_name.erase(0, base_name_prefix.length());
   return base::StringToInt(base_name, id);
 }
 
-mojom::ConnectionType ConnectionTypeFromInputDeviceType(
-    ui::InputDeviceType type) {
-  switch (type) {
-    case ui::InputDeviceType::INPUT_DEVICE_INTERNAL:
-      return mojom::ConnectionType::kInternal;
-    case ui::InputDeviceType::INPUT_DEVICE_USB:
-      return mojom::ConnectionType::kUsb;
-    case ui::InputDeviceType::INPUT_DEVICE_BLUETOOTH:
-      return mojom::ConnectionType::kBluetooth;
-    case ui::InputDeviceType::INPUT_DEVICE_UNKNOWN:
-      return mojom::ConnectionType::kUnknown;
-  }
+// Determine if this particular evdev provides touchpad or touchscreen input;
+// we do not want stylus devices, which also claim to be touchscreens.
+bool IsTouchInputDevice(InputDeviceInformation* device_info) {
+  return (device_info->event_device_info.HasTouchpad() ||
+          (device_info->event_device_info.HasTouchscreen() &&
+           !device_info->event_device_info.HasStylus()));
 }
+
 }  // namespace
 
-std::unique_ptr<ui::EventDeviceInfo> InputDeviceInfoHelper::GetDeviceInfo(
+// All blockings calls for identifying hardware need to go here: both
+// EventDeviceInfo::Initialize and ui::GetInputPathInSys can block in
+// base::MakeAbsoluteFilePath.
+std::unique_ptr<InputDeviceInformation> InputDeviceInfoHelper::GetDeviceInfo(
+    int id,
     base::FilePath path) {
   base::ScopedFD fd(open(path.value().c_str(), O_RDWR | O_NONBLOCK));
   if (fd.get() < 0) {
@@ -61,12 +63,26 @@
     return nullptr;
   }
 
-  auto device_info = std::make_unique<ui::EventDeviceInfo>();
-  if (!device_info->Initialize(fd.get(), path)) {
+  auto info = std::make_unique<InputDeviceInformation>();
+
+  if (!info->event_device_info.Initialize(fd.get(), path)) {
     LOG(ERROR) << "Failed to get device info for " << path;
     return nullptr;
   }
-  return device_info;
+
+  const base::FilePath sys_path = ui::GetInputPathInSys(path);
+
+  info->path = path;
+  info->evdev_id = id;
+  info->connection_type = InputDataProvider::ConnectionTypeFromInputDeviceType(
+      info->event_device_info.device_type());
+  info->input_device = ui::InputDevice(
+      id, info->event_device_info.device_type(), info->event_device_info.name(),
+      info->event_device_info.phys(), sys_path,
+      info->event_device_info.vendor_id(), info->event_device_info.product_id(),
+      info->event_device_info.version());
+
+  return info;
 }
 
 InputDataProvider::InputDataProvider()
@@ -84,6 +100,21 @@
   device_manager_->RemoveObserver(this);
 }
 
+// static
+mojom::ConnectionType InputDataProvider::ConnectionTypeFromInputDeviceType(
+    ui::InputDeviceType type) {
+  switch (type) {
+    case ui::InputDeviceType::INPUT_DEVICE_INTERNAL:
+      return mojom::ConnectionType::kInternal;
+    case ui::InputDeviceType::INPUT_DEVICE_USB:
+      return mojom::ConnectionType::kUsb;
+    case ui::InputDeviceType::INPUT_DEVICE_BLUETOOTH:
+      return mojom::ConnectionType::kBluetooth;
+    case ui::InputDeviceType::INPUT_DEVICE_UNKNOWN:
+      return mojom::ConnectionType::kUnknown;
+  }
+}
+
 void InputDataProvider::Initialize() {
   device_manager_->AddObserver(this);
   device_manager_->ScanDevices(this);
@@ -104,6 +135,7 @@
 void InputDataProvider::OnBoundInterfaceDisconnect() {
   receiver_.reset();
 }
+
 void InputDataProvider::GetConnectedDevices(
     GetConnectedDevicesCallback callback) {
   std::vector<mojom::KeyboardInfoPtr> keyboard_vector;
@@ -158,10 +190,12 @@
 
   if (event.action_type() == ui::DeviceEvent::ActionType::ADD) {
     info_helper_.AsyncCall(&InputDeviceInfoHelper::GetDeviceInfo)
-        .WithArgs(event.path())
+        .WithArgs(id, event.path())
         .Then(base::BindOnce(&InputDataProvider::ProcessDeviceInfo,
-                             weak_factory_.GetWeakPtr(), id));
+                             weak_factory_.GetWeakPtr()));
+
   } else {
+    DCHECK(event.action_type() == ui::DeviceEvent::ActionType::REMOVE);
     if (keyboards_.contains(id)) {
       keyboards_.erase(id);
       for (auto& observer : connected_devices_observers_) {
@@ -176,40 +210,39 @@
   }
 }
 
+InputDeviceInformation::InputDeviceInformation() = default;
+InputDeviceInformation::~InputDeviceInformation() = default;
+
 void InputDataProvider::ProcessDeviceInfo(
-    int id,
-    std::unique_ptr<ui::EventDeviceInfo> device_info) {
+    std::unique_ptr<InputDeviceInformation> device_info) {
   if (device_info == nullptr) {
     return;
   }
 
-  if (device_info->HasTouchpad() ||
-      (device_info->HasTouchscreen() && !device_info->HasStylus())) {
-    AddTouchDevice(id, device_info.get());
-  } else if (device_info->HasKeyboard()) {
-    AddKeyboard(id, device_info.get());
+  if (IsTouchInputDevice(device_info.get())) {
+    AddTouchDevice(device_info.get());
+  } else if (device_info->event_device_info.HasKeyboard()) {
+    AddKeyboard(device_info.get());
   }
 }
 
-void InputDataProvider::AddTouchDevice(int id,
-                                       const ui::EventDeviceInfo* device_info) {
-  touch_devices_[id] = touch_helper_.ConstructTouchDevice(
-      id, device_info,
-      ConnectionTypeFromInputDeviceType(device_info->device_type()));
+void InputDataProvider::AddTouchDevice(
+    const InputDeviceInformation* device_info) {
+  touch_devices_[device_info->evdev_id] =
+      touch_helper_.ConstructTouchDevice(device_info);
 
   for (auto& observer : connected_devices_observers_) {
-    observer->OnTouchDeviceConnected(touch_devices_[id]->Clone());
+    observer->OnTouchDeviceConnected(
+        touch_devices_[device_info->evdev_id]->Clone());
   }
 }
 
-void InputDataProvider::AddKeyboard(int id,
-                                    const ui::EventDeviceInfo* device_info) {
-  keyboards_[id] = keyboard_helper_.ConstructKeyboard(
-      id, device_info,
-      ConnectionTypeFromInputDeviceType(device_info->device_type()));
+void InputDataProvider::AddKeyboard(const InputDeviceInformation* device_info) {
+  keyboards_[device_info->evdev_id] =
+      keyboard_helper_.ConstructKeyboard(device_info);
 
   for (auto& observer : connected_devices_observers_) {
-    observer->OnKeyboardConnected(keyboards_[id]->Clone());
+    observer->OnKeyboardConnected(keyboards_[device_info->evdev_id]->Clone());
   }
 }
 
diff --git a/ash/webui/diagnostics_ui/backend/input_data_provider.h b/ash/webui/diagnostics_ui/backend/input_data_provider.h
index 699e974e..cf86582 100644
--- a/ash/webui/diagnostics_ui/backend/input_data_provider.h
+++ b/ash/webui/diagnostics_ui/backend/input_data_provider.h
@@ -18,6 +18,7 @@
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote_set.h"
+#include "ui/chromeos/events/event_rewriter_chromeos.h"
 #include "ui/events/ozone/device/device_event.h"
 #include "ui/events/ozone/device/device_event_observer.h"
 #include "ui/events/ozone/device/device_manager.h"
@@ -26,14 +27,37 @@
 namespace ash {
 namespace diagnostics {
 
+// Wrapper for tracking several pieces of information about an evdev-backed
+// device.
+class InputDeviceInformation {
+ public:
+  InputDeviceInformation();
+  InputDeviceInformation(const InputDeviceInformation& other) = delete;
+  InputDeviceInformation& operator=(const InputDeviceInformation& other) =
+      delete;
+  ~InputDeviceInformation();
+
+  int evdev_id;
+  ui::EventDeviceInfo event_device_info;
+  ui::InputDevice input_device;
+  mojom::ConnectionType connection_type;
+  base::FilePath path;
+};
+
+// Class for running GetDeviceInfo in its own sequence that can block.
 class InputDeviceInfoHelper {
  public:
+  InputDeviceInfoHelper() {}
   virtual ~InputDeviceInfoHelper() {}
 
-  virtual std::unique_ptr<ui::EventDeviceInfo> GetDeviceInfo(
+  virtual std::unique_ptr<InputDeviceInformation> GetDeviceInfo(
+      int evdev_id,
       base::FilePath path);
 };
 
+// Provides information about input devices connected to the system. Implemented
+// in the browser process and called by the Diagnostics SWA (a renderer
+// process).
 class InputDataProvider : public mojom::InputDataProvider,
                           public ui::DeviceEventObserver {
  public:
@@ -48,6 +72,8 @@
   // Handler for when remote attached to |receiver_| disconnects.
   void OnBoundInterfaceDisconnect();
   bool ReceiverIsBound();
+  static mojom::ConnectionType ConnectionTypeFromInputDeviceType(
+      ui::InputDeviceType type);
 
   // mojom::InputDataProvider:
   void GetConnectedDevices(GetConnectedDevicesCallback callback) override;
@@ -69,15 +95,15 @@
  private:
   void Initialize();
 
-  void ProcessDeviceInfo(int id,
-                         std::unique_ptr<ui::EventDeviceInfo> device_info);
+  void ProcessDeviceInfo(std::unique_ptr<InputDeviceInformation> device_info);
 
-  void AddTouchDevice(int id, const ui::EventDeviceInfo* device_info);
-  void AddKeyboard(int id, const ui::EventDeviceInfo* device_info);
+  void AddTouchDevice(const InputDeviceInformation* device_info);
+  void AddKeyboard(const InputDeviceInformation* device_info);
 
   InputDataProviderKeyboard keyboard_helper_;
   InputDataProviderTouch touch_helper_;
 
+  // Map by evdev ids to information blocks
   base::flat_map<int, mojom::KeyboardInfoPtr> keyboards_;
   base::flat_map<int, mojom::TouchDeviceInfoPtr> touch_devices_;
 
diff --git a/ash/webui/diagnostics_ui/backend/input_data_provider_keyboard.cc b/ash/webui/diagnostics_ui/backend/input_data_provider_keyboard.cc
index 568bad7..aab7065 100644
--- a/ash/webui/diagnostics_ui/backend/input_data_provider_keyboard.cc
+++ b/ash/webui/diagnostics_ui/backend/input_data_provider_keyboard.cc
@@ -4,10 +4,17 @@
 
 #include "ash/webui/diagnostics_ui/backend/input_data_provider_keyboard.h"
 
+#include <fcntl.h>
+#include <linux/input.h>
 #include <vector>
 
 #include "ash/constants/ash_switches.h"
+#include "ash/display/privacy_screen_controller.h"
+#include "ash/shell.h"
+#include "ash/webui/diagnostics_ui/backend/input_data_provider.h"
 #include "base/command_line.h"
+#include "base/containers/fixed_flat_map.h"
+#include "base/files/scoped_file.h"
 #include "base/logging.h"
 #include "base/run_loop.h"
 #include "base/strings/strcat.h"
@@ -16,6 +23,9 @@
 #include "base/strings/string_util.h"
 #include "chromeos/system/statistics_provider.h"
 #include "ui/base/ime/ash/input_method_manager.h"
+#include "ui/chromeos/events/event_rewriter_chromeos.h"
+#include "ui/events/devices/device_util_linux.h"
+#include "ui/events/devices/input_device.h"
 #include "ui/events/event_constants.h"
 #include "ui/events/keycodes/dom/keycode_converter.h"
 #include "ui/events/ozone/evdev/event_device_info.h"
@@ -25,6 +35,135 @@
 
 namespace {
 
+enum {
+  kFKey1 = 0,
+  kFKey2,
+  kFKey3,
+  kFKey4,
+  kFKey5,
+  kFKey6,
+  kFKey7,
+  kFKey8,
+  kFKey9,
+  kFKey10,
+  kFKey11,
+  kFKey12,
+  kFKey13,
+  kFKey14,
+  kFKey15
+};
+
+// Mapping from keyboard scancodes to TopRowKeys (must be in scancode-sorted
+// order). This replicates and should be identical to the mapping behaviour
+// of ChromeOS: changes will be needed if new AT scancodes or HID mappings
+// are used in a top-row key, likely added in
+// ui/events/keycodes/dom/dom_code_data.inc
+//
+// Note that there are no dedicated scancodes for kScreenMirror.
+constexpr auto kScancodeMapping =
+    base::MakeFixedFlatMap<uint32_t, mojom::TopRowKey>({
+        // Vivaldi extended Set-1 AT-style scancodes
+        {0x90, mojom::TopRowKey::kPreviousTrack},
+        {0x91, mojom::TopRowKey::kFullscreen},
+        {0x92, mojom::TopRowKey::kOverview},
+        {0x93, mojom::TopRowKey::kScreenshot},
+        {0x94, mojom::TopRowKey::kScreenBrightnessDown},
+        {0x95, mojom::TopRowKey::kScreenBrightnessUp},
+        {0x96, mojom::TopRowKey::kPrivacyScreenToggle},
+        {0x97, mojom::TopRowKey::kKeyboardBacklightDown},
+        {0x98, mojom::TopRowKey::kKeyboardBacklightUp},
+        {0x99, mojom::TopRowKey::kNextTrack},
+        {0x9A, mojom::TopRowKey::kPlayPause},
+        {0xA0, mojom::TopRowKey::kVolumeMute},
+        {0xAE, mojom::TopRowKey::kVolumeDown},
+        {0xB0, mojom::TopRowKey::kVolumeUp},
+        {0xD3, mojom::TopRowKey::kDelete},  // Only relevant for Drallion.
+        {0xE9, mojom::TopRowKey::kForward},
+        {0xEA, mojom::TopRowKey::kBack},
+        {0xE7, mojom::TopRowKey::kRefresh},
+
+        // HID 32-bit usage codes
+        {0x070046, mojom::TopRowKey::kScreenshot},
+        {0x0C00E2, mojom::TopRowKey::kVolumeMute},
+        {0x0C00E9, mojom::TopRowKey::kVolumeUp},
+        {0x0C00EA, mojom::TopRowKey::kVolumeDown},
+        {0x0C006F, mojom::TopRowKey::kScreenBrightnessUp},
+        {0x0C0070, mojom::TopRowKey::kScreenBrightnessDown},
+        {0x0C0079, mojom::TopRowKey::kKeyboardBacklightUp},
+        {0x0C007A, mojom::TopRowKey::kKeyboardBacklightDown},
+        {0x0C00B5, mojom::TopRowKey::kNextTrack},
+        {0x0C00B6, mojom::TopRowKey::kPreviousTrack},
+        {0x0C00CD, mojom::TopRowKey::kPlayPause},
+        {0x0C0224, mojom::TopRowKey::kBack},
+        {0x0C0225, mojom::TopRowKey::kForward},
+        {0x0C0227, mojom::TopRowKey::kRefresh},
+        {0x0C0232, mojom::TopRowKey::kFullscreen},
+        {0x0C029F, mojom::TopRowKey::kOverview},
+        {0x0C02D0, mojom::TopRowKey::kPrivacyScreenToggle},
+    });
+
+// Hard-coded top-row key mappings. These are intended to match the behaviour of
+// EventRewriterChromeOS::RewriteFunctionKeys for historical keyboards. No
+// updates should be needed, as all new keyboards are expected to be using
+// customizable top row keys (vivaldi).
+
+constexpr mojom::TopRowKey kSystemKeys1[] = {
+    mojom::TopRowKey::kBack,
+    mojom::TopRowKey::kForward,
+    mojom::TopRowKey::kRefresh,
+    mojom::TopRowKey::kFullscreen,
+    mojom::TopRowKey::kOverview,
+    mojom::TopRowKey::kScreenBrightnessDown,
+    mojom::TopRowKey::kScreenBrightnessUp,
+    mojom::TopRowKey::kVolumeMute,
+    mojom::TopRowKey::kVolumeDown,
+    mojom::TopRowKey::kVolumeUp};
+
+constexpr mojom::TopRowKey kSystemKeys2[] = {
+    mojom::TopRowKey::kBack,
+    mojom::TopRowKey::kRefresh,
+    mojom::TopRowKey::kFullscreen,
+    mojom::TopRowKey::kOverview,
+    mojom::TopRowKey::kScreenBrightnessDown,
+    mojom::TopRowKey::kScreenBrightnessUp,
+    mojom::TopRowKey::kPlayPause,
+    mojom::TopRowKey::kVolumeMute,
+    mojom::TopRowKey::kVolumeDown,
+    mojom::TopRowKey::kVolumeUp};
+
+constexpr mojom::TopRowKey kSystemKeysWilco[] = {
+    mojom::TopRowKey::kBack,
+    mojom::TopRowKey::kRefresh,
+    mojom::TopRowKey::kFullscreen,
+    mojom::TopRowKey::kOverview,
+    mojom::TopRowKey::kScreenBrightnessDown,
+    mojom::TopRowKey::kScreenBrightnessUp,
+    mojom::TopRowKey::kVolumeMute,
+    mojom::TopRowKey::kVolumeDown,
+    mojom::TopRowKey::kVolumeUp,
+    mojom::TopRowKey::kNone,          // F10
+    mojom::TopRowKey::kNone,          // F11
+    mojom::TopRowKey::kScreenMirror,  // F12
+    mojom::TopRowKey::kDelete  // Just a normal Delete key, but in the top row.
+};
+
+constexpr mojom::TopRowKey kSystemKeysDrallion[] = {
+    mojom::TopRowKey::kBack,
+    mojom::TopRowKey::kRefresh,
+    mojom::TopRowKey::kFullscreen,
+    mojom::TopRowKey::kOverview,
+    mojom::TopRowKey::kScreenBrightnessDown,
+    mojom::TopRowKey::kScreenBrightnessUp,
+    mojom::TopRowKey::kVolumeMute,
+    mojom::TopRowKey::kVolumeDown,
+    mojom::TopRowKey::kVolumeUp,
+    mojom::TopRowKey::kNone,  // F10
+    mojom::TopRowKey::kNone,  // F11
+    mojom::TopRowKey::kNone,  // F12 - May be Privacy Screen on some models.
+    mojom::TopRowKey::kScreenMirror,
+    mojom::TopRowKey::kDelete  // Just a normal Delete key, but in the top row.
+};
+
 mojom::MechanicalLayout GetSystemMechanicalLayout() {
   chromeos::system::StatisticsProvider* stats_provider =
       chromeos::system::StatisticsProvider::GetInstance();
@@ -151,17 +290,96 @@
   return glyph_set;
 }
 
+void InputDataProviderKeyboard::ProcessKeyboardTopRowLayout(
+    const InputDeviceInformation* device_info,
+    ui::EventRewriterChromeOS::KeyboardTopRowLayout* out_top_row_layout,
+    std::vector<mojom::TopRowKey>* out_top_row_keys) {
+  ui::InputDevice input_device = device_info->input_device;
+  ui::EventRewriterChromeOS::DeviceType device_type;
+  ui::EventRewriterChromeOS::KeyboardTopRowLayout top_row_layout;
+  base::flat_map<uint32_t, ui::EventRewriterChromeOS::MutableKeyState>
+      scan_code_map;
+  ui::EventRewriterChromeOS::IdentifyKeyboard(input_device, &device_type,
+                                              &top_row_layout, &scan_code_map);
+
+  // Simple array in physical order from left to right
+  std::vector<mojom::TopRowKey> top_row_keys = {};
+
+  switch (top_row_layout) {
+    case ui::EventRewriterChromeOS::kKbdTopRowLayoutWilco:
+      top_row_keys.assign(std::begin(kSystemKeysWilco),
+                          std::end(kSystemKeysWilco));
+      break;
+
+    case ui::EventRewriterChromeOS::kKbdTopRowLayoutDrallion:
+      top_row_keys.assign(std::begin(kSystemKeysDrallion),
+                          std::end(kSystemKeysDrallion));
+
+      // On some Drallion devices, the F12 key is used for the Privacy Screen.
+
+      // This should be the same logic as in
+      // EventRewriterControllerImpl::Initialize. This is a historic device, and
+      // this logic should not need to be updated, as newer devices will use
+      // custom top row layouts (vivaldi).
+      if (Shell::Get()->privacy_screen_controller() &&
+          Shell::Get()->privacy_screen_controller()->IsSupported()) {
+        top_row_keys[kFKey12] = mojom::TopRowKey::kPrivacyScreenToggle;
+      }
+
+      break;
+
+    case ui::EventRewriterChromeOS::kKbdTopRowLayoutCustom:
+
+      // Process scan-code map generated from custom top-row key layout: it maps
+      // from physical scan codes to several things, including VKEY key-codes,
+      // which we will use to produce indexes.
+
+      for (auto iter = scan_code_map.begin(); iter != scan_code_map.end();
+           iter++) {
+        size_t fn_key_number = iter->second.key_code - ui::VKEY_F1;
+        uint32_t scancode = iter->first;
+
+        if (top_row_keys.size() < fn_key_number + 1)
+          top_row_keys.resize(fn_key_number + 1, mojom::TopRowKey::kNone);
+
+        if (kScancodeMapping.contains(scancode))
+          top_row_keys[fn_key_number] = kScancodeMapping.at(scancode);
+        else
+          top_row_keys[fn_key_number] = mojom::TopRowKey::kUnknown;
+      }
+      break;
+
+    case ui::EventRewriterChromeOS::kKbdTopRowLayout2:
+      top_row_keys.assign(std::begin(kSystemKeys2), std::end(kSystemKeys2));
+      break;
+
+    case ui::EventRewriterChromeOS::kKbdTopRowLayout1:
+    default:
+      top_row_keys.assign(std::begin(kSystemKeys1), std::end(kSystemKeys1));
+  }
+
+  *out_top_row_layout = std::move(top_row_layout);
+  *out_top_row_keys = std::move(top_row_keys);
+}
+
 mojom::KeyboardInfoPtr InputDataProviderKeyboard::ConstructKeyboard(
-    int id,
-    const ui::EventDeviceInfo* device_info,
-    mojom::ConnectionType connection_type) {
+    const InputDeviceInformation* device_info) {
   mojom::KeyboardInfoPtr result = mojom::KeyboardInfo::New();
-  result->id = id;
-  result->connection_type = connection_type;
-  result->name = device_info->name();
+
+  result->id = device_info->evdev_id;
+  result->connection_type = device_info->connection_type;
+  result->name = device_info->event_device_info.name();
+
+  // TODO(crbug.com/1207678): review support for WWCB keyboards, Chromebase
+  // keyboards, and Dell KM713 Chrome keyboard.
+
+  ui::EventRewriterChromeOS::KeyboardTopRowLayout top_row_layout_type;
+
+  ProcessKeyboardTopRowLayout(device_info, &top_row_layout_type,
+                              &result->top_row_keys);
 
   if (result->connection_type == mojom::ConnectionType::kInternal) {
-    if (device_info->HasKeyEvent(KEY_KBD_LAYOUT_NEXT)) {
+    if (device_info->event_device_info.HasKeyEvent(KEY_KBD_LAYOUT_NEXT)) {
       // Only Dell Enterprise devices have this key, marked by a globe icon.
       result->physical_layout = mojom::PhysicalLayout::kChromeOSDellEnterprise;
     } else {
@@ -176,14 +394,36 @@
             chromeos::switches::kHasNumberPad)
             ? mojom::NumberPadPresence::kPresent
             : mojom::NumberPadPresence::kNotPresent;
+
+    // Log if there is contradictory information.
+    if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+            chromeos::switches::kHasNumberPad) &&
+        !device_info->event_device_info.HasNumberpad())
+      LOG(ERROR) << "OS believes internal numberpad is implemented, but "
+                    "evdev disagrees.";
   } else {
     result->physical_layout = mojom::PhysicalLayout::kUnknown;
-    result->number_pad_present = mojom::NumberPadPresence::kUnknown;
-    // TODO(crbug.com/1207678): support WWCB keyboards, Chromebase keyboards,
-    // and Dell KM713 Chrome keyboard.
+
+    if (top_row_layout_type == ui::EventRewriterChromeOS::KeyboardTopRowLayout::
+                                   kKbdTopRowLayoutCustom) {
+      // If keyboard has WWCB top row custom layout (vivaldi) then we can trust
+      // the HID descriptor to be accurate about presence of keys.
+      result->number_pad_present =
+          !device_info->event_device_info.HasNumberpad()
+              ? mojom::NumberPadPresence::kNotPresent
+              : mojom::NumberPadPresence::kPresent;
+    } else {
+      // Without WWCB information, absence of KP keycodes means it definitely
+      // doesn't have a numberpad, but the presence isn't a reliable indicator.
+      result->number_pad_present =
+          !device_info->event_device_info.HasNumberpad()
+              ? mojom::NumberPadPresence::kNotPresent
+              : mojom::NumberPadPresence::kUnknown;
+    }
   }
 
-  result->has_assistant_key = device_info->HasKeyEvent(KEY_ASSISTANT);
+  result->has_assistant_key =
+      device_info->event_device_info.HasKeyEvent(KEY_ASSISTANT);
 
   return result;
 }
diff --git a/ash/webui/diagnostics_ui/backend/input_data_provider_keyboard.h b/ash/webui/diagnostics_ui/backend/input_data_provider_keyboard.h
index 14776d1..ea6f9481 100644
--- a/ash/webui/diagnostics_ui/backend/input_data_provider_keyboard.h
+++ b/ash/webui/diagnostics_ui/backend/input_data_provider_keyboard.h
@@ -7,6 +7,7 @@
 
 #include "ash/webui/diagnostics_ui/mojom/input_data_provider.mojom.h"
 #include "base/memory/weak_ptr.h"
+#include "ui/chromeos/events/event_rewriter_chromeos.h"
 #include "ui/events/ozone/evdev/event_device_info.h"
 #include "ui/events/ozone/layout/xkb/xkb_evdev_codes.h"
 #include "ui/events/ozone/layout/xkb/xkb_keyboard_layout_engine.h"
@@ -14,6 +15,8 @@
 namespace ash {
 namespace diagnostics {
 
+class InputDeviceInformation;
+
 // Helper to provide InputDataProvider diagnostic interface with
 // keyboard-specific logic.
 class InputDataProviderKeyboard {
@@ -29,15 +32,18 @@
       mojom::InputDataProvider::GetKeyboardVisualLayoutCallback callback);
 
   mojom::KeyboardInfoPtr ConstructKeyboard(
-      int id,
-      const ui::EventDeviceInfo* device_info,
-      mojom::ConnectionType connection_type);
+      const InputDeviceInformation* device_info);
 
  private:
   void ProcessXkbLayout(
       mojom::InputDataProvider::GetKeyboardVisualLayoutCallback callback);
   mojom::KeyGlyphSetPtr LookupGlyphSet(uint32_t evdev_code);
 
+  void ProcessKeyboardTopRowLayout(
+      const InputDeviceInformation* device_info,
+      ui::EventRewriterChromeOS::KeyboardTopRowLayout* out_top_row_layout,
+      std::vector<mojom::TopRowKey>* out_top_row_keys);
+
   ui::XkbEvdevCodes xkb_evdev_codes_;
   ui::XkbKeyboardLayoutEngine xkb_layout_engine_;
 
diff --git a/ash/webui/diagnostics_ui/backend/input_data_provider_touch.cc b/ash/webui/diagnostics_ui/backend/input_data_provider_touch.cc
index 29335cc..48b05e87 100644
--- a/ash/webui/diagnostics_ui/backend/input_data_provider_touch.cc
+++ b/ash/webui/diagnostics_ui/backend/input_data_provider_touch.cc
@@ -3,6 +3,8 @@
 // found in the LICENSE file.
 
 #include "ash/webui/diagnostics_ui/backend/input_data_provider_touch.h"
+#include "ash/webui/diagnostics_ui/backend/input_data_provider.h"
+#include "base/logging.h"
 #include "ui/events/ozone/evdev/event_device_info.h"
 
 namespace ash {
@@ -12,15 +14,17 @@
 InputDataProviderTouch::~InputDataProviderTouch() {}
 
 mojom::TouchDeviceInfoPtr InputDataProviderTouch::ConstructTouchDevice(
-    int id,
-    const ui::EventDeviceInfo* device_info,
-    mojom::ConnectionType connection_type) {
+    const InputDeviceInformation* device_info) {
   mojom::TouchDeviceInfoPtr result = mojom::TouchDeviceInfo::New();
-  result->id = id;
-  result->connection_type = connection_type;
-  result->type = device_info->HasTouchpad() ? mojom::TouchDeviceType::kPointer
-                                            : mojom::TouchDeviceType::kDirect;
-  result->name = device_info->name();
+
+  result->id = device_info->evdev_id;
+  result->connection_type = device_info->connection_type;
+
+  // TODO(crbug.com/1207678): double-check logic
+  result->type = device_info->event_device_info.HasTouchpad()
+                     ? mojom::TouchDeviceType::kPointer
+                     : mojom::TouchDeviceType::kDirect;
+  result->name = device_info->event_device_info.name();
   return result;
 }
 
diff --git a/ash/webui/diagnostics_ui/backend/input_data_provider_touch.h b/ash/webui/diagnostics_ui/backend/input_data_provider_touch.h
index 93aaa565..5fe7db2 100644
--- a/ash/webui/diagnostics_ui/backend/input_data_provider_touch.h
+++ b/ash/webui/diagnostics_ui/backend/input_data_provider_touch.h
@@ -11,6 +11,8 @@
 namespace ash {
 namespace diagnostics {
 
+class InputDeviceInformation;
+
 // Helper to provide InputDataProvider diagnostic interface with touch-specific
 // logic.
 class InputDataProviderTouch {
@@ -21,9 +23,7 @@
   ~InputDataProviderTouch();
 
   mojom::TouchDeviceInfoPtr ConstructTouchDevice(
-      int id,
-      const ui::EventDeviceInfo* device_info,
-      mojom::ConnectionType connection_type);
+      const InputDeviceInformation* device_info);
 };
 
 }  // namespace diagnostics
diff --git a/ash/webui/diagnostics_ui/backend/input_data_provider_unittest.cc b/ash/webui/diagnostics_ui/backend/input_data_provider_unittest.cc
index a09db7b9..e07bf86f 100644
--- a/ash/webui/diagnostics_ui/backend/input_data_provider_unittest.cc
+++ b/ash/webui/diagnostics_ui/backend/input_data_provider_unittest.cc
@@ -8,10 +8,13 @@
 
 #include "base/command_line.h"
 #include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
 #include "base/test/bind.h"
 #include "base/test/task_environment.h"
+#include "base/test/test_future.h"
 #include "chromeos/system/fake_statistics_provider.h"
 #include "chromeos/system/statistics_provider.h"
+#include "device/udev_linux/fake_udev_loader.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/events/ozone/device/device_event_observer.h"
 #include "ui/events/ozone/device/device_manager.h"
@@ -20,6 +23,77 @@
 namespace ash {
 namespace diagnostics {
 
+namespace {
+
+constexpr mojom::TopRowKey kClassicTopRowKeys[] = {
+    mojom::TopRowKey::kBack,
+    mojom::TopRowKey::kForward,
+    mojom::TopRowKey::kRefresh,
+    mojom::TopRowKey::kFullscreen,
+    mojom::TopRowKey::kOverview,
+    mojom::TopRowKey::kScreenBrightnessDown,
+    mojom::TopRowKey::kScreenBrightnessUp,
+    mojom::TopRowKey::kVolumeMute,
+    mojom::TopRowKey::kVolumeDown,
+    mojom::TopRowKey::kVolumeUp};
+
+constexpr mojom::TopRowKey kInternalJinlonTopRowKeys[] = {
+    mojom::TopRowKey::kBack,
+    mojom::TopRowKey::kRefresh,
+    mojom::TopRowKey::kFullscreen,
+    mojom::TopRowKey::kOverview,
+    mojom::TopRowKey::kScreenshot,
+    mojom::TopRowKey::kScreenBrightnessDown,
+    mojom::TopRowKey::kScreenBrightnessUp,
+    mojom::TopRowKey::kPrivacyScreenToggle,
+    mojom::TopRowKey::kKeyboardBacklightDown,
+    mojom::TopRowKey::kKeyboardBacklightUp,
+    mojom::TopRowKey::kVolumeMute,
+    mojom::TopRowKey::kVolumeDown,
+    mojom::TopRowKey::kVolumeUp};
+
+// One possible variant of a Dell configuration
+constexpr mojom::TopRowKey kInternalDellTopRowKeys[] = {
+    mojom::TopRowKey::kBack,
+    mojom::TopRowKey::kRefresh,
+    mojom::TopRowKey::kFullscreen,
+    mojom::TopRowKey::kOverview,
+    mojom::TopRowKey::kScreenBrightnessDown,
+    mojom::TopRowKey::kScreenBrightnessUp,
+    mojom::TopRowKey::kVolumeMute,
+    mojom::TopRowKey::kVolumeDown,
+    mojom::TopRowKey::kVolumeUp,
+    mojom::TopRowKey::kNone,
+    mojom::TopRowKey::kNone,
+    mojom::TopRowKey::kScreenMirror,
+    mojom::TopRowKey::kDelete};
+
+constexpr char kKbdTopRowPropertyName[] = "CROS_KEYBOARD_TOP_ROW_LAYOUT";
+constexpr char kKbdTopRowLayoutAttributeName[] = "function_row_physmap";
+
+constexpr char kSillyDeviceName[] = "eventWithoutANumber";
+
+constexpr char kInvalidMechnicalLayout[] = "Not ANSI, JIS, or ISO";
+
+// NOTE: This is only creates a simple ui::InputDevice based on a device
+// capabilities report; it is not suitable for subclasses of ui::InputDevice.
+ui::InputDevice InputDeviceFromCapabilities(
+    int device_id,
+    const ui::DeviceCapabilities& capabilities) {
+  ui::EventDeviceInfo device_info = {};
+  ui::CapabilitiesToDeviceInfo(capabilities, &device_info);
+
+  const std::string sys_path =
+      base::StringPrintf("/dev/input/event%d-%s", device_id, capabilities.path);
+
+  return ui::InputDevice(device_id, device_info.device_type(),
+                         device_info.name(), device_info.phys(),
+                         base::FilePath(sys_path), device_info.vendor_id(),
+                         device_info.product_id(), device_info.version());
+}
+
+}  // namespace
+
 class FakeDeviceManager : public ui::DeviceManager {
  public:
   FakeDeviceManager() {}
@@ -60,34 +134,71 @@
 
 class FakeInputDeviceInfoHelper : public InputDeviceInfoHelper {
  public:
-  std::unique_ptr<ui::EventDeviceInfo> GetDeviceInfo(
+  FakeInputDeviceInfoHelper() {}
+
+  ~FakeInputDeviceInfoHelper() override {}
+
+  std::unique_ptr<InputDeviceInformation> GetDeviceInfo(
+      int id,
       base::FilePath path) override {
-    std::unique_ptr<ui::EventDeviceInfo> dev_info =
-        std::make_unique<ui::EventDeviceInfo>();
     ui::DeviceCapabilities device_caps;
-    std::string base_name = path.BaseName().value();
+    const std::string base_name = path.BaseName().value();
+
     if (base_name == "event0") {
       device_caps = ui::kLinkKeyboard;
+      EXPECT_EQ(0, id);
     } else if (base_name == "event1") {
       device_caps = ui::kLinkTouchpad;
+      EXPECT_EQ(1, id);
     } else if (base_name == "event2") {
       device_caps = ui::kKohakuTouchscreen;
+      EXPECT_EQ(2, id);
     } else if (base_name == "event3") {
       device_caps = ui::kKohakuStylus;
+      EXPECT_EQ(3, id);
     } else if (base_name == "event4") {
       device_caps = ui::kHpUsbKeyboard;
+      EXPECT_EQ(4, id);
     } else if (base_name == "event5") {
-      device_caps = ui::kSarienKeyboard;
+      device_caps = ui::kSarienKeyboard;  // Wilco
+      EXPECT_EQ(5, id);
     } else if (base_name == "event6") {
       device_caps = ui::kEveKeyboard;
+      EXPECT_EQ(6, id);
     } else if (base_name == "event7") {
+      device_caps = ui::kJinlonKeyboard;
+      EXPECT_EQ(7, id);
+    } else if (base_name == "event8") {
+      device_caps = ui::kMicrosoftBluetoothNumberPad;
+      EXPECT_EQ(8, id);
+    } else if (base_name == "event9") {
+      device_caps = ui::kLogitechTouchKeyboardK400;
+      EXPECT_EQ(9, id);
+    } else if (base_name == kSillyDeviceName) {
+      // Simulate a device that is properly described, but has a malformed
+      // device name.
+      EXPECT_EQ(98, id);
+      device_caps = ui::kLinkKeyboard;
+    } else if (base_name == "event99") {
+      EXPECT_EQ(99, id);
       // Simulate a device that couldn't be opened or have its info determined
       // for whatever reason.
       return nullptr;
     }
 
-    EXPECT_TRUE(ui::CapabilitiesToDeviceInfo(device_caps, dev_info.get()));
-    return dev_info;
+    auto info = std::make_unique<InputDeviceInformation>();
+
+    EXPECT_TRUE(
+        ui::CapabilitiesToDeviceInfo(device_caps, &info->event_device_info));
+    info->evdev_id = id;
+    info->path = path;
+    info->input_device =
+        InputDeviceFromCapabilities(info->evdev_id, device_caps);
+    info->connection_type =
+        InputDataProvider::ConnectionTypeFromInputDeviceType(
+            info->event_device_info.device_type());
+
+    return info;
   }
 };
 
@@ -112,7 +223,46 @@
 
     auto manager = std::make_unique<FakeDeviceManager>();
     manager_ = manager.get();
+    fake_udev_ = std::make_unique<testing::FakeUdevLoader>();
     provider_ = std::make_unique<TestInputDataProvider>(std::move(manager));
+
+    // Apply these early; delaying until
+    // FakeInputDeviceInfoHelper::GetDeviceInfo() is not appropriate, as
+    // fake_udev is not thread safe. (If multiple devices are constructed in a
+    // row, then GetDeviceInfo() invocation can overlap with
+    // ProcessInputDataProvider::ProcessDeviceInfo() which reads from udev).
+    UdevAddFakeDeviceCapabilities("/dev/input/event5", ui::kSarienKeyboard);
+    UdevAddFakeDeviceCapabilities("/dev/input/event6", ui::kEveKeyboard);
+    UdevAddFakeDeviceCapabilities("/dev/input/event7", ui::kJinlonKeyboard);
+  }
+
+  void UdevAddFakeDeviceCapabilities(
+      const std::string& device_name,
+      const ui::DeviceCapabilities& device_caps) {
+    std::map<std::string, std::string>
+        sysfs_properties;  // Old style numeric tags
+    std::map<std::string, std::string>
+        sysfs_attributes;  // New style vivaldi scancode layouts
+
+    if (device_caps.kbd_function_row_physmap &&
+        strlen(device_caps.kbd_function_row_physmap) > 0) {
+      sysfs_attributes[kKbdTopRowLayoutAttributeName] =
+          device_caps.kbd_function_row_physmap;
+    }
+
+    if (device_caps.kbd_top_row_layout &&
+        strlen(device_caps.kbd_top_row_layout) > 0) {
+      sysfs_properties[kKbdTopRowPropertyName] = device_caps.kbd_top_row_layout;
+    }
+
+    // Each device needs a unique sys path
+    const std::string sys_path = device_name + "-" + device_caps.path;
+
+    fake_udev_->AddFakeDevice(device_caps.name, sys_path.c_str(),
+                              /*subsystem=*/"input", /*devnode=*/absl::nullopt,
+                              /*devtype=*/absl::nullopt,
+                              std::move(sysfs_attributes),
+                              std::move(sysfs_properties));
   }
 
   ~InputDataProviderTest() override {
@@ -123,12 +273,12 @@
  protected:
   base::test::TaskEnvironment task_environment_;
   FakeDeviceManager* manager_;
+  std::unique_ptr<testing::FakeUdevLoader> fake_udev_;
   chromeos::system::FakeStatisticsProvider statistics_provider_;
   std::unique_ptr<InputDataProvider> provider_;
 };
 
 TEST_F(InputDataProviderTest, GetConnectedDevices_DeviceInfoMapping) {
-  base::RunLoop run_loop;
   ui::DeviceEvent event0(ui::DeviceEvent::DeviceType::INPUT,
                          ui::DeviceEvent::ActionType::ADD,
                          base::FilePath("/dev/input/event0"));
@@ -147,44 +297,48 @@
   provider_->OnDeviceEvent(event3);
   task_environment_.RunUntilIdle();
 
-  provider_->GetConnectedDevices(base::BindLambdaForTesting(
-      [&](std::vector<mojom::KeyboardInfoPtr> keyboards,
-          std::vector<mojom::TouchDeviceInfoPtr> touch_devices) {
-        ASSERT_EQ(1ul, keyboards.size());
-        // The stylus device should be filtered out, hence only 2 touch devices.
-        ASSERT_EQ(2ul, touch_devices.size());
+  base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
+                         std::vector<mojom::TouchDeviceInfoPtr>>
+      future;
+  provider_->GetConnectedDevices(future.GetCallback());
 
-        mojom::KeyboardInfoPtr keyboard = keyboards[0].Clone();
-        EXPECT_EQ(0u, keyboard->id);
-        EXPECT_EQ(mojom::ConnectionType::kInternal, keyboard->connection_type);
-        EXPECT_EQ("AT Translated Set 2 keyboard", keyboard->name);
+  const auto& keyboards = future.Get<0>();
+  const auto& touch_devices = future.Get<1>();
 
-        mojom::TouchDeviceInfoPtr touchpad = touch_devices[0].Clone();
-        EXPECT_EQ(1u, touchpad->id);
-        EXPECT_EQ(mojom::ConnectionType::kInternal, touchpad->connection_type);
-        EXPECT_EQ(mojom::TouchDeviceType::kPointer, touchpad->type);
-        EXPECT_EQ("Atmel maXTouch Touchpad", touchpad->name);
+  ASSERT_EQ(1ul, keyboards.size());
+  // The stylus device should be filtered out, hence only 2 touch devices.
+  ASSERT_EQ(2ul, touch_devices.size());
 
-        mojom::TouchDeviceInfoPtr touchscreen = touch_devices[1].Clone();
-        EXPECT_EQ(2u, touchscreen->id);
-        EXPECT_EQ(mojom::ConnectionType::kInternal,
-                  touchscreen->connection_type);
-        EXPECT_EQ(mojom::TouchDeviceType::kDirect, touchscreen->type);
-        EXPECT_EQ("Atmel maXTouch Touchscreen", touchscreen->name);
+  const mojom::KeyboardInfoPtr& keyboard = keyboards[0];
+  EXPECT_EQ(0u, keyboard->id);
+  EXPECT_EQ(mojom::ConnectionType::kInternal, keyboard->connection_type);
+  EXPECT_EQ("AT Translated Set 2 keyboard", keyboard->name);
 
-        run_loop.Quit();
-      }));
-  run_loop.Run();
+  const mojom::TouchDeviceInfoPtr& touchpad = touch_devices[0];
+  EXPECT_EQ(1u, touchpad->id);
+  EXPECT_EQ(mojom::ConnectionType::kInternal, touchpad->connection_type);
+  EXPECT_EQ(mojom::TouchDeviceType::kPointer, touchpad->type);
+  EXPECT_EQ("Atmel maXTouch Touchpad", touchpad->name);
+
+  const mojom::TouchDeviceInfoPtr& touchscreen = touch_devices[1];
+  EXPECT_EQ(2u, touchscreen->id);
+  EXPECT_EQ(mojom::ConnectionType::kInternal, touchscreen->connection_type);
+  EXPECT_EQ(mojom::TouchDeviceType::kDirect, touchscreen->type);
+  EXPECT_EQ("Atmel maXTouch Touchscreen", touchscreen->name);
 }
 
 TEST_F(InputDataProviderTest, GetConnectedDevices_AddEventAfterFirstCall) {
-  base::RunLoop run_loop;
-  provider_->GetConnectedDevices(base::BindLambdaForTesting(
-      [&](std::vector<mojom::KeyboardInfoPtr> keyboards,
-          std::vector<mojom::TouchDeviceInfoPtr> touch_devices) {
-        ASSERT_EQ(0ul, keyboards.size());
-        ASSERT_EQ(0ul, touch_devices.size());
-      }));
+  {
+    base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
+                           std::vector<mojom::TouchDeviceInfoPtr>>
+        future;
+    provider_->GetConnectedDevices(future.GetCallback());
+
+    const auto& keyboards = future.Get<0>();
+    const auto& touch_devices = future.Get<1>();
+    ASSERT_EQ(0ul, keyboards.size());
+    ASSERT_EQ(0ul, touch_devices.size());
+  }
 
   ui::DeviceEvent event(ui::DeviceEvent::DeviceType::INPUT,
                         ui::DeviceEvent::ActionType::ADD,
@@ -192,25 +346,60 @@
   provider_->OnDeviceEvent(event);
   task_environment_.RunUntilIdle();
 
-  provider_->GetConnectedDevices(base::BindLambdaForTesting(
-      [&](std::vector<mojom::KeyboardInfoPtr> keyboards,
-          std::vector<mojom::TouchDeviceInfoPtr> touch_devices) {
-        ASSERT_EQ(1ul, keyboards.size());
-        mojom::KeyboardInfoPtr keyboard = keyboards[0].Clone();
-        EXPECT_EQ(4u, keyboard->id);
-        EXPECT_EQ(mojom::ConnectionType::kUsb, keyboard->connection_type);
-        EXPECT_EQ("Chicony HP Elite USB Keyboard", keyboard->name);
+  {
+    base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
+                           std::vector<mojom::TouchDeviceInfoPtr>>
+        future;
+    provider_->GetConnectedDevices(future.GetCallback());
 
-        EXPECT_EQ(0ul, touch_devices.size());
+    const auto& keyboards = future.Get<0>();
+    const auto& touch_devices = future.Get<1>();
 
-        run_loop.Quit();
-      }));
+    ASSERT_EQ(1ul, keyboards.size());
+    const mojom::KeyboardInfoPtr& keyboard = keyboards[0];
+    EXPECT_EQ(4u, keyboard->id);
+    EXPECT_EQ(mojom::ConnectionType::kUsb, keyboard->connection_type);
+    EXPECT_EQ("Chicony HP Elite USB Keyboard", keyboard->name);
 
-  run_loop.Run();
+    EXPECT_EQ(0ul, touch_devices.size());
+  }
+}
+
+TEST_F(InputDataProviderTest, GetConnectedDevices_AddUnusualDevices) {
+  ui::DeviceEvent event0(ui::DeviceEvent::DeviceType::INPUT,
+                         ui::DeviceEvent::ActionType::ADD,
+                         base::FilePath("/dev/input/event8"));
+  ui::DeviceEvent event1(ui::DeviceEvent::DeviceType::INPUT,
+                         ui::DeviceEvent::ActionType::ADD,
+                         base::FilePath("/dev/input/event9"));
+  provider_->OnDeviceEvent(event0);
+  provider_->OnDeviceEvent(event1);
+  task_environment_.RunUntilIdle();
+
+  base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
+                         std::vector<mojom::TouchDeviceInfoPtr>>
+      future;
+  provider_->GetConnectedDevices(future.GetCallback());
+
+  const auto& keyboards = future.Get<0>();
+  const auto& touch_devices = future.Get<1>();
+
+  ASSERT_EQ(2ul, keyboards.size());
+  // The stylus device should be filtered out, hence only 2 touch devices.
+  ASSERT_EQ(0ul, touch_devices.size());
+
+  const mojom::KeyboardInfoPtr& keyboard1 = keyboards[0];
+  EXPECT_EQ(8u, keyboard1->id);
+  EXPECT_EQ(mojom::ConnectionType::kBluetooth, keyboard1->connection_type);
+  EXPECT_EQ(ui::kMicrosoftBluetoothNumberPad.name, keyboard1->name);
+
+  const mojom::KeyboardInfoPtr& keyboard2 = keyboards[1];
+  EXPECT_EQ(9u, keyboard2->id);
+  EXPECT_EQ(mojom::ConnectionType::kUnknown, keyboard2->connection_type);
+  EXPECT_EQ(ui::kLogitechTouchKeyboardK400.name, keyboard2->name);
 }
 
 TEST_F(InputDataProviderTest, GetConnectedDevices_Remove) {
-  base::RunLoop run_loop;
   ui::DeviceEvent add_touch_event(ui::DeviceEvent::DeviceType::INPUT,
                                   ui::DeviceEvent::ActionType::ADD,
                                   base::FilePath("/dev/input/event1"));
@@ -221,15 +410,21 @@
   provider_->OnDeviceEvent(add_kbd_event);
   task_environment_.RunUntilIdle();
 
-  provider_->GetConnectedDevices(base::BindLambdaForTesting(
-      [&](std::vector<mojom::KeyboardInfoPtr> keyboards,
-          std::vector<mojom::TouchDeviceInfoPtr> touch_devices) {
-        ASSERT_EQ(1ul, keyboards.size());
-        EXPECT_EQ(4u, keyboards[0]->id);
+  {
+    base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
+                           std::vector<mojom::TouchDeviceInfoPtr>>
+        future;
+    provider_->GetConnectedDevices(future.GetCallback());
 
-        ASSERT_EQ(1ul, touch_devices.size());
-        EXPECT_EQ(1u, touch_devices[0]->id);
-      }));
+    const auto& keyboards = future.Get<0>();
+    const auto& touch_devices = future.Get<1>();
+
+    ASSERT_EQ(1ul, keyboards.size());
+    EXPECT_EQ(4u, keyboards[0]->id);
+
+    ASSERT_EQ(1ul, touch_devices.size());
+    EXPECT_EQ(1u, touch_devices[0]->id);
+  }
 
   ui::DeviceEvent remove_touch_event(ui::DeviceEvent::DeviceType::INPUT,
                                      ui::DeviceEvent::ActionType::REMOVE,
@@ -241,20 +436,21 @@
   provider_->OnDeviceEvent(remove_kbd_event);
   task_environment_.RunUntilIdle();
 
-  provider_->GetConnectedDevices(base::BindLambdaForTesting(
-      [&](std::vector<mojom::KeyboardInfoPtr> keyboards,
-          std::vector<mojom::TouchDeviceInfoPtr> touch_devices) {
-        EXPECT_EQ(0ul, keyboards.size());
-        EXPECT_EQ(0ul, touch_devices.size());
+  {
+    base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
+                           std::vector<mojom::TouchDeviceInfoPtr>>
+        future;
+    provider_->GetConnectedDevices(future.GetCallback());
 
-        run_loop.Quit();
-      }));
+    const auto& keyboards = future.Get<0>();
+    const auto& touch_devices = future.Get<1>();
 
-  run_loop.Run();
+    EXPECT_EQ(0ul, keyboards.size());
+    EXPECT_EQ(0ul, touch_devices.size());
+  }
 }
 
 TEST_F(InputDataProviderTest, KeyboardPhysicalLayoutDetection) {
-  base::RunLoop run_loop;
   statistics_provider_.SetMachineStatistic(
       chromeos::system::kKeyboardMechanicalLayoutKey, "ISO");
 
@@ -267,50 +463,77 @@
   ui::DeviceEvent event2(ui::DeviceEvent::DeviceType::INPUT,
                          ui::DeviceEvent::ActionType::ADD,
                          base::FilePath("/dev/input/event5"));
+  ui::DeviceEvent event3(ui::DeviceEvent::DeviceType::INPUT,
+                         ui::DeviceEvent::ActionType::ADD,
+                         base::FilePath("/dev/input/event7"));
   provider_->OnDeviceEvent(event0);
   provider_->OnDeviceEvent(event1);
   provider_->OnDeviceEvent(event2);
+  provider_->OnDeviceEvent(event3);
   task_environment_.RunUntilIdle();
 
-  provider_->GetConnectedDevices(base::BindLambdaForTesting(
-      [&](std::vector<mojom::KeyboardInfoPtr> keyboards,
-          std::vector<mojom::TouchDeviceInfoPtr> touch_devices) {
-        ASSERT_EQ(3ul, keyboards.size());
+  base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
+                         std::vector<mojom::TouchDeviceInfoPtr>>
+      future;
+  provider_->GetConnectedDevices(future.GetCallback());
 
-        mojom::KeyboardInfoPtr builtin_keyboard = keyboards[0].Clone();
-        EXPECT_EQ(0u, builtin_keyboard->id);
-        EXPECT_EQ(mojom::PhysicalLayout::kChromeOS,
-                  builtin_keyboard->physical_layout);
-        EXPECT_EQ(mojom::MechanicalLayout::kIso,
-                  builtin_keyboard->mechanical_layout);
-        EXPECT_EQ(mojom::NumberPadPresence::kNotPresent,
-                  builtin_keyboard->number_pad_present);
+  const auto& keyboards = future.Get<0>();
 
-        mojom::KeyboardInfoPtr external_keyboard = keyboards[1].Clone();
-        EXPECT_EQ(4u, external_keyboard->id);
-        EXPECT_EQ(mojom::PhysicalLayout::kUnknown,
-                  external_keyboard->physical_layout);
-        EXPECT_EQ(mojom::MechanicalLayout::kUnknown,
-                  external_keyboard->mechanical_layout);
-        EXPECT_EQ(mojom::NumberPadPresence::kUnknown,
-                  external_keyboard->number_pad_present);
+  ASSERT_EQ(4ul, keyboards.size());
 
-        mojom::KeyboardInfoPtr dell_internal_keyboard = keyboards[2].Clone();
-        EXPECT_EQ(5u, dell_internal_keyboard->id);
-        EXPECT_EQ(mojom::PhysicalLayout::kChromeOSDellEnterprise,
-                  dell_internal_keyboard->physical_layout);
-        EXPECT_EQ(mojom::MechanicalLayout::kIso,
-                  dell_internal_keyboard->mechanical_layout);
-        EXPECT_EQ(mojom::NumberPadPresence::kNotPresent,
-                  dell_internal_keyboard->number_pad_present);
+  const mojom::KeyboardInfoPtr& builtin_keyboard = keyboards[0];
+  EXPECT_EQ(0u, builtin_keyboard->id);
+  EXPECT_EQ(mojom::PhysicalLayout::kChromeOS,
+            builtin_keyboard->physical_layout);
+  EXPECT_EQ(mojom::MechanicalLayout::kIso, builtin_keyboard->mechanical_layout);
+  EXPECT_EQ(mojom::NumberPadPresence::kNotPresent,
+            builtin_keyboard->number_pad_present);
+  EXPECT_EQ(
+      std::vector(std::begin(kClassicTopRowKeys), std::end(kClassicTopRowKeys)),
+      builtin_keyboard->top_row_keys);
 
-        run_loop.Quit();
-      }));
-  run_loop.Run();
+  const mojom::KeyboardInfoPtr& external_keyboard = keyboards[1];
+  EXPECT_EQ(4u, external_keyboard->id);
+  EXPECT_EQ(mojom::PhysicalLayout::kUnknown,
+            external_keyboard->physical_layout);
+  EXPECT_EQ(mojom::MechanicalLayout::kUnknown,
+            external_keyboard->mechanical_layout);
+  EXPECT_EQ(mojom::NumberPadPresence::kUnknown,
+            external_keyboard->number_pad_present);
+  EXPECT_EQ(
+      std::vector(std::begin(kClassicTopRowKeys), std::end(kClassicTopRowKeys)),
+      external_keyboard->top_row_keys);
+
+  const mojom::KeyboardInfoPtr& dell_internal_keyboard = keyboards[2];
+  EXPECT_EQ(5u, dell_internal_keyboard->id);
+  EXPECT_EQ(mojom::PhysicalLayout::kChromeOSDellEnterprise,
+            dell_internal_keyboard->physical_layout);
+  EXPECT_EQ(mojom::MechanicalLayout::kIso,
+            dell_internal_keyboard->mechanical_layout);
+  EXPECT_EQ(mojom::NumberPadPresence::kNotPresent,
+            dell_internal_keyboard->number_pad_present);
+  EXPECT_EQ(std::vector(std::begin(kInternalDellTopRowKeys),
+                        std::end(kInternalDellTopRowKeys)),
+            dell_internal_keyboard->top_row_keys);
+
+  const mojom::KeyboardInfoPtr& jinlon_internal_keyboard = keyboards[3];
+  EXPECT_EQ(7u, jinlon_internal_keyboard->id);
+  EXPECT_EQ(mojom::PhysicalLayout::kChromeOS,
+            jinlon_internal_keyboard->physical_layout);
+  EXPECT_EQ(mojom::MechanicalLayout::kIso,
+            jinlon_internal_keyboard->mechanical_layout);
+  EXPECT_EQ(mojom::NumberPadPresence::kNotPresent,
+            jinlon_internal_keyboard->number_pad_present);
+  EXPECT_EQ(std::vector(std::begin(kInternalJinlonTopRowKeys),
+                        std::end(kInternalJinlonTopRowKeys)),
+            jinlon_internal_keyboard->top_row_keys);
+
+  // TODO(b/208729519): We should check a Drallion keyboard, however that
+  // invokes a check through the global Shell that does not operate in
+  // this test.
 }
 
 TEST_F(InputDataProviderTest, KeyboardAssistantKeyDetection) {
-  base::RunLoop run_loop;
   ui::DeviceEvent link_event(ui::DeviceEvent::DeviceType::INPUT,
                              ui::DeviceEvent::ActionType::ADD,
                              base::FilePath("/dev/input/event0"));
@@ -321,43 +544,46 @@
   provider_->OnDeviceEvent(eve_event);
   task_environment_.RunUntilIdle();
 
-  provider_->GetConnectedDevices(base::BindLambdaForTesting(
-      [&](std::vector<mojom::KeyboardInfoPtr> keyboards,
-          std::vector<mojom::TouchDeviceInfoPtr> touch_devices) {
-        ASSERT_EQ(2ul, keyboards.size());
+  base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
+                         std::vector<mojom::TouchDeviceInfoPtr>>
+      future;
+  provider_->GetConnectedDevices(future.GetCallback());
+  const auto& keyboards = future.Get<0>();
 
-        mojom::KeyboardInfoPtr link_keyboard = keyboards[0].Clone();
-        EXPECT_EQ(0u, link_keyboard->id);
-        EXPECT_FALSE(link_keyboard->has_assistant_key);
-        mojom::KeyboardInfoPtr eve_keyboard = keyboards[1].Clone();
-        EXPECT_EQ(6u, eve_keyboard->id);
-        EXPECT_TRUE(eve_keyboard->has_assistant_key);
-      }));
+  ASSERT_EQ(2ul, keyboards.size());
+
+  const mojom::KeyboardInfoPtr& link_keyboard = keyboards[0];
+  EXPECT_EQ(0u, link_keyboard->id);
+  EXPECT_FALSE(link_keyboard->has_assistant_key);
+  const mojom::KeyboardInfoPtr& eve_keyboard = keyboards[1];
+  EXPECT_EQ(6u, eve_keyboard->id);
+  EXPECT_TRUE(eve_keyboard->has_assistant_key);
 }
 
-TEST_F(InputDataProviderTest, KeyboardNumberPadDetection) {
+TEST_F(InputDataProviderTest, KeyboardNumberPadDetectionInternal) {
+  // Detection of internal number pad depends on command-line
+  // argument, and is not a property of the keyboard device.
+
   base::CommandLine::ForCurrentProcess()->InitFromArgv(
       {"", "--has-number-pad"});
-  base::RunLoop run_loop;
   ui::DeviceEvent link_event(ui::DeviceEvent::DeviceType::INPUT,
                              ui::DeviceEvent::ActionType::ADD,
                              base::FilePath("/dev/input/event0"));
   provider_->OnDeviceEvent(link_event);
   task_environment_.RunUntilIdle();
 
-  provider_->GetConnectedDevices(base::BindLambdaForTesting(
-      [&](std::vector<mojom::KeyboardInfoPtr> keyboards,
-          std::vector<mojom::TouchDeviceInfoPtr> touch_devices) {
-        ASSERT_EQ(1ul, keyboards.size());
+  base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
+                         std::vector<mojom::TouchDeviceInfoPtr>>
+      future;
+  provider_->GetConnectedDevices(future.GetCallback());
+  const auto& keyboards = future.Get<0>();
 
-        mojom::KeyboardInfoPtr builtin_keyboard = keyboards[0].Clone();
-        EXPECT_EQ(0u, builtin_keyboard->id);
-        EXPECT_EQ(mojom::NumberPadPresence::kPresent,
-                  builtin_keyboard->number_pad_present);
+  ASSERT_EQ(1ul, keyboards.size());
 
-        run_loop.Quit();
-      }));
-  run_loop.Run();
+  const mojom::KeyboardInfoPtr& builtin_keyboard = keyboards[0];
+  EXPECT_EQ(0u, builtin_keyboard->id);
+  EXPECT_EQ(mojom::NumberPadPresence::kPresent,
+            builtin_keyboard->number_pad_present);
 }
 
 TEST_F(InputDataProviderTest, ObserveConnectedDevices_Keyboards) {
@@ -404,14 +630,37 @@
   EXPECT_EQ(1u, fake_observer.touch_devices_disconnected[0]);
 }
 
-TEST_F(InputDataProviderTest, BadDeviceDoesntCrash) {
+TEST_F(InputDataProviderTest, ChangeDeviceDoesNotCrash) {
+  ui::DeviceEvent add_device_event(ui::DeviceEvent::DeviceType::INPUT,
+                                   ui::DeviceEvent::ActionType::ADD,
+                                   base::FilePath("/dev/input/event1"));
+  ui::DeviceEvent change_device_event(ui::DeviceEvent::DeviceType::INPUT,
+                                      ui::DeviceEvent::ActionType::CHANGE,
+                                      base::FilePath("/dev/input/event1"));
+  provider_->OnDeviceEvent(add_device_event);
+  task_environment_.RunUntilIdle();
+  provider_->OnDeviceEvent(change_device_event);
+  task_environment_.RunUntilIdle();
+}
+
+TEST_F(InputDataProviderTest, BadDeviceDoesNotCrash) {
+  // Try a device that specifically fails to be processed
   ui::DeviceEvent add_bad_device_event(ui::DeviceEvent::DeviceType::INPUT,
                                        ui::DeviceEvent::ActionType::ADD,
-                                       base::FilePath("/dev/input/event7"));
+                                       base::FilePath("/dev/input/event99"));
   provider_->OnDeviceEvent(add_bad_device_event);
   task_environment_.RunUntilIdle();
 }
 
+TEST_F(InputDataProviderTest, SillyDeviceDoesNotCrash) {
+  // Try a device that has data, but has a non-parseable name.
+  ui::DeviceEvent add_silly_device_event(ui::DeviceEvent::DeviceType::INPUT,
+                                         ui::DeviceEvent::ActionType::ADD,
+                                         base::FilePath(kSillyDeviceName));
+  provider_->OnDeviceEvent(add_silly_device_event);
+  task_environment_.RunUntilIdle();
+}
+
 TEST_F(InputDataProviderTest, GetKeyboardVisualLayout_AmericanEnglish) {
   statistics_provider_.SetMachineStatistic(chromeos::system::kKeyboardLayoutKey,
                                            "xkb:us::eng,m17n:ar,t13n:ar");
@@ -422,28 +671,25 @@
   provider_->OnDeviceEvent(add_keyboard_event);
   task_environment_.RunUntilIdle();
 
-  base::RunLoop run_loop;
-  provider_->GetKeyboardVisualLayout(
-      6, base::BindLambdaForTesting(
-             [&](base::flat_map<uint32_t, mojom::KeyGlyphSetPtr> layout) {
-               ASSERT_FALSE(layout[KEY_Q].is_null());
-               EXPECT_EQ("q", layout[KEY_Q]->main_glyph);
-               EXPECT_FALSE(layout[KEY_Q]->shift_glyph.has_value());
+  base::test::TestFuture<base::flat_map<uint32_t, mojom::KeyGlyphSetPtr>>
+      future;
+  provider_->GetKeyboardVisualLayout(6, future.GetCallback());
+  const auto& layout = future.Get<0>();
 
-               ASSERT_FALSE(layout[KEY_3].is_null());
-               EXPECT_EQ("3", layout[KEY_3]->main_glyph);
-               EXPECT_EQ("#", layout[KEY_3]->shift_glyph);
+  ASSERT_FALSE(layout.at(KEY_Q).is_null());
+  EXPECT_EQ("q", layout.at(KEY_Q)->main_glyph);
+  EXPECT_FALSE(layout.at(KEY_Q)->shift_glyph.has_value());
 
-               // Check all of the essential keys (at least on US QWERTY) have
-               // glyphs.
-               for (auto const& entry : layout) {
-                 EXPECT_FALSE(entry.second.is_null())
-                     << "No glyphs for evdev code " << entry.first;
-               }
+  ASSERT_FALSE(layout.at(KEY_3).is_null());
+  EXPECT_EQ("3", layout.at(KEY_3)->main_glyph);
+  EXPECT_EQ("#", layout.at(KEY_3)->shift_glyph);
 
-               run_loop.Quit();
-             }));
-  run_loop.Run();
+  // Check all of the essential keys (at least on US QWERTY) have
+  // glyphs.
+  for (auto const& entry : layout) {
+    EXPECT_FALSE(entry.second.is_null())
+        << "No glyphs for evdev code " << entry.first;
+  }
 }
 
 TEST_F(InputDataProviderTest, GetKeyboardVisualLayout_FrenchFrench) {
@@ -456,27 +702,85 @@
   provider_->OnDeviceEvent(add_keyboard_event);
   task_environment_.RunUntilIdle();
 
-  base::RunLoop run_loop;
-  provider_->GetKeyboardVisualLayout(
-      6, base::BindLambdaForTesting(
-             [&](base::flat_map<uint32_t, mojom::KeyGlyphSetPtr> layout) {
-               ASSERT_FALSE(layout[KEY_Q].is_null());
-               EXPECT_EQ("a", layout[KEY_Q]->main_glyph);
-               EXPECT_FALSE(layout[KEY_Q]->shift_glyph.has_value());
+  base::test::TestFuture<base::flat_map<uint32_t, mojom::KeyGlyphSetPtr>>
+      future;
+  provider_->GetKeyboardVisualLayout(6, future.GetCallback());
+  const auto& layout = future.Get<0>();
 
-               ASSERT_FALSE(layout[KEY_3].is_null());
-               EXPECT_EQ("\"", layout[KEY_3]->main_glyph);
-               EXPECT_EQ("3", layout[KEY_3]->shift_glyph);
+  ASSERT_FALSE(layout.at(KEY_Q).is_null());
+  EXPECT_EQ("a", layout.at(KEY_Q)->main_glyph);
+  EXPECT_FALSE(layout.at(KEY_Q)->shift_glyph.has_value());
 
-               // Check all of the essential keys have glyphs.
-               for (auto const& entry : layout) {
-                 EXPECT_FALSE(entry.second.is_null())
-                     << "No glyphs for evdev code " << entry.first;
-               }
+  ASSERT_FALSE(layout.at(KEY_3).is_null());
+  EXPECT_EQ("\"", layout.at(KEY_3)->main_glyph);
+  EXPECT_EQ("3", layout.at(KEY_3)->shift_glyph);
 
-               run_loop.Quit();
-             }));
-  run_loop.Run();
+  // Check all of the essential keys have glyphs.
+  for (auto const& entry : layout) {
+    EXPECT_FALSE(entry.second.is_null())
+        << "No glyphs for evdev code " << entry.first;
+  }
+}
+
+TEST_F(InputDataProviderTest, GetKeyboardMechanicalLayout_Unknown1) {
+  statistics_provider_.ClearMachineStatistic(
+      chromeos::system::kKeyboardMechanicalLayoutKey);
+
+  ui::DeviceEvent add_keyboard_event(ui::DeviceEvent::DeviceType::INPUT,
+                                     ui::DeviceEvent::ActionType::ADD,
+                                     base::FilePath("/dev/input/event6"));
+  provider_->OnDeviceEvent(add_keyboard_event);
+  task_environment_.RunUntilIdle();
+
+  {
+    base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
+                           std::vector<mojom::TouchDeviceInfoPtr>>
+        future;
+    provider_->GetConnectedDevices(future.GetCallback());
+
+    const auto& keyboards = future.Get<0>();
+
+    ASSERT_EQ(1ul, keyboards.size());
+
+    const mojom::KeyboardInfoPtr& builtin_keyboard = keyboards[0];
+    EXPECT_EQ(6u, builtin_keyboard->id);
+    EXPECT_EQ(mojom::PhysicalLayout::kChromeOS,
+              builtin_keyboard->physical_layout);
+    EXPECT_EQ(mojom::MechanicalLayout::kUnknown,
+              builtin_keyboard->mechanical_layout);
+    EXPECT_EQ(mojom::NumberPadPresence::kNotPresent,
+              builtin_keyboard->number_pad_present);
+  }
+}
+
+TEST_F(InputDataProviderTest, GetKeyboardMechanicalLayout_Unknown2) {
+  statistics_provider_.SetMachineStatistic(
+      chromeos::system::kKeyboardMechanicalLayoutKey, kInvalidMechnicalLayout);
+  ui::DeviceEvent add_keyboard_event(ui::DeviceEvent::DeviceType::INPUT,
+                                     ui::DeviceEvent::ActionType::ADD,
+                                     base::FilePath("/dev/input/event6"));
+  provider_->OnDeviceEvent(add_keyboard_event);
+  task_environment_.RunUntilIdle();
+
+  {
+    base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
+                           std::vector<mojom::TouchDeviceInfoPtr>>
+        future;
+    provider_->GetConnectedDevices(future.GetCallback());
+
+    const auto& keyboards = future.Get<0>();
+
+    ASSERT_EQ(1ul, keyboards.size());
+
+    const mojom::KeyboardInfoPtr& builtin_keyboard = keyboards[0];
+    EXPECT_EQ(6u, builtin_keyboard->id);
+    EXPECT_EQ(mojom::PhysicalLayout::kChromeOS,
+              builtin_keyboard->physical_layout);
+    EXPECT_EQ(mojom::MechanicalLayout::kUnknown,
+              builtin_keyboard->mechanical_layout);
+    EXPECT_EQ(mojom::NumberPadPresence::kNotPresent,
+              builtin_keyboard->number_pad_present);
+  }
 }
 
 TEST_F(InputDataProviderTest, ResetReceiverOnDisconnect) {
diff --git a/ash/webui/diagnostics_ui/mojom/input_data_provider.mojom b/ash/webui/diagnostics_ui/mojom/input_data_provider.mojom
index 22cc37a..e3a07bd 100644
--- a/ash/webui/diagnostics_ui/mojom/input_data_provider.mojom
+++ b/ash/webui/diagnostics_ui/mojom/input_data_provider.mojom
@@ -5,13 +5,15 @@
 module ash.diagnostics.mojom;
 
 enum ConnectionType {
-  kInternal,  // Includes internal USB devices.
+  // Includes devices connected over USB that are on fully internal busses, as
+  // well as the keyboards/touchpads for detachables.
+  kInternal,
   kUsb,
   kBluetooth,
+  // An unknown device is most likely to be internal.
   kUnknown,
 };
 
-// The physical style of a keyboard.
 enum PhysicalLayout {
   kUnknown,
   // A typical Chrome OS keyboard with action keys on the top row, reduced
@@ -31,11 +33,40 @@
 };
 
 enum NumberPadPresence {
+  // Unknown indicates there is no reliable evidence whether a numberpad is
+  // present. This is common for external keyboards.
   kUnknown,
   kPresent,
   kNotPresent,
 };
 
+// Note that this enumeration will need to be extended if new keys are added.
+enum TopRowKey {
+  // Either no key at all, or no special action key at this position.
+  kNone,
+  // Marker for keys which cannot be decoded, but have some action.
+  kUnknown,
+  kBack,
+  kForward,
+  kRefresh,
+  kFullscreen,
+  kOverview,
+  kScreenshot,
+  kScreenBrightnessDown,
+  kScreenBrightnessUp,
+  kPrivacyScreenToggle,
+  kVolumeMute,
+  kVolumeDown,
+  kVolumeUp,
+  kKeyboardBacklightDown,
+  kKeyboardBacklightUp,
+  kNextTrack,
+  kPreviousTrack,
+  kPlayPause,
+  kScreenMirror,
+  kDelete,
+};
+
 // Describes a connected keyboard.
 struct KeyboardInfo {
   // The number of the keyboard's /dev/input/event* node.
@@ -44,8 +75,11 @@
   string name;
   PhysicalLayout physical_layout;
   MechanicalLayout mechanical_layout;
-  bool has_assistant_key;
   NumberPadPresence number_pad_present;
+  // Excludes left-most Escape key, and right-most key (usually Power/Lock).
+  array<TopRowKey> top_row_keys;
+  // Only applicable to CrOS keyboards.
+  bool has_assistant_key;
 };
 
 // Describes the glyphs that appear on a single key.
diff --git a/ash/webui/diagnostics_ui/resources/diagnostics_types.js b/ash/webui/diagnostics_ui/resources/diagnostics_types.js
index e79f6d9..4b0e379 100644
--- a/ash/webui/diagnostics_ui/resources/diagnostics_types.js
+++ b/ash/webui/diagnostics_ui/resources/diagnostics_types.js
@@ -465,6 +465,12 @@
 export const NumberPadPresence = ash.diagnostics.mojom.NumberPadPresence;
 
 /**
+ * Type alias for TopRowKey.
+ * @typedef {ash.diagnostics.mojom.TopRowKey}
+ */
+export const TopRowKey = ash.diagnostics.mojom.TopRowKey;
+
+/**
  * Type alias for KeyboardInfo.
  * @typedef {ash.diagnostics.mojom.KeyboardInfo}
  */
diff --git a/ash/webui/diagnostics_ui/resources/fake_data.js b/ash/webui/diagnostics_ui/resources/fake_data.js
index e98de2d..1facc4a 100644
--- a/ash/webui/diagnostics_ui/resources/fake_data.js
+++ b/ash/webui/diagnostics_ui/resources/fake_data.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {AuthenticationType, BatteryChargeStatus, BatteryHealth, BatteryInfo, BatteryState, ConnectionType, CpuUsage, ExternalPowerSource, KeyboardInfo, LockType, MechanicalLayout, MemoryUsage, Network, NetworkGuidInfo, NetworkState, NetworkType, NumberPadPresence, PhysicalLayout, PowerRoutineResult, RoamingState, RoutineType, SecurityType, StandardRoutineResult, SystemInfo, TouchDeviceInfo, TouchDeviceType, WiFiStateProperties} from './diagnostics_types.js';
+import {AuthenticationType, BatteryChargeStatus, BatteryHealth, BatteryInfo, BatteryState, ConnectionType, CpuUsage, ExternalPowerSource, KeyboardInfo, LockType, MechanicalLayout, MemoryUsage, Network, NetworkGuidInfo, NetworkState, NetworkType, NumberPadPresence, PhysicalLayout, PowerRoutineResult, RoamingState, RoutineType, SecurityType, StandardRoutineResult, SystemInfo, TopRowKey, TouchDeviceInfo, TouchDeviceType, WiFiStateProperties} from './diagnostics_types.js';
 import {stringToMojoString16} from './mojo_utils.js';
 
 /** @type {!Array<!BatteryChargeStatus>} */
@@ -629,6 +629,12 @@
     physicalLayout: PhysicalLayout.kChromeOS,
     mechanicalLayout: MechanicalLayout.kAnsi,
     hasAssistantKey: true,
+    topRowKeys: [
+      TopRowKey.kBack, TopRowKey.kForward, TopRowKey.kRefresh,
+      TopRowKey.kFullscreen, TopRowKey.kOverview,
+      TopRowKey.kScreenBrightnessDown, TopRowKey.kScreenBrightnessUp,
+      TopRowKey.kVolumeMute, TopRowKey.kVolumeDown, TopRowKey.kVolumeUp
+    ],
     numberPadPresent: NumberPadPresence.kPresent,
   },
 ];
diff --git a/ash/webui/shimless_rma/resources/reimaging_provisioning_page.html b/ash/webui/shimless_rma/resources/reimaging_provisioning_page.html
index 4506111..7ffa8e1 100644
--- a/ash/webui/shimless_rma/resources/reimaging_provisioning_page.html
+++ b/ash/webui/shimless_rma/resources/reimaging_provisioning_page.html
@@ -3,15 +3,27 @@
     height: 200px;
     width: 300px;
   }
+
+  paper-spinner-lite {
+    height: 300px;
+    width: 300px;
+  }
 </style>
 
 <base-page orientation="column">
   <div slot="header">
     <h1>[[i18n('provisioningPageTitleText')]]</h1>
     <div id="provisioningDeviceStatus">[[statusString_]]</div>
+    <div>
+      <cr-button id="retryProvisioningButton" class="cancel-button"
+          on-click="onRetryProvsioningButtonClicked_"
+          hidden$="[[!shouldShowRetryButton_]]">
+        [[i18n('provisioningPageFailedRetryButtonLabel')]]
+      </cr-button>
+    </div>
   </div>
   <div slot="body">
-    <!-- TODO(joonbug): Replace with spinner -->
-    <iron-icon icon="shimless:shimless-placeholder"></iron-icon>
+    <paper-spinner-lite hidden$="[[!shouldShowSpinner_]]" active>
+    </paper-spinner-lite>
   </div>
 </base-page>
diff --git a/ash/webui/shimless_rma/resources/reimaging_provisioning_page.js b/ash/webui/shimless_rma/resources/reimaging_provisioning_page.js
index ef94348..abc9cf2 100644
--- a/ash/webui/shimless_rma/resources/reimaging_provisioning_page.js
+++ b/ash/webui/shimless_rma/resources/reimaging_provisioning_page.js
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import 'chrome://resources/cr_elements/icons.m.js';
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
 import './shimless_rma_shared_css.js';
@@ -67,6 +68,18 @@
         type: String,
         computed: 'getStatusString_(status_, progress_)',
       },
+
+      /** @protected {boolean} */
+      shouldShowSpinner_: {
+        type: Boolean,
+        value: false,
+      },
+
+      /** @protected {boolean} */
+      shouldShowRetryButton_: {
+        type: Boolean,
+        value: false,
+      },
     };
   }
 
@@ -119,6 +132,10 @@
         'disable-next-button',
         {bubbles: true, composed: true, detail: disabled},
         ));
+    this.shouldShowSpinner_ = this.status_ === ProvisioningStatus.kInProgress;
+    this.shouldShowRetryButton_ =
+        this.status_ === ProvisioningStatus.kFailedBlocking ||
+        this.status_ === ProvisioningStatus.kFailedNonBlocking;
   }
 
   /** @return {!Promise<!StateResult>} */
@@ -130,6 +147,26 @@
       return Promise.reject(new Error('Provisioning is not complete.'));
     }
   }
+
+  /** @private */
+  onRetryProvsioningButtonClicked_() {
+    if (this.status_ !== ProvisioningStatus.kFailedBlocking &&
+        this.status_ !== ProvisioningStatus.kFailedNonBlocking) {
+      console.error('Provisioning has not failed.');
+      return;
+    }
+
+    this.dispatchEvent(new CustomEvent(
+        'transition-state',
+        {
+          bubbles: true,
+          composed: true,
+          detail: (() => {
+            return this.shimlessRmaService_.retryProvisioning();
+          })
+        },
+        ));
+  }
 }
 
 customElements.define(ReimagingProvisioningPage.is, ReimagingProvisioningPage);
diff --git a/ash/webui/shimless_rma/shimless_rma.cc b/ash/webui/shimless_rma/shimless_rma.cc
index 2ac3c051..7a19f41 100644
--- a/ash/webui/shimless_rma/shimless_rma.cc
+++ b/ash/webui/shimless_rma/shimless_rma.cc
@@ -186,6 +186,8 @@
        IDS_SHIMLESS_RMA_PROVISIONING_FAILED_BLOCKING},
       {"provisioningPageFailedNonBlockingText",
        IDS_SHIMLESS_RMA_PROVISIONING_FAILED_NON_BLOCKING},
+      {"provisioningPageFailedRetryButtonLabel",
+       IDS_SHIMLESS_RMA_PROVISIONING_FAILED_RETRY_BUTTON_LABEL},
       // Repair complete page
       {"repairCompletedTitleText", IDS_SHIMLESS_RMA_REPAIR_COMPLETED},
       {"repairCompletedDescriptionText",
diff --git a/base/logging.cc b/base/logging.cc
index f4888b0..174785c 100644
--- a/base/logging.cc
+++ b/base/logging.cc
@@ -804,9 +804,12 @@
 #endif
 #elif defined(OS_FUCHSIA)
     // LogMessage() will silently drop the message if the logger is not valid.
-    GetScopedFxLogger().LogMessage(
-        file_, line_, base::StringPiece(str_newline).substr(message_start_),
-        LogSeverityToFuchsiaLogSeverity(severity_));
+    // Skip the final character of |str_newline|, since LogMessage() will add
+    // a newline.
+    const auto message = base::StringPiece(str_newline).substr(message_start_);
+    GetScopedFxLogger().LogMessage(file_, line_,
+                                   message.substr(0, message.size() - 1),
+                                   LogSeverityToFuchsiaLogSeverity(severity_));
 #endif  // OS_FUCHSIA
   }
 
diff --git a/build/skia_gold_common/skia_gold_session.py b/build/skia_gold_common/skia_gold_session.py
index 7e69a23..5683b772 100644
--- a/build/skia_gold_common/skia_gold_session.py
+++ b/build/skia_gold_common/skia_gold_session.py
@@ -5,6 +5,7 @@
 
 import logging
 import os
+import platform
 import shutil
 import sys
 import tempfile
@@ -17,7 +18,11 @@
 if sys.platform == 'win32':
   GOLDCTL_BINARY = os.path.join(GOLDCTL_BINARY, 'win', 'goldctl') + '.exe'
 elif sys.platform == 'darwin':
-  GOLDCTL_BINARY = os.path.join(GOLDCTL_BINARY, 'mac', 'goldctl')
+  machine = platform.machine().lower()
+  if any(machine.startswith(m) for m in ('arm64', 'aarch64')):
+    GOLDCTL_BINARY = os.path.join(GOLDCTL_BINARY, 'mac_arm64', 'goldctl')
+  else:
+    GOLDCTL_BINARY = os.path.join(GOLDCTL_BINARY, 'mac_amd64', 'goldctl')
 else:
   GOLDCTL_BINARY = os.path.join(GOLDCTL_BINARY, 'linux', 'goldctl')
 
diff --git a/cc/trees/proxy_main.cc b/cc/trees/proxy_main.cc
index a3ba577b..f3eeb68 100644
--- a/cc/trees/proxy_main.cc
+++ b/cc/trees/proxy_main.cc
@@ -405,6 +405,8 @@
   layer_tree_host_->RecordEndOfFrameMetrics(
       begin_main_frame_start_time,
       begin_main_frame_state->active_sequence_trackers);
+  if (blocking)
+    commit_trace_.reset();
 }
 
 void ProxyMain::DidCompleteCommit(CommitTimestamps commit_timestamps) {
diff --git a/chrome/android/features/vr/OWNERS b/chrome/android/features/vr/OWNERS
index 7461f332..b84a4e5 100644
--- a/chrome/android/features/vr/OWNERS
+++ b/chrome/android/features/vr/OWNERS
@@ -1,3 +1,2 @@
-alcooper@chromium.org
-bialpio@chromium.org
+file://components/webxr/OWNERS
 mthiesse@chromium.org
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/DividerLineMenuItemViewBinder.java b/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/DividerLineMenuItemViewBinder.java
index 68dbb5c..3eedce5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/DividerLineMenuItemViewBinder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/DividerLineMenuItemViewBinder.java
@@ -48,6 +48,7 @@
             int id = model.get(AppMenuItemProperties.MENU_ITEM_ID);
             assert id == R.id.divider_line_id;
             view.setId(id);
+            view.setEnabled(false);
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/quickactionsearchwidget/QuickActionSearchWidgetProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/quickactionsearchwidget/QuickActionSearchWidgetProvider.java
index 216f9621..7c13333 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/quickactionsearchwidget/QuickActionSearchWidgetProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/quickactionsearchwidget/QuickActionSearchWidgetProvider.java
@@ -89,7 +89,12 @@
         RemoteViews getRemoteViews(@NonNull Context context,
                 @NonNull SearchActivityPreferences prefs, @NonNull AppWidgetManager manager,
                 int widgetId) {
-            return getDelegate().createDinoWidgetRemoteViews(context, prefs);
+            Bundle options = manager.getAppWidgetOptions(widgetId);
+            return getDelegate().createDinoWidgetRemoteViews(context, prefs,
+                    getPortraitModeTargetAreaWidth(options),
+                    getPortraitModeTargetAreaHeight(options),
+                    getLandscapeModeTargetAreaWidth(options),
+                    getLandscapeModeTargetAreaHeight(options));
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
index b65a8a5..a52e845 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
@@ -730,8 +730,12 @@
 
         if (shareDirectly) {
             RecordUserAction.record("MobileMenuDirectShare");
+            new UkmRecorder.Bridge().recordEventWithBooleanMetric(
+                    tab.getWebContents(), "MobileMenu.DirectShare", "HasOccurred");
         } else {
             RecordUserAction.record("MobileMenuShare");
+            new UkmRecorder.Bridge().recordEventWithBooleanMetric(
+                    tab.getWebContents(), "MobileMenu.Share", "HasOccurred");
         }
         shareDelegate.share(tab, shareDirectly, ShareOrigin.OVERFLOW_MENU);
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr/VR_JAVA_OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/vr/VR_JAVA_OWNERS
index c07db33..b84a4e5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/vr/VR_JAVA_OWNERS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr/VR_JAVA_OWNERS
@@ -1,2 +1,2 @@
-bialpio@chromium.org
+file://components/webxr/OWNERS
 mthiesse@chromium.org
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/OWNERS b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/OWNERS
index 9d0ccab2..d89d1987 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/OWNERS
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/OWNERS
@@ -1,2 +1 @@
 file://chrome/android/java/src/org/chromium/chrome/browser/vr/VR_JAVA_OWNERS
-bsheedy@chromium.org
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 748a5b1..09f541f 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -6499,6 +6499,12 @@
       <message name="IDS_ACCNAME_TAB_SEARCH" desc="The accessible name for the Tab Search bubble.">
         Search tabs
       </message>
+      <message name="IDS_ACCNAME_TAB_SCROLL_LEADING" desc="The accessible name for the TabStrip leading scroll button.">
+        Scroll towards the first tab
+      </message>
+      <message name="IDS_ACCNAME_TAB_SCROLL_TRAILING" desc="The accessible name for the TabStrip trailing scroll button.">
+        Scroll towards the last tab
+      </message>
 
       <!-- Clipboard permission -->
       <message name="IDS_ALLOWED_CLIPBOARD_TITLE" desc="Title of the info bubble shown when a site has been allowed access to read the contents of the system clipboard.">
diff --git a/chrome/app/generated_resources_grd/IDS_ACCNAME_TAB_SCROLL_LEADING.png.sha1 b/chrome/app/generated_resources_grd/IDS_ACCNAME_TAB_SCROLL_LEADING.png.sha1
new file mode 100644
index 0000000..0fd4b26
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_ACCNAME_TAB_SCROLL_LEADING.png.sha1
@@ -0,0 +1 @@
+ba7dce18cc93123490aaa6ed96d5ac16d56e0679
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_ACCNAME_TAB_SCROLL_TRAILING.png.sha1 b/chrome/app/generated_resources_grd/IDS_ACCNAME_TAB_SCROLL_TRAILING.png.sha1
new file mode 100644
index 0000000..0fd4b26
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_ACCNAME_TAB_SCROLL_TRAILING.png.sha1
@@ -0,0 +1 @@
+ba7dce18cc93123490aaa6ed96d5ac16d56e0679
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 08c30085..d12c537 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1989,7 +1989,6 @@
     "//chrome/browser/touch_to_fill",
     "//chrome/browser/ui",
     "//chrome/browser/ui/color:mixers",
-    "//chrome/browser/ui/webui/app_management:mojo_bindings",
     "//chrome/browser/ui/webui/app_service_internals:mojo_bindings",
     "//chrome/browser/ui/webui/bluetooth_internals",
     "//chrome/browser/ui/webui/bluetooth_internals:mojo_bindings",
@@ -2415,6 +2414,7 @@
     "//ui/surface",
     "//ui/web_dialogs",
     "//ui/webui",
+    "//ui/webui/resources/cr_components/app_management:mojo_bindings",
     "//ui/webui/resources/cr_components/color_change_listener:mojom",
     "//ui/webui/resources/cr_components/customize_themes:mojom",
     "//ui/webui/resources/cr_components/most_visited:mojom",
@@ -7390,7 +7390,6 @@
       "//chrome/browser/resources/chromeos/smb_shares:web_components",
       "//chrome/browser/resources/chromeos/vm:web_components",
       "//chrome/browser/supervised_user:supervised_user_unscaled_resources",
-      "//chrome/browser/ui/webui/app_management:mojo_bindings_js",
       "//chrome/browser/ui/webui/chromeos/add_supervision:mojo_bindings_js",
       "//chrome/browser/ui/webui/chromeos/crostini_installer:mojo_bindings_js",
       "//chrome/browser/ui/webui/chromeos/crostini_upgrader:mojo_bindings_js",
@@ -7398,6 +7397,7 @@
       "//chrome/browser/ui/webui/chromeos/vm:mojo_bindings_webui_js",
       "//chrome/browser/ui/webui/settings/chromeos:mojom_js",
       "//chrome/browser/ui/webui/settings/chromeos/os_apps_page/mojom:mojom_js",
+      "//ui/webui/resources/cr_components/app_management:mojo_bindings_js",
     ]
   }
 
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index db900fd..8fb19e9 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -1209,19 +1209,43 @@
 
 constexpr FeatureEntry::FeatureParam kOmniboxZeroSuggestCacheDuration15Secs[] =
     {{"ZeroSuggestCacheDurationSec", "15"}};
+constexpr FeatureEntry::FeatureParam
+    kOmniboxZeroSuggestCacheDuration15SecsCounterfactual[] = {
+        {"ZeroSuggestCacheDurationSec", "15"},
+        {"ZeroSuggestCacheCounterfactual", "true"}};
 constexpr FeatureEntry::FeatureParam kOmniboxZeroSuggestCacheDuration30Secs[] =
     {{"ZeroSuggestCacheDurationSec", "30"}};
+constexpr FeatureEntry::FeatureParam
+    kOmniboxZeroSuggestCacheDuration30SecsCounterfactual[] = {
+        {"ZeroSuggestCacheDurationSec", "30"},
+        {"ZeroSuggestCacheCounterfactual", "true"}};
 constexpr FeatureEntry::FeatureParam kOmniboxZeroSuggestCacheDuration60Secs[] =
     {{"ZeroSuggestCacheDurationSec", "60"}};
+constexpr FeatureEntry::FeatureParam
+    kOmniboxZeroSuggestCacheDuration60SecsCounterfactual[] = {
+        {"ZeroSuggestCacheDurationSec", "60"},
+        {"ZeroSuggestCacheCounterfactual", "true"}};
 
 constexpr FeatureEntry::FeatureVariation
     kOmniboxZeroSuggestPrefetchingVariations[] = {
         {"15 seconds", kOmniboxZeroSuggestCacheDuration15Secs,
          base::size(kOmniboxZeroSuggestCacheDuration15Secs), nullptr},
+        {"15 seconds (counterfactual)",
+         kOmniboxZeroSuggestCacheDuration15SecsCounterfactual,
+         base::size(kOmniboxZeroSuggestCacheDuration15SecsCounterfactual),
+         nullptr},
         {"30 seconds", kOmniboxZeroSuggestCacheDuration30Secs,
          base::size(kOmniboxZeroSuggestCacheDuration30Secs), nullptr},
+        {"30 seconds (counterfactual)",
+         kOmniboxZeroSuggestCacheDuration30SecsCounterfactual,
+         base::size(kOmniboxZeroSuggestCacheDuration30SecsCounterfactual),
+         nullptr},
         {"60 seconds", kOmniboxZeroSuggestCacheDuration60Secs,
-         base::size(kOmniboxZeroSuggestCacheDuration60Secs), nullptr}};
+         base::size(kOmniboxZeroSuggestCacheDuration60Secs), nullptr},
+        {"60 seconds (counterfactual)",
+         kOmniboxZeroSuggestCacheDuration60SecsCounterfactual,
+         base::size(kOmniboxZeroSuggestCacheDuration60SecsCounterfactual),
+         nullptr}};
 
 const FeatureEntry::FeatureParam kOmniboxUIMaxAutocompleteMatches3[] = {
     {OmniboxFieldTrial::kUIMaxAutocompleteMatchesParam, "3"}};
diff --git a/chrome/browser/android/vr/OWNERS b/chrome/browser/android/vr/OWNERS
index 6a8c7d0..b84a4e5 100644
--- a/chrome/browser/android/vr/OWNERS
+++ b/chrome/browser/android/vr/OWNERS
@@ -1,6 +1,2 @@
-alcooper@chromium.org
-bialpio@chromium.org
+file://components/webxr/OWNERS
 mthiesse@chromium.org
-
-# WebXR-related, including GvrSchedulerDelegate.
-klausw@chromium.org
diff --git a/chrome/browser/android/vr/arcore_device/OWNERS b/chrome/browser/android/vr/arcore_device/OWNERS
deleted file mode 100644
index 8f984dc5..0000000
--- a/chrome/browser/android/vr/arcore_device/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-bialpio@chromium.org
-klausw@chromium.org
diff --git a/chrome/browser/ash/crosapi/url_handler_ash.cc b/chrome/browser/ash/crosapi/url_handler_ash.cc
index 7d48990..bb91c59 100644
--- a/chrome/browser/ash/crosapi/url_handler_ash.cc
+++ b/chrome/browser/ash/crosapi/url_handler_ash.cc
@@ -64,6 +64,10 @@
 }
 
 void UrlHandlerAsh::OpenUrl(const GURL& url) {
+  OpenUrlInternal(url);
+}
+
+bool UrlHandlerAsh::OpenUrlInternal(const GURL& url) {
   GURL target_url = crosapi::gurl_os_handler_utils::SanitizeAshURL(url);
   // Settings will be handled.
   if (target_url == GURL(chrome::kChromeUIOSSettingsURL)) {
@@ -72,7 +76,7 @@
     settings_window_manager->ShowChromePageForProfile(
         ProfileManager::GetPrimaryUserProfile(), target_url,
         display::kInvalidDisplayId);
-    return;
+    return true;
   }
 
   web_app::SystemAppType app_id;
@@ -104,10 +108,11 @@
     }
   } else {
     LOG(ERROR) << "Invalid URL passed to UrlHandlerAsh::OpenUrl:" << url;
-    return;
+    return false;
   }
   ShowOsAppForProfile(ProfileManager::GetPrimaryUserProfile(), target_url,
                       app_id);
+  return true;
 }
 
 }  // namespace crosapi
diff --git a/chrome/browser/ash/crosapi/url_handler_ash.h b/chrome/browser/ash/crosapi/url_handler_ash.h
index 5e46526c..401f955 100644
--- a/chrome/browser/ash/crosapi/url_handler_ash.h
+++ b/chrome/browser/ash/crosapi/url_handler_ash.h
@@ -24,6 +24,10 @@
   // crosapi::mojom::UrlHandler:
   void OpenUrl(const GURL& url) override;
 
+  // Returns |false| when the URL was invalid and will not get processed and
+  // |true| when the URL will get processed (synchronous or asynchronously).
+  bool OpenUrlInternal(const GURL& url);
+
  private:
   mojo::ReceiverSet<mojom::UrlHandler> receivers_;
 };
diff --git a/chrome/browser/ash/policy/dlp/dlp_content_manager_ash.cc b/chrome/browser/ash/policy/dlp/dlp_content_manager_ash.cc
index de3f6651..1cb29c1 100644
--- a/chrome/browser/ash/policy/dlp/dlp_content_manager_ash.cc
+++ b/chrome/browser/ash/policy/dlp/dlp_content_manager_ash.cc
@@ -148,37 +148,7 @@
     OnDlpRestrictionCheckedCallback callback) {
   ConfidentialContentsInfo info =
       GetScreenShareConfidentialContentsInfo(media_id);
-  MaybeReportEvent(info.restriction_info,
-                   DlpRulesManager::Restriction::kScreenShare);
-  DlpBooleanHistogram(dlp::kScreenShareBlockedUMA,
-                      IsBlocked(info.restriction_info));
-  if (IsBlocked(info.restriction_info)) {
-    ShowDlpScreenShareDisabledNotification(application_title);
-    std::move(callback).Run(false);
-    return;
-  }
-  if (is_screen_share_warning_mode_enabled_ && IsWarn(info.restriction_info)) {
-    // Check which of the contents were already allowed and don't warn for
-    // those.
-    RemoveAllowedContents(info.confidential_contents,
-                          DlpRulesManager::Restriction::kScreenShare);
-    if (info.confidential_contents.IsEmpty()) {
-      // The user already allowed all the visible content.
-      std::move(callback).Run(true);
-      return;
-    }
-    // base::Unretained(this) is safe here because DlpContentManagerAsh is
-    // initialized as a singleton that's always available in the system.
-    warn_notifier_->ShowDlpScreenShareWarningDialog(
-        base::BindOnce(&DlpContentManagerAsh::OnDlpWarnDialogReply,
-                       base::Unretained(this), info.confidential_contents,
-                       DlpRulesManager::Restriction::kScreenShare,
-                       std::move(callback)),
-        info.confidential_contents, application_title);
-    return;
-  }
-  // No restrictions apply.
-  std::move(callback).Run(true);
+  ProcessScreenShareRestriction(application_title, info, std::move(callback));
 }
 
 void DlpContentManagerAsh::OnVideoCaptureStarted(const ScreenshotArea& area) {
@@ -640,21 +610,8 @@
     return GetConfidentialContentsOnScreen(DlpContentRestriction::kScreenShare);
   }
   if (media_id.type == content::DesktopMediaID::Type::TYPE_WEB_CONTENTS) {
-    content::WebContents* web_contents =
-        content::WebContents::FromRenderFrameHost(
-            content::RenderFrameHost::FromID(
-                media_id.web_contents_id.render_process_id,
-                media_id.web_contents_id.main_render_frame_id));
-    ConfidentialContentsInfo info;
-    if (web_contents && !web_contents->IsBeingDestroyed()) {
-      info.restriction_info =
-          GetConfidentialRestrictions(web_contents)
-              .GetRestrictionLevelAndUrl(DlpContentRestriction::kScreenShare);
-      info.confidential_contents.Add(web_contents);
-    } else {
-      NOTREACHED();
-    }
-    return info;
+    return GetScreenShareConfidentialContentsInfoForWebContents(
+        media_id.web_contents_id);
   }
   DCHECK_EQ(media_id.type, content::DesktopMediaID::Type::TYPE_WINDOW);
   ConfidentialContentsInfo info;
diff --git a/chrome/browser/ash/policy/dlp/dlp_content_manager_ash.h b/chrome/browser/ash/policy/dlp/dlp_content_manager_ash.h
index bf9cd92..0f4b8824 100644
--- a/chrome/browser/ash/policy/dlp/dlp_content_manager_ash.h
+++ b/chrome/browser/ash/policy/dlp/dlp_content_manager_ash.h
@@ -90,11 +90,10 @@
   // application |application_name| is restricted or not advised. Depending on
   // the result, calls |callback| and passes an indicator whether to proceed or
   // not.
-  // Virtual to allow tests to override.
-  virtual void CheckScreenShareRestriction(
+  void CheckScreenShareRestriction(
       const content::DesktopMediaID& media_id,
       const std::u16string& application_title,
-      OnDlpRestrictionCheckedCallback callback);
+      OnDlpRestrictionCheckedCallback callback) override;
 
   // Called when video capturing for |area| is started.
   void OnVideoCaptureStarted(const ScreenshotArea& area);
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/cros_healthd_metric_sampler.cc b/chrome/browser/ash/policy/reporting/metrics_reporting/cros_healthd_metric_sampler.cc
index ed18d27..59033e6 100644
--- a/chrome/browser/ash/policy/reporting/metrics_reporting/cros_healthd_metric_sampler.cc
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/cros_healthd_metric_sampler.cc
@@ -64,9 +64,9 @@
 
 void HandleBusResult(MetricCallback callback,
                      CrosHealthdMetricSampler::MetricType metric_type,
+                     MetricData metric_data,
                      cros_healthd::TelemetryInfoPtr result) {
   bool anything_reported = false;
-  MetricData metric_data;
   const auto& bus_result = result->bus_result;
 
   if (!bus_result.is_null()) {
@@ -99,15 +99,15 @@
   }
 
   if (anything_reported) {
-    std::move(callback).Run(metric_data);
+    std::move(callback).Run(std::move(metric_data));
   }
 }
 
 void HandleCpuResult(MetricCallback callback,
                      CrosHealthdMetricSampler::MetricType metric_type,
+                     MetricData metric_data,
                      cros_healthd::TelemetryInfoPtr result) {
   bool anything_reported = false;
-  MetricData metric_data;
   const auto& cpu_result = result->cpu_result;
 
   if (!cpu_result.is_null()) {
@@ -148,15 +148,15 @@
   }
 
   if (anything_reported) {
-    std::move(callback).Run(metric_data);
+    std::move(callback).Run(std::move(metric_data));
   }
 }
 
 void HandleAudioResult(MetricCallback callback,
                        CrosHealthdMetricSampler::MetricType metric_type,
+                       MetricData metric_data,
                        chromeos::cros_healthd::mojom::TelemetryInfoPtr result) {
   bool anything_reported = false;
-  MetricData metric_data;
   auto* const audio_info_out =
       metric_data.mutable_telemetry_data()->mutable_audio_telemetry();
   const auto& audio_result = result->audio_result;
@@ -192,15 +192,15 @@
   }
 
   if (anything_reported) {
-    std::move(callback).Run(metric_data);
+    std::move(callback).Run(std::move(metric_data));
   }
 }
 
 void HandleMemoryResult(MetricCallback callback,
                         CrosHealthdMetricSampler::MetricType metric_type,
+                        MetricData metric_data,
                         cros_healthd::TelemetryInfoPtr result) {
   bool anything_reported = false;
-  MetricData metric_data;
   const auto& memory_result = result->memory_result;
 
   if (!memory_result.is_null()) {
@@ -251,13 +251,14 @@
   }
 
   if (anything_reported) {
-    std::move(callback).Run(metric_data);
+    std::move(callback).Run(std::move(metric_data));
   }
 }
 
 void OnHealthdInfoReceived(MetricCallback callback,
                            cros_healthd::ProbeCategoryEnum probe_category,
                            CrosHealthdMetricSampler::MetricType metric_type,
+                           MetricData metric_data,
                            cros_healthd::TelemetryInfoPtr result) {
   if (!result) {
     DVLOG(1) << "cros_healthd: null telemetry result";
@@ -266,19 +267,23 @@
 
   switch (probe_category) {
     case cros_healthd::ProbeCategoryEnum::kAudio: {
-      HandleAudioResult(std::move(callback), metric_type, std::move(result));
+      HandleAudioResult(std::move(callback), metric_type,
+                        std::move(metric_data), std::move(result));
       break;
     }
     case cros_healthd::ProbeCategoryEnum::kBus: {
-      HandleBusResult(std::move(callback), metric_type, std::move(result));
+      HandleBusResult(std::move(callback), metric_type, std::move(metric_data),
+                      std::move(result));
       break;
     }
     case cros_healthd::ProbeCategoryEnum::kCpu: {
-      HandleCpuResult(std::move(callback), metric_type, std::move(result));
+      HandleCpuResult(std::move(callback), metric_type, std::move(metric_data),
+                      std::move(result));
       break;
     }
     case cros_healthd::ProbeCategoryEnum::kMemory: {
-      HandleMemoryResult(std::move(callback), metric_type, std::move(result));
+      HandleMemoryResult(std::move(callback), metric_type,
+                         std::move(metric_data), std::move(result));
       break;
     }
     default: {
@@ -299,9 +304,14 @@
 void CrosHealthdMetricSampler::Collect(MetricCallback callback) {
   auto healthd_callback =
       base::BindOnce(OnHealthdInfoReceived, std::move(callback),
-                     probe_category_, metric_type_);
+                     probe_category_, metric_type_, std::move(metric_data_));
+  metric_data_.Clear();
   chromeos::cros_healthd::ServiceConnection::GetInstance()->ProbeTelemetryInfo(
       std::vector<cros_healthd::ProbeCategoryEnum>{probe_category_},
       std::move(healthd_callback));
 }
+
+void CrosHealthdMetricSampler::SetMetricData(MetricData metric_data) {
+  metric_data_ = std::move(metric_data);
+}
 }  // namespace reporting
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/cros_healthd_metric_sampler.h b/chrome/browser/ash/policy/reporting/metrics_reporting/cros_healthd_metric_sampler.h
index 1a4291d..ecf533c 100644
--- a/chrome/browser/ash/policy/reporting/metrics_reporting/cros_healthd_metric_sampler.h
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/cros_healthd_metric_sampler.h
@@ -30,7 +30,17 @@
   // Collect is called to invoke the healthd probing process.
   void Collect(MetricCallback callback) override;
 
+  // Set the metric data that the sampler will collect on. This can be used if
+  // part of the info or telemetry collected for the probe category is set
+  // without the the healthd metric sampler. After one call to Collect(), this
+  // metric data is cleared.
+  void SetMetricData(MetricData metric_data);
+
  private:
+  // The metric data to populate when calling Collect(). This can be set using
+  // SetMetricData and is cleared after every call to Collect()
+  MetricData metric_data_;
+
   // probe_category is the category to probe from the health daemon.
   const chromeos::cros_healthd::mojom::ProbeCategoryEnum probe_category_;
 
diff --git a/chrome/browser/ash/policy/reporting/metrics_reporting/cros_healthd_metric_sampler_unittest.cc b/chrome/browser/ash/policy/reporting/metrics_reporting/cros_healthd_metric_sampler_unittest.cc
index b418e2b..4cd6cd5 100644
--- a/chrome/browser/ash/policy/reporting/metrics_reporting/cros_healthd_metric_sampler_unittest.cc
+++ b/chrome/browser/ash/policy/reporting/metrics_reporting/cros_healthd_metric_sampler_unittest.cc
@@ -119,11 +119,12 @@
 
 MetricData CollectData(cros_healthd::TelemetryInfoPtr telemetry_info,
                        cros_healthd::ProbeCategoryEnum probe_category,
-                       CrosHealthdMetricSampler::MetricType metric_type) {
-  MetricData data;
+                       CrosHealthdMetricSampler::MetricType metric_type,
+                       MetricData metric_data) {
   chromeos::cros_healthd::FakeCrosHealthdClient::Get()
       ->SetProbeTelemetryInfoResponseForTesting(telemetry_info);
   CrosHealthdMetricSampler sampler(probe_category, metric_type);
+  sampler.SetMetricData(metric_data);
   test::TestEvent<MetricData> metric_collect_event;
 
   sampler.Collect(metric_collect_event.cb());
@@ -177,7 +178,7 @@
           test_case.healthd_encryption_state, test_case.max_keys,
           test_case.key_length, test_case.healthd_encryption_algorithm)),
       cros_healthd::ProbeCategoryEnum::kMemory,
-      CrosHealthdMetricSampler::MetricType::kInfo);
+      CrosHealthdMetricSampler::MetricType::kInfo, MetricData{});
 
   ASSERT_TRUE(result.has_info_data());
   ASSERT_TRUE(result.info_data().has_memory_info());
@@ -196,7 +197,7 @@
   MetricData result =
       CollectData(CreateBusResult(test_case.healthd_security_level),
                   cros_healthd::ProbeCategoryEnum::kBus,
-                  CrosHealthdMetricSampler::MetricType::kInfo);
+                  CrosHealthdMetricSampler::MetricType::kInfo, MetricData{});
   ASSERT_TRUE(result.has_info_data());
   ASSERT_TRUE(result.info_data().has_bus_device_info());
   ASSERT_TRUE(result.info_data().bus_device_info().has_thunderbolt_info());
@@ -205,10 +206,34 @@
       test_case.reporting_security_level);
 }
 
+TEST_F(CrosHealthdMetricSamplerTest, SetMetricData) {
+  MetricData metric_data;
+  auto* const memory_encryption_info_out = metric_data.mutable_info_data()
+                                               ->mutable_memory_info()
+                                               ->mutable_tme_info();
+  memory_encryption_info_out->set_key_length(1);
+  // Pass a null memory result so that only encryption state is set.
+  MetricData result = CollectData(
+      CreateMemoryResult(nullptr), cros_healthd::ProbeCategoryEnum::kMemory,
+      CrosHealthdMetricSampler::MetricType::kInfo, metric_data);
+
+  ASSERT_TRUE(result.has_info_data());
+  ASSERT_TRUE(result.info_data().has_memory_info());
+  ASSERT_TRUE(result.info_data().memory_info().has_tme_info());
+
+  // Since only encryption state should be set by the sampler, we can verify
+  // that the keylength field set above propagated to the metric data.
+  const auto& tme_info = result.info_data().memory_info().tme_info();
+  EXPECT_EQ(tme_info.key_length(), 1);
+  EXPECT_EQ(tme_info.encryption_state(),
+            ::reporting::MEMORY_ENCRYPTION_STATE_DISABLED);
+}
+
 TEST_F(CrosHealthdMetricSamplerTest, TestKeylockerConfigured) {
-  MetricData result = CollectData(CreateCpuResult(CreateKeylockerInfo(true)),
-                                  cros_healthd::ProbeCategoryEnum::kCpu,
-                                  CrosHealthdMetricSampler::MetricType::kInfo);
+  MetricData result =
+      CollectData(CreateCpuResult(CreateKeylockerInfo(true)),
+                  cros_healthd::ProbeCategoryEnum::kCpu,
+                  CrosHealthdMetricSampler::MetricType::kInfo, MetricData{});
 
   ASSERT_TRUE(result.has_info_data());
   ASSERT_TRUE(result.info_data().has_cpu_info());
@@ -218,9 +243,10 @@
 }
 
 TEST_F(CrosHealthdMetricSamplerTest, TestKeylockerUnconfigured) {
-  MetricData result = CollectData(CreateCpuResult(CreateKeylockerInfo(false)),
-                                  cros_healthd::ProbeCategoryEnum::kCpu,
-                                  CrosHealthdMetricSampler::MetricType::kInfo);
+  MetricData result =
+      CollectData(CreateCpuResult(CreateKeylockerInfo(false)),
+                  cros_healthd::ProbeCategoryEnum::kCpu,
+                  CrosHealthdMetricSampler::MetricType::kInfo, MetricData{});
 
   ASSERT_TRUE(result.has_info_data());
   ASSERT_TRUE(result.info_data().has_cpu_info());
@@ -230,9 +256,9 @@
 }
 
 TEST_F(CrosHealthdMetricSamplerTest, TestKeylockerUnsupported) {
-  MetricData result = CollectData(CreateCpuResult(nullptr),
-                                  cros_healthd::ProbeCategoryEnum::kCpu,
-                                  CrosHealthdMetricSampler::MetricType::kInfo);
+  MetricData result = CollectData(
+      CreateCpuResult(nullptr), cros_healthd::ProbeCategoryEnum::kCpu,
+      CrosHealthdMetricSampler::MetricType::kInfo, MetricData{});
 
   ASSERT_TRUE(result.has_info_data());
   ASSERT_TRUE(result.info_data().has_cpu_info());
@@ -280,7 +306,7 @@
           /*input_gain=*/50, /*input_device_name=*/"airpods", /*underruns=*/2,
           /*severe_underruns=*/2)),
       cros_healthd::ProbeCategoryEnum::kAudio,
-      CrosHealthdMetricSampler::MetricType::kTelemetry);
+      CrosHealthdMetricSampler::MetricType::kTelemetry, MetricData{});
 
   ASSERT_TRUE(result.has_telemetry_data());
   ASSERT_TRUE(result.telemetry_data().has_audio_telemetry());
@@ -298,7 +324,7 @@
           /*input_gain=*/0, /*input_device_name=*/"", /*underruns=*/0,
           /*severe_underruns=*/0)),
       cros_healthd::ProbeCategoryEnum::kAudio,
-      CrosHealthdMetricSampler::MetricType::kTelemetry);
+      CrosHealthdMetricSampler::MetricType::kTelemetry, MetricData{});
 
   ASSERT_TRUE(result.has_telemetry_data());
   ASSERT_TRUE(result.telemetry_data().has_audio_telemetry());
diff --git a/chrome/browser/ash/web_applications/os_url_handler_system_web_app_info.cc b/chrome/browser/ash/web_applications/os_url_handler_system_web_app_info.cc
index 46f60bb..54ef592 100644
--- a/chrome/browser/ash/web_applications/os_url_handler_system_web_app_info.cc
+++ b/chrome/browser/ash/web_applications/os_url_handler_system_web_app_info.cc
@@ -20,6 +20,8 @@
 
 namespace {
 
+bool g_enable_delegate_for_testing = false;
+
 SkColor GetBgColor(bool use_dark_mode) {
   return cros_styles::ResolveColor(
       cros_styles::ColorName::kBgColor, use_dark_mode,
@@ -70,7 +72,8 @@
 }
 
 bool OsUrlHandlerSystemWebAppDelegate::IsAppEnabled() const {
-  return crosapi::browser_util::IsLacrosEnabled();
+  return g_enable_delegate_for_testing ||
+         crosapi::browser_util::IsLacrosEnabled();
 }
 
 bool OsUrlHandlerSystemWebAppDelegate::ShouldShowInLauncher() const {
@@ -108,3 +111,7 @@
       crosapi::gurl_os_handler_utils::GetSystemUrlFromChromeUrl(target_url);
   return ChromeWebUIControllerFactory::GetInstance()->CanHandleUrl(target_url);
 }
+
+void OsUrlHandlerSystemWebAppDelegate::EnableDelegateForTesting(bool enable) {
+  g_enable_delegate_for_testing = enable;
+}
diff --git a/chrome/browser/ash/web_applications/os_url_handler_system_web_app_info.h b/chrome/browser/ash/web_applications/os_url_handler_system_web_app_info.h
index fd76aa9..a313bbc0 100644
--- a/chrome/browser/ash/web_applications/os_url_handler_system_web_app_info.h
+++ b/chrome/browser/ash/web_applications/os_url_handler_system_web_app_info.h
@@ -33,6 +33,9 @@
   bool ShouldShowInSearch() const override;
   bool ShouldReuseExistingWindow() const override;
   bool IsUrlInSystemAppScope(const GURL& url) const override;
+
+  // Can be called by a test to enforce the app to be enabled.
+  static void EnableDelegateForTesting(bool enable);
 };
 
 #endif  // CHROME_BROWSER_ASH_WEB_APPLICATIONS_OS_URL_HANDLER_SYSTEM_WEB_APP_INFO_H_
diff --git a/chrome/browser/chrome_browser_interface_binders.cc b/chrome/browser/chrome_browser_interface_binders.cc
index b7105dc..6a21319 100644
--- a/chrome/browser/chrome_browser_interface_binders.cc
+++ b/chrome/browser/chrome_browser_interface_binders.cc
@@ -209,7 +209,6 @@
 #include "chrome/browser/apps/digital_goods/digital_goods_factory_impl.h"
 #include "chrome/browser/nearby_sharing/common/nearby_share_features.h"
 #include "chrome/browser/speech/cros_speech_recognition_service_factory.h"
-#include "chrome/browser/ui/webui/app_management/app_management.mojom.h"
 #include "chrome/browser/ui/webui/chromeos/add_supervision/add_supervision.mojom.h"
 #include "chrome/browser/ui/webui/chromeos/add_supervision/add_supervision_ui.h"
 #include "chrome/browser/ui/webui/chromeos/audio/audio.mojom.h"
@@ -252,6 +251,7 @@
 #include "chromeos/services/network_health/public/mojom/network_health.mojom.h"  // nogncheck
 #include "media/capture/video/chromeos/mojom/camera_app.mojom.h"
 #include "third_party/blink/public/mojom/digital_goods/digital_goods.mojom.h"
+#include "ui/webui/resources/cr_components/app_management/app_management.mojom.h"
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if defined(OS_WIN) || BUILDFLAG(IS_CHROMEOS_ASH) || defined(OS_MAC) || \
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 5ebad974f..6125095 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -6163,10 +6163,8 @@
 void ChromeContentBrowserClient::OnKeepaliveRequestStarted(
     content::BrowserContext* context) {
 #if !defined(OS_ANDROID)
-  // TODO(crbug.com/1161996): Remove this entry once the investigation is
-  // done.
-  VLOG(1) << "OnKeepaliveRequestStarted: " << num_keepalive_requests_ << " ==> "
-          << num_keepalive_requests_ + 1;
+  DVLOG(1) << "OnKeepaliveRequestStarted: " << num_keepalive_requests_
+           << " ==> " << num_keepalive_requests_ + 1;
   ++num_keepalive_requests_;
   DCHECK_GT(num_keepalive_requests_, 0u);
 
@@ -6180,10 +6178,8 @@
   const auto timeout = GetKeepaliveTimerTimeout(context);
   keepalive_deadline_ = std::max(keepalive_deadline_, now + timeout);
   if (keepalive_deadline_ > now && !keepalive_timer_.IsRunning()) {
-    // TODO(crbug.com/1161996): Remove this entry once the investigation is
-    // done.
-    VLOG(1) << "Starting a keepalive timer(" << timeout.InSecondsF()
-            << " seconds)";
+    DVLOG(1) << "Starting a keepalive timer(" << timeout.InSecondsF()
+             << " seconds)";
     keepalive_timer_.Start(
         FROM_HERE, keepalive_deadline_ - now,
         base::BindOnce(
@@ -6198,15 +6194,11 @@
 void ChromeContentBrowserClient::OnKeepaliveRequestFinished() {
 #if !defined(OS_ANDROID)
   DCHECK_GT(num_keepalive_requests_, 0u);
-  // TODO(crbug.com/1161996): Remove this entry once the investigation is
-  // done.
-  VLOG(1) << "OnKeepaliveRequestFinished: " << num_keepalive_requests_
-          << " ==> " << num_keepalive_requests_ - 1;
+  DVLOG(1) << "OnKeepaliveRequestFinished: " << num_keepalive_requests_
+           << " ==> " << num_keepalive_requests_ - 1;
   --num_keepalive_requests_;
   if (num_keepalive_requests_ == 0) {
-    // TODO(crbug.com/1161996): Remove this entry once the investigation is
-    // done.
-    VLOG(1) << "Stopping the keepalive timer";
+    DVLOG(1) << "Stopping the keepalive timer";
     keepalive_timer_.Stop();
     // This deletes the keep alive handle attached to the timer function and
     // unblock the shutdown sequence.
@@ -6313,14 +6305,9 @@
 
 void ChromeContentBrowserClient::OnKeepaliveTimerFired(
     std::unique_ptr<ScopedKeepAlive> keep_alive_handle) {
-  // TODO(crbug.com/1161996): Remove this entry once the investigation is done.
-  VLOG(1) << "OnKeepaliveTimerFired";
   const auto now = base::TimeTicks::Now();
   const auto then = keepalive_deadline_;
   if (now < then) {
-    // TODO(crbug.com/1161996): Remove this entry once the investigation is
-    // done.
-    VLOG(1) << "Extending keepalive timer";
     keepalive_timer_.Start(
         FROM_HERE, then - now,
         base::BindOnce(&ChromeContentBrowserClient::OnKeepaliveTimerFired,
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index fa6248bc..4bcceea 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -4635,6 +4635,8 @@
     "policy/dlp/dlp_rules_manager_impl_unittest.cc",
     "policy/dlp/dlp_rules_manager_test_utils.cc",
     "policy/dlp/dlp_rules_manager_test_utils.h",
+    "policy/dlp/mock_dlp_content_manager.cc",
+    "policy/dlp/mock_dlp_content_manager.h",
     "policy/dlp/mock_dlp_content_observer.cc",
     "policy/dlp/mock_dlp_content_observer.h",
     "policy/dlp/mock_dlp_rules_manager.cc",
diff --git a/chrome/browser/chromeos/policy/dlp/dlp_content_manager.cc b/chrome/browser/chromeos/policy/dlp/dlp_content_manager.cc
index a901f45..f395aa3 100644
--- a/chrome/browser/chromeos/policy/dlp/dlp_content_manager.cc
+++ b/chrome/browser/chromeos/policy/dlp/dlp_content_manager.cc
@@ -195,6 +195,63 @@
       .GetRestrictionLevelAndUrl(DlpContentRestriction::kPrint);
 }
 
+DlpContentManager::ConfidentialContentsInfo
+DlpContentManager::GetScreenShareConfidentialContentsInfoForWebContents(
+    const content::WebContentsMediaCaptureId& web_contents_id) const {
+  content::WebContents* web_contents =
+      content::WebContents::FromRenderFrameHost(
+          content::RenderFrameHost::FromID(
+              web_contents_id.render_process_id,
+              web_contents_id.main_render_frame_id));
+  ConfidentialContentsInfo info;
+  if (web_contents && !web_contents->IsBeingDestroyed()) {
+    info.restriction_info =
+        GetConfidentialRestrictions(web_contents)
+            .GetRestrictionLevelAndUrl(DlpContentRestriction::kScreenShare);
+    info.confidential_contents.Add(web_contents);
+  } else {
+    NOTREACHED();
+  }
+  return info;
+}
+
+void DlpContentManager::ProcessScreenShareRestriction(
+    const std::u16string& application_title,
+    ConfidentialContentsInfo info,
+    OnDlpRestrictionCheckedCallback callback) {
+  MaybeReportEvent(info.restriction_info,
+                   DlpRulesManager::Restriction::kScreenShare);
+  DlpBooleanHistogram(dlp::kScreenShareBlockedUMA,
+                      IsBlocked(info.restriction_info));
+  if (IsBlocked(info.restriction_info)) {
+    ShowDlpScreenShareDisabledNotification(application_title);
+    std::move(callback).Run(false);
+    return;
+  }
+  if (IsWarn(info.restriction_info)) {
+    // Check which of the contents were already allowed and don't warn for
+    // those.
+    RemoveAllowedContents(info.confidential_contents,
+                          DlpRulesManager::Restriction::kScreenShare);
+    if (info.confidential_contents.IsEmpty()) {
+      // The user already allowed all the visible content.
+      std::move(callback).Run(true);
+      return;
+    }
+    // base::Unretained(this) is safe here because DlpContentManager is
+    // initialized as a singleton that's always available in the system.
+    warn_notifier_->ShowDlpScreenShareWarningDialog(
+        base::BindOnce(&DlpContentManager::OnDlpWarnDialogReply,
+                       base::Unretained(this), info.confidential_contents,
+                       DlpRulesManager::Restriction::kScreenShare,
+                       std::move(callback)),
+        info.confidential_contents, application_title);
+    return;
+  }
+  // No restrictions apply.
+  std::move(callback).Run(true);
+}
+
 void DlpContentManager::OnDlpWarnDialogReply(
     const DlpConfidentialContents& confidential_contents,
     DlpRulesManager::Restriction restriction,
diff --git a/chrome/browser/chromeos/policy/dlp/dlp_content_manager.h b/chrome/browser/chromeos/policy/dlp/dlp_content_manager.h
index 1c2545c..5d6f237 100644
--- a/chrome/browser/chromeos/policy/dlp/dlp_content_manager.h
+++ b/chrome/browser/chromeos/policy/dlp/dlp_content_manager.h
@@ -19,6 +19,8 @@
 #include "url/gurl.h"
 
 namespace content {
+struct DesktopMediaID;
+struct WebContentsMediaCaptureId;
 class WebContents;
 }  // namespace content
 
@@ -52,6 +54,15 @@
   void CheckPrintingRestriction(content::WebContents* web_contents,
                                 OnDlpRestrictionCheckedCallback callback);
 
+  // Checks whether screen sharing of content from the |media_id| source with
+  // application |application_name| is restricted or not advised. Depending on
+  // the result, calls |callback| and passes an indicator whether to proceed or
+  // not.
+  virtual void CheckScreenShareRestriction(
+      const content::DesktopMediaID& media_id,
+      const std::u16string& application_title,
+      OnDlpRestrictionCheckedCallback callback) = 0;
+
  protected:
   // Structure that relates a list of confidential contents to the
   // corresponding restriction level.
@@ -80,6 +91,17 @@
   RestrictionLevelAndUrl GetPrintingRestrictionInfo(
       content::WebContents* web_contents) const;
 
+  // Returns confidential info for screen share of a single WebContents with
+  // |web_contents_id|.
+  ConfidentialContentsInfo GetScreenShareConfidentialContentsInfoForWebContents(
+      const content::WebContentsMediaCaptureId& web_contents_id) const;
+
+  // Applies retrieved restrictions in |info| to screens share attempt from
+  // app |application_title| and calls the |callback| with a result.
+  void ProcessScreenShareRestriction(const std::u16string& application_title,
+                                     ConfidentialContentsInfo info,
+                                     OnDlpRestrictionCheckedCallback callback);
+
   // Called back from warning dialogs. Passes along the user's response,
   // reflected in the value of |should_proceed| along to |callback| which
   // handles continuing or cancelling the action based on this response. In case
diff --git a/chrome/browser/chromeos/policy/dlp/dlp_content_manager_lacros.cc b/chrome/browser/chromeos/policy/dlp/dlp_content_manager_lacros.cc
index cc47c83d..f8d0e1c7 100644
--- a/chrome/browser/chromeos/policy/dlp/dlp_content_manager_lacros.cc
+++ b/chrome/browser/chromeos/policy/dlp/dlp_content_manager_lacros.cc
@@ -69,6 +69,23 @@
   return g_dlp_content_manager;
 }
 
+void DlpContentManagerLacros::CheckScreenShareRestriction(
+    const content::DesktopMediaID& media_id,
+    const std::u16string& application_title,
+    OnDlpRestrictionCheckedCallback callback) {
+  if (media_id.type == content::DesktopMediaID::Type::TYPE_WEB_CONTENTS) {
+    ProcessScreenShareRestriction(
+        application_title,
+        GetScreenShareConfidentialContentsInfoForWebContents(
+            media_id.web_contents_id),
+        std::move(callback));
+  } else {
+    // No restrictions apply.
+    // TODO(crbug.com/1278814): Pass the check to Ash to process there.
+    std::move(callback).Run(true);
+  }
+}
+
 DlpContentManagerLacros::DlpContentManagerLacros() = default;
 DlpContentManagerLacros::~DlpContentManagerLacros() = default;
 
diff --git a/chrome/browser/chromeos/policy/dlp/dlp_content_manager_lacros.h b/chrome/browser/chromeos/policy/dlp/dlp_content_manager_lacros.h
index da6d4483..6236b1e 100644
--- a/chrome/browser/chromeos/policy/dlp/dlp_content_manager_lacros.h
+++ b/chrome/browser/chromeos/policy/dlp/dlp_content_manager_lacros.h
@@ -9,6 +9,7 @@
 #include "base/containers/flat_set.h"
 #include "chrome/browser/chromeos/policy/dlp/dlp_content_manager.h"
 #include "chrome/browser/chromeos/policy/dlp/dlp_content_restriction_set.h"
+#include "content/public/browser/desktop_media_id.h"
 #include "ui/aura/window_observer.h"
 
 namespace aura {
@@ -30,6 +31,15 @@
   // There will always be a single instance created on the first access.
   static DlpContentManagerLacros* Get();
 
+  // Checks whether screen sharing of content from the |media_id| source with
+  // application |application_name| is restricted or not advised. Depending on
+  // the result, calls |callback| and passes an indicator whether to proceed or
+  // not.
+  void CheckScreenShareRestriction(
+      const content::DesktopMediaID& media_id,
+      const std::u16string& application_title,
+      OnDlpRestrictionCheckedCallback callback) override;
+
  private:
   DlpContentManagerLacros();
   ~DlpContentManagerLacros() override;
diff --git a/chrome/browser/chromeos/policy/dlp/mock_dlp_content_manager.cc b/chrome/browser/chromeos/policy/dlp/mock_dlp_content_manager.cc
new file mode 100644
index 0000000..e7ab8ae
--- /dev/null
+++ b/chrome/browser/chromeos/policy/dlp/mock_dlp_content_manager.cc
@@ -0,0 +1,13 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/policy/dlp/mock_dlp_content_manager.h"
+
+namespace policy {
+
+MockDlpContentManager::MockDlpContentManager() = default;
+
+MockDlpContentManager::~MockDlpContentManager() = default;
+
+}  // namespace policy
diff --git a/chrome/browser/chromeos/policy/dlp/mock_dlp_content_manager.h b/chrome/browser/chromeos/policy/dlp/mock_dlp_content_manager.h
new file mode 100644
index 0000000..e773f8a
--- /dev/null
+++ b/chrome/browser/chromeos/policy/dlp/mock_dlp_content_manager.h
@@ -0,0 +1,37 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_POLICY_DLP_MOCK_DLP_CONTENT_MANAGER_H_
+#define CHROME_BROWSER_CHROMEOS_POLICY_DLP_MOCK_DLP_CONTENT_MANAGER_H_
+
+#include "chrome/browser/chromeos/policy/dlp/dlp_content_manager.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace policy {
+
+class MockDlpContentManager : public DlpContentManager {
+ public:
+  MockDlpContentManager();
+  ~MockDlpContentManager() override;
+
+  MOCK_METHOD(void,
+              OnConfidentialityChanged,
+              (content::WebContents*, const DlpContentRestrictionSet&),
+              (override));
+  MOCK_METHOD(void,
+              OnWebContentsDestroyed,
+              (content::WebContents*),
+              (override));
+  MOCK_METHOD(void, OnVisibilityChanged, (content::WebContents*), (override));
+  MOCK_METHOD(void,
+              CheckScreenShareRestriction,
+              (const content::DesktopMediaID& media_id,
+               const std::u16string& application_title,
+               OnDlpRestrictionCheckedCallback callback),
+              (override));
+};
+
+}  // namespace policy
+
+#endif  // CHROME_BROWSER_CHROMEOS_POLICY_DLP_MOCK_DLP_CONTENT_MANAGER_H_
diff --git a/chrome/browser/devtools/protocol/devtools_protocol_browsertest.cc b/chrome/browser/devtools/protocol/devtools_protocol_browsertest.cc
index 771400e..ad3eb6b 100644
--- a/chrome/browser/devtools/protocol/devtools_protocol_browsertest.cc
+++ b/chrome/browser/devtools/protocol/devtools_protocol_browsertest.cc
@@ -44,6 +44,7 @@
 #include "third_party/blink/public/common/features.h"
 #include "third_party/boringssl/src/include/openssl/nid.h"
 #include "third_party/boringssl/src/include/openssl/ssl.h"
+#include "url/origin.h"
 
 using DevToolsProtocolTest = DevToolsProtocolTestBase;
 using testing::AllOf;
@@ -207,6 +208,68 @@
   EXPECT_EQ(nullptr, DevToolsWindow::FindDevToolsWindow(agent_host_.get()));
 }
 
+IN_PROC_BROWSER_TEST_F(
+    DevToolsProtocolTest,
+    NoPendingUrlShownWhenAttachedToBrowserInitiatedFailedNavigation) {
+  GURL url("invalid.scheme:for-sure");
+  ui_test_utils::AllBrowserTabAddedWaiter tab_added_waiter;
+
+  content::WebContents* web_contents =
+      browser()->OpenURL(content::OpenURLParams(
+          url, content::Referrer(), WindowOpenDisposition::NEW_FOREGROUND_TAB,
+          ui::PAGE_TRANSITION_TYPED, false));
+  tab_added_waiter.Wait();
+  content::NavigationController& navigation_controller =
+      web_contents->GetController();
+  content::NavigationEntry* pending_entry =
+      navigation_controller.GetPendingEntry();
+  ASSERT_NE(nullptr, pending_entry);
+  EXPECT_EQ(url, pending_entry->GetURL());
+
+  EXPECT_EQ(pending_entry, navigation_controller.GetVisibleEntry());
+  agent_host_ = content::DevToolsAgentHost::GetOrCreateFor(web_contents);
+  agent_host_->AttachClient(this);
+  SendCommandSync("Page.enable");
+
+  // Ensure that a failed pending entry is cleared when the DevTools protocol
+  // attaches, so that any modified page content is not attributed to the failed
+  // URL. (crbug/1192417)
+  EXPECT_EQ(nullptr, navigation_controller.GetPendingEntry());
+  EXPECT_EQ(GURL(""), navigation_controller.GetVisibleEntry()->GetURL());
+}
+
+IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest,
+                       NoPendingUrlShownForPageNavigateFromChromeExtension) {
+  GURL url("https://example.com");
+  // DevTools protocol use cases that have an initiator origin (e.g., for
+  // extensions) should use renderer-initiated navigations and be subject to URL
+  // spoof defenses.
+  navigation_initiator_origin_ =
+      url::Origin::Create(GURL("chrome-extension://abc123/"));
+
+  // Attach DevTools and start a navigation but don't wait for it to finish.
+  Attach();
+  SendCommandSync("Page.enable");
+  base::DictionaryValue params;
+  params.SetStringKey("url", url.spec());
+  SendCommand("Page.navigate", std::move(params), false);
+  content::NavigationController& navigation_controller =
+      web_contents()->GetController();
+  content::NavigationEntry* pending_entry =
+      navigation_controller.GetPendingEntry();
+  ASSERT_NE(nullptr, pending_entry);
+  EXPECT_EQ(url, pending_entry->GetURL());
+
+  // Attaching the DevTools protocol to the initial empty document of a new tab
+  // should prevent the pending URL from being visible, since the protocol
+  // allows modifying the initial empty document in a way that could be useful
+  // for URL spoofs.
+  EXPECT_NE(pending_entry, navigation_controller.GetVisibleEntry());
+  EXPECT_NE(nullptr, navigation_controller.GetPendingEntry());
+  EXPECT_EQ(GURL("about:blank"),
+            navigation_controller.GetVisibleEntry()->GetURL());
+}
+
 class DevToolsProtocolTest_AppId : public DevToolsProtocolTest {
  public:
   DevToolsProtocolTest_AppId() {
diff --git a/chrome/browser/devtools/protocol/devtools_protocol_test_support.cc b/chrome/browser/devtools/protocol/devtools_protocol_test_support.cc
index 2c9b449..670128f 100644
--- a/chrome/browser/devtools/protocol/devtools_protocol_test_support.cc
+++ b/chrome/browser/devtools/protocol/devtools_protocol_test_support.cc
@@ -146,6 +146,10 @@
 bool DevToolsProtocolTestBase::AllowUnsafeOperations() {
   return allow_unsafe_operations_;
 }
+absl::optional<url::Origin>
+DevToolsProtocolTestBase::GetNavigationInitiatorOrigin() {
+  return navigation_initiator_origin_;
+}
 
 bool DevToolsProtocolTestBase::MaySendInputEventsToBrowser() {
   return may_send_input_event_to_browser_;
diff --git a/chrome/browser/devtools/protocol/devtools_protocol_test_support.h b/chrome/browser/devtools/protocol/devtools_protocol_test_support.h
index 3cfba04..45dc805d 100644
--- a/chrome/browser/devtools/protocol/devtools_protocol_test_support.h
+++ b/chrome/browser/devtools/protocol/devtools_protocol_test_support.h
@@ -15,6 +15,8 @@
 #include "chrome/test/base/in_process_browser_test.h"
 #include "content/public/browser/devtools_agent_host.h"
 #include "content/public/browser/devtools_agent_host_client.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "url/origin.h"
 
 class DevToolsProtocolTestBase : public InProcessBrowserTest,
                                  public content::DevToolsAgentHostClient {
@@ -26,6 +28,8 @@
     allow_unsafe_operations_ = allow;
   }
 
+  absl::optional<url::Origin> GetNavigationInitiatorOrigin() override;
+
  protected:
   using NotificationMatcher = base::RepeatingCallback<bool(const base::Value&)>;
 
@@ -82,6 +86,7 @@
   base::Value waiting_for_notification_params_;
   bool allow_unsafe_operations_ = true;
   bool may_send_input_event_to_browser_ = true;
+  absl::optional<url::Origin> navigation_initiator_origin_;
 };
 
 #endif  // CHROME_BROWSER_DEVTOOLS_PROTOCOL_DEVTOOLS_PROTOCOL_TEST_SUPPORT_H_
diff --git a/chrome/browser/extensions/api/debugger/debugger_api.cc b/chrome/browser/extensions/api/debugger/debugger_api.cc
index 73e19fb18..83c0bbe 100644
--- a/chrome/browser/extensions/api/debugger/debugger_api.cc
+++ b/chrome/browser/extensions/api/debugger/debugger_api.cc
@@ -58,6 +58,7 @@
 #include "extensions/common/manifest_constants.h"
 #include "extensions/common/permissions/permissions_data.h"
 #include "extensions/common/switches.h"
+#include "url/origin.h"
 
 using content::DevToolsAgentHost;
 using content::RenderProcessHost;
@@ -208,6 +209,7 @@
   bool MayAttachToBrowser() override;
   bool MayReadLocalFiles() override;
   bool MayWriteLocalFiles() override;
+  absl::optional<url::Origin> GetNavigationInitiatorOrigin() override;
 
  private:
   using PendingRequests =
@@ -440,6 +442,14 @@
   return false;
 }
 
+absl::optional<url::Origin>
+ExtensionDevToolsClientHost::GetNavigationInitiatorOrigin() {
+  // Ensure that navigations started by debugger API are treated as
+  // renderer-initiated by this extension, so that URL spoof defenses are in
+  // effect.
+  return extension_->origin();
+}
+
 // DebuggerFunction -----------------------------------------------------------
 
 DebuggerFunction::DebuggerFunction() : client_host_(nullptr) {}
diff --git a/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl_unittest.cc b/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl_unittest.cc
index 10b3348..5cdbc3c 100644
--- a/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl_unittest.cc
+++ b/chrome/browser/feature_guide/notifications/internal/feature_notification_guide_service_impl_unittest.cc
@@ -74,6 +74,14 @@
         OPTIMIZATION_TARGET_SEGMENTATION_CHROME_LOW_USER_ENGAGEMENT;
     std::move(callback).Run(result);
   }
+  segmentation_platform::SegmentSelectionResult GetCachedSegmentResult(
+      const std::string& segmentation_key) override {
+    segmentation_platform::SegmentSelectionResult result;
+    result.is_ready = true;
+    result.segment = optimization_guide::proto::OptimizationTarget::
+        OPTIMIZATION_TARGET_SEGMENTATION_CHROME_LOW_USER_ENGAGEMENT;
+    return result;
+  }
   void EnableMetrics(bool signal_collection_allowed) override {}
   segmentation_platform::ServiceProxy* GetServiceProxy() override {
     return nullptr;
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 6e733cdb..8a925641 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -827,7 +827,7 @@
   {
     "name": "context-menu-google-lens-chip",
     "owners": [ "yusuyoutube@google.com", "benwgold@google.com", "lens-chrome@google.com" ],
-    "expiry_milestone": 96
+    "expiry_milestone": 100
   },
   { "name":"context-menu-phase2",
       "owners":["gambard", "bling-flags@google.com"],
@@ -846,17 +846,17 @@
   {
     "name": "context-menu-search-with-google-lens",
     "owners": [ "benwgold@google.com", "lens-chrome@google.com" ],
-    "expiry_milestone": 96
+    "expiry_milestone": 100
   },
   {
     "name": "context-menu-shop-with-google-lens",
     "owners": [ "yusuyoutube@google.com", "benwgold@google.com", "lens-chrome@google.com" ],
-    "expiry_milestone": 96
+    "expiry_milestone": 100
   },
   {
     "name": "context-menu-translate-with-google-lens",
     "owners": [ "juanmojica@google.com", "hakop@google.com", "benwgold@google.com", "lens-chrome@google.com" ],
-    "expiry_milestone": 96
+    "expiry_milestone": 100
   },
   {
     "name": "contextual-nudges",
@@ -2213,7 +2213,7 @@
   {
     "name": "enable-lens-region-search",
     "owners": [ "stanfield@google.com", "benwgold@google.com", "juanmojica@google.com" ],
-    "expiry_milestone": 98
+    "expiry_milestone": 100
   },
   {
     "name": "enable-libinput-to-handle-touchpad",
@@ -2519,12 +2519,12 @@
   {
     "name": "enable-quick-action-search-widget-android",
     "owners": [ "maxtauro@google.com", "ender@google.com" ],
-    "expiry_milestone": 98
+    "expiry_milestone": 105
   },
   {
     "name": "enable-quick-action-search-widget-android-dino-variant",
     "owners": [ "maxtauro@google.com", "ender@google.com" ],
-    "expiry_milestone": 98
+    "expiry_milestone": 105
   },
   {
     "name": "enable-raw-draw",
@@ -3342,7 +3342,7 @@
   {
     "name": "google-lens-sdk-intent",
     "owners": [ "juanmojica@google.com", "benwgold@google.com", "lens-chrome@google.com" ],
-    "expiry_milestone": 96
+    "expiry_milestone": 100
   },
   {
     "name": "google-mobile-services-passwords",
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFeatureFlagsSafeModeUnitTest.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFeatureFlagsSafeModeUnitTest.java
index ec4d2e7..354661e 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFeatureFlagsSafeModeUnitTest.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFeatureFlagsSafeModeUnitTest.java
@@ -12,8 +12,10 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
 
 import org.chromium.base.FeatureList;
+import org.chromium.base.task.test.ShadowPostTask;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.chrome.browser.flags.CachedFlagsSafeMode.Behavior;
 
@@ -28,9 +30,10 @@
  * {@link CachedFlagsSafeMode}.
  */
 @RunWith(BaseRobolectricTestRunner.class)
+@Config(shadows = {ShadowPostTask.class})
 public class CachedFeatureFlagsSafeModeUnitTest {
-    private static final String CRASHY_FEATURE = "FeatureA";
-    private static final String OK_FEATURE = "FeatureB";
+    private static final String CRASHY_FEATURE = "CrashyFeature";
+    private static final String OK_FEATURE = "OkFeature";
 
     Map<String, Boolean> mDefaultsSwapped;
 
@@ -324,7 +327,12 @@
     private void endCleanRun(boolean crashyFeatureValue, boolean okFeatureValue) {
         FeatureList.setTestFeatures(makeFeatureMap(crashyFeatureValue, okFeatureValue));
         CachedFeatureFlags.cacheNativeFlags(Arrays.asList(CRASHY_FEATURE, OK_FEATURE));
+
         CachedFeatureFlags.onEndCheckpoint();
+        // Async task writing values should have run synchronously because of ShadowPostTask.
+        assertTrue(CachedFlagsSafeMode.getSafeValuePreferences().contains(
+                "Chrome.Flags.CachedFlag.CrashyFeature"));
+
         CachedFeatureFlags.resetFlagsForTesting();
     }
 
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFlagsSafeMode.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFlagsSafeMode.java
index ce4452cc..d2b2d79 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFlagsSafeMode.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/CachedFlagsSafeMode.java
@@ -16,6 +16,7 @@
 import org.chromium.base.Log;
 import org.chromium.base.TraceEvent;
 import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.task.AsyncTask;
 import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
 import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
 import org.chromium.components.version_info.VersionInfo;
@@ -129,9 +130,25 @@
     void onEndCheckpoint(ValuesReturned safeValuesReturned) {
         SharedPreferencesManager.getInstance().writeInt(
                 ChromePreferenceKeys.FLAGS_CRASH_STREAK_BEFORE_CACHE, 0);
-        writeSafeValues(safeValuesReturned);
-        RecordHistogram.recordEnumeratedHistogram(
-                "Variations.SafeModeCachedFlags.Cached", mBehavior.get(), Behavior.NUM_ENTRIES);
+
+        new AsyncTask<Void>() {
+            @Override
+            protected Void doInBackground() {
+                try {
+                    writeSafeValues(safeValuesReturned);
+                } catch (Exception e) {
+                    Log.e(TAG, "Exception writing safe values.", e);
+                    cancel(true);
+                }
+                return null;
+            }
+
+            @Override
+            protected void onPostExecute(Void unused) {
+                RecordHistogram.recordEnumeratedHistogram("Variations.SafeModeCachedFlags.Cached",
+                        mBehavior.get(), Behavior.NUM_ENTRIES);
+            }
+        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
     }
 
     private void engageSafeModeInNative() {
diff --git a/chrome/browser/lens/java/src/org/chromium/chrome/browser/lens/LensIntentParams.java b/chrome/browser/lens/java/src/org/chromium/chrome/browser/lens/LensIntentParams.java
index 9fc22b12..46d3813 100644
--- a/chrome/browser/lens/java/src/org/chromium/chrome/browser/lens/LensIntentParams.java
+++ b/chrome/browser/lens/java/src/org/chromium/chrome/browser/lens/LensIntentParams.java
@@ -40,8 +40,6 @@
 
         public Builder() {}
 
-        // TODO(b/180967190): remove the with* methods for the required params once
-        // downstream references are updated.
         // lensEntryPoint and isIncognito are required params when creating the
         // LensIntentParams.
         public Builder(@LensEntryPoint int lensEntryPoint, boolean isIncognito) {
@@ -51,16 +49,6 @@
         }
 
         /**
-         * Sets the Lens entry point.
-         *
-         * @param lensEntryPoint The entry point to set as a parameter
-         */
-        public Builder withLensEntryPoint(@LensEntryPoint int lensEntryPoint) {
-            this.mLensEntryPoint = lensEntryPoint;
-            return this;
-        }
-
-        /**
          * Sets the image URI.
          *
          * @param imageUri The image URI to set as a parameter
@@ -101,16 +89,6 @@
         }
 
         /**
-         * Sets whether the client is incognito.
-         *
-         * @param isIncognito Whether the client is incognito as a boolean parameter
-         */
-        public Builder withIsIncognito(boolean isIncognito) {
-            this.mIsIncognito = isIncognito;
-            return this;
-        }
-
-        /**
          * Sets whether the client requires account confirmation.
          *
          * @param requiresConfirmation Whether the client requires account confirmation as a boolean
diff --git a/chrome/browser/media/webrtc/display_media_access_handler.cc b/chrome/browser/media/webrtc/display_media_access_handler.cc
index 6625771d..2c971c7b 100644
--- a/chrome/browser/media/webrtc/display_media_access_handler.cc
+++ b/chrome/browser/media/webrtc/display_media_access_handler.cc
@@ -14,7 +14,6 @@
 #include "base/containers/cxx20_erase.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
-#include "build/chromeos_buildflags.h"
 #include "chrome/browser/bad_message.h"
 #include "chrome/browser/media/webrtc/desktop_capture_devices_util.h"
 #include "chrome/browser/media/webrtc/desktop_media_picker_factory_impl.h"
@@ -36,9 +35,9 @@
 #include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
 #include "third_party/blink/public/mojom/permissions_policy/permissions_policy_feature.mojom.h"
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-#include "chrome/browser/ash/policy/dlp/dlp_content_manager_ash.h"
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#if defined(OS_CHROMEOS)
+#include "chrome/browser/chromeos/policy/dlp/dlp_content_manager.h"
+#endif  // defined(OS_CHROMEOS)
 
 #if defined(OS_MAC)
 #include "chrome/browser/media/webrtc/system_media_capture_permissions_mac.h"
@@ -438,16 +437,16 @@
   }
 #endif  // defined(OS_MAC)
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if defined(OS_CHROMEOS)
   // Check Data Leak Prevention restrictions on Chrome.
-  policy::DlpContentManagerAsh::Get()->CheckScreenShareRestriction(
+  policy::DlpContentManager::Get()->CheckScreenShareRestriction(
       media_id, GetApplicationTitle(web_contents),
       base::BindOnce(&DisplayMediaAccessHandler::OnDlpRestrictionChecked,
                      base::Unretained(this), web_contents, media_id));
-#else   // BUILDFLAG(IS_CHROMEOS_ASH)
+#else   // defined(OS_CHROMEOS)
   FinalizeResult(web_contents, media_id,
                  blink::mojom::MediaStreamRequestResult::OK);
-#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // !defined(OS_CHROMEOS)
 }
 
 void DisplayMediaAccessHandler::WebContentsDestroyed(
@@ -457,7 +456,7 @@
   pending_requests_.erase(web_contents);
 }
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if defined(OS_CHROMEOS)
 void DisplayMediaAccessHandler::OnDlpRestrictionChecked(
     content::WebContents* web_contents,
     const content::DesktopMediaID& media_id,
@@ -468,7 +467,7 @@
           : blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
   FinalizeResult(web_contents, media_id, result);
 }
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // defined(OS_CHROMEOS)
 
 void DisplayMediaAccessHandler::DeletePendingAccessRequest(
     int render_process_id,
diff --git a/chrome/browser/media/webrtc/display_media_access_handler.h b/chrome/browser/media/webrtc/display_media_access_handler.h
index 97e6e4b1..99f7c75 100644
--- a/chrome/browser/media/webrtc/display_media_access_handler.h
+++ b/chrome/browser/media/webrtc/display_media_access_handler.h
@@ -8,7 +8,6 @@
 #include <memory>
 
 #include "base/containers/flat_map.h"
-#include "build/chromeos_buildflags.h"
 #include "chrome/browser/media/capture_access_handler_base.h"
 #include "chrome/browser/media/media_access_handler.h"
 #include "chrome/browser/media/webrtc/capture_policy_utils.h"
@@ -95,12 +94,12 @@
   void OnDisplaySurfaceSelected(content::WebContents* web_contents,
                                 content::DesktopMediaID media_id);
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if defined(OS_CHROMEOS)
   // Called back after checking Data Leak Prevention (DLP) restrictions.
   void OnDlpRestrictionChecked(content::WebContents* web_contents,
                                const content::DesktopMediaID& media_id,
                                bool is_dlp_allowed);
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // defined(OS_CHROMEOS)
 
   void DeletePendingAccessRequest(int render_process_id,
                                   int render_frame_id,
diff --git a/chrome/browser/media/webrtc/display_media_access_handler_unittest.cc b/chrome/browser/media/webrtc/display_media_access_handler_unittest.cc
index 6c7962a..277ea6f 100644
--- a/chrome/browser/media/webrtc/display_media_access_handler_unittest.cc
+++ b/chrome/browser/media/webrtc/display_media_access_handler_unittest.cc
@@ -13,7 +13,6 @@
 #include "base/run_loop.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
-#include "build/chromeos_buildflags.h"
 #include "chrome/browser/media/webrtc/fake_desktop_media_picker_factory.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
@@ -30,8 +29,8 @@
 #include "base/mac/mac_util.h"
 #endif
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-#include "chrome/browser/ash/policy/dlp/mock_dlp_content_manager_ash.h"
+#if defined(OS_CHROMEOS)
+#include "chrome/browser/chromeos/policy/dlp/mock_dlp_content_manager.h"
 #endif
 
 class DisplayMediaAccessHandlerTest : public ChromeRenderViewHostTestHarness {
@@ -252,14 +251,14 @@
   EXPECT_EQ(0u, devices.size());
 }
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if defined(OS_CHROMEOS)
 TEST_F(DisplayMediaAccessHandlerTest, DlpRestricted) {
   const content::DesktopMediaID media_id(content::DesktopMediaID::TYPE_SCREEN,
                                          content::DesktopMediaID::kFakeId);
 
   // Setup Data Leak Prevention restriction.
-  policy::MockDlpContentManagerAsh mock_dlp_content_manager;
-  policy::ScopedDlpContentManagerAshForTesting scoped_dlp_content_manager(
+  policy::MockDlpContentManager mock_dlp_content_manager;
+  policy::ScopedDlpContentObserverForTesting scoped_dlp_content_observer(
       &mock_dlp_content_manager);
   EXPECT_CALL(mock_dlp_content_manager, CheckScreenShareRestriction)
       .WillOnce([](const content::DesktopMediaID& media_id,
@@ -506,15 +505,15 @@
       /*expected_number_of_devices=*/2u);
 }
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if defined(OS_CHROMEOS)
 TEST_F(DisplayMediaAccessHandlerTest, ChangeSourceDlpRestricted) {
   const content::DesktopMediaID media_id(
       content::DesktopMediaID::TYPE_WEB_CONTENTS,
       content::DesktopMediaID::kNullId, GetWebContentsMediaCaptureId());
 
   // Setup Data Leak Prevention restriction.
-  policy::MockDlpContentManagerAsh mock_dlp_content_manager;
-  policy::ScopedDlpContentManagerAshForTesting scoped_dlp_content_manager_(
+  policy::MockDlpContentManager mock_dlp_content_manager;
+  policy::ScopedDlpContentObserverForTesting scoped_dlp_content_observer(
       &mock_dlp_content_manager);
   EXPECT_CALL(mock_dlp_content_manager, CheckScreenShareRestriction)
       .WillOnce([](const content::DesktopMediaID& media_id,
diff --git a/chrome/browser/optimization_guide/prediction/prediction_manager.cc b/chrome/browser/optimization_guide/prediction/prediction_manager.cc
index d53fb10..99da00a 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_manager.cc
+++ b/chrome/browser/optimization_guide/prediction/prediction_manager.cc
@@ -539,7 +539,7 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   std::unique_ptr<StoreUpdateData> prediction_model_update_data =
       StoreUpdateData::CreatePredictionModelStoreUpdateData(
-          clock_->Now() + features::StoredModelsInactiveDuration());
+          clock_->Now() + features::StoredModelsValidDuration());
   bool has_models_to_update = false;
   std::string debug_msg;
   for (const auto& model : prediction_models) {
@@ -621,7 +621,7 @@
   // Store the received model in the store.
   std::unique_ptr<StoreUpdateData> prediction_model_update_data =
       StoreUpdateData::CreatePredictionModelStoreUpdateData(
-          clock_->Now() + features::StoredModelsInactiveDuration());
+          clock_->Now() + features::StoredModelsValidDuration());
   prediction_model_update_data->CopyPredictionModelIntoUpdateData(model);
   model_and_features_store_->UpdatePredictionModels(
       std::move(prediction_model_update_data),
diff --git a/chrome/browser/prefs/chrome_pref_service_factory.cc b/chrome/browser/prefs/chrome_pref_service_factory.cc
index 43e1f8ed..22e37f3 100644
--- a/chrome/browser/prefs/chrome_pref_service_factory.cc
+++ b/chrome/browser/prefs/chrome_pref_service_factory.cc
@@ -48,6 +48,7 @@
 #include "components/prefs/pref_service.h"
 #include "components/prefs/pref_store.h"
 #include "components/prefs/pref_value_store.h"
+#include "components/prefs/standalone_browser_pref_store.h"
 #include "components/safe_browsing/core/common/safe_browsing_prefs.h"
 #include "components/search_engines/default_search_manager.h"
 #include "components/search_engines/search_engines_pref_names.h"
@@ -313,6 +314,12 @@
   }
 #endif
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  scoped_refptr<PrefStore> standalone_browser_prefs =
+      base::MakeRefCounted<StandaloneBrowserPrefStore>();
+  factory->set_standalone_browser_prefs(standalone_browser_prefs);
+#endif
+
   factory->set_async(async);
   factory->set_extension_prefs(std::move(extension_prefs));
   factory->set_command_line_prefs(
diff --git a/chrome/browser/resources/download_shelf/app.ts b/chrome/browser/resources/download_shelf/app.ts
index 1680034..c370d49 100644
--- a/chrome/browser/resources/download_shelf/app.ts
+++ b/chrome/browser/resources/download_shelf/app.ts
@@ -24,25 +24,22 @@
   constructor() {
     super();
 
-    /** @private {!DownloadShelfApiProxy} */
     this.apiProxy_ = DownloadShelfApiProxyImpl.getInstance();
 
-    const showAllButton = this.$('#show-all-button') as HTMLElement;
+    const showAllButton = this.$<HTMLElement>('#show-all-button')!;
     showAllButton.innerText = loadTimeData.getString('showAll');
     showAllButton.addEventListener('click', () => this.onShowAll_());
 
-    const closeButton = this.$('#close-button');
+    const closeButton = this.$('#close-button')!;
     closeButton.setAttribute('aria-label', loadTimeData.getString('close'));
     closeButton.addEventListener('click', () => this.onClose_());
   }
 
-  /** @private */
-  onShowAll_() {
+  private onShowAll_() {
     this.apiProxy_.doShowAll();
   }
 
-  /** @private */
-  onClose_() {
+  private onClose_() {
     this.apiProxy_.doClose();
   }
 }
diff --git a/chrome/browser/resources/download_shelf/download_item.ts b/chrome/browser/resources/download_shelf/download_item.ts
index d19f678..30037957 100644
--- a/chrome/browser/resources/download_shelf/download_item.ts
+++ b/chrome/browser/resources/download_shelf/download_item.ts
@@ -44,20 +44,20 @@
     this.opened = false;
     this.apiProxy_ = DownloadShelfApiProxyImpl.getInstance();
 
-    this.$('#shadow-mask')
-        .addEventListener('click', () => this.onOpenButtonClick_());
-    this.$('#dropdown-button')
-        .addEventListener('click', e => this.onDropdownButtonClick_(e));
+    this.$('#shadow-mask')!.addEventListener(
+        'click', () => this.onOpenButtonClick_());
+    this.$('#dropdown-button')!.addEventListener(
+        'click', e => this.onDropdownButtonClick_(e));
     const discardButton = this.$('#discard-button') as HTMLElement;
     discardButton.innerText = loadTimeData.getString('discardButtonText');
     discardButton.addEventListener('click', () => this.onDiscardButtonClick_());
-    this.$('#keep-button')
-        .addEventListener('click', () => this.onKeepButtonClick_());
+    this.$('#keep-button')!.addEventListener(
+        'click', () => this.onKeepButtonClick_());
     this.addEventListener('contextmenu', e => this.onContextMenu_(e));
 
-    this.$('.progress-indicator').addEventListener('animationend', () => {
-      this.$('.progress-indicator')
-          .classList.remove('download-complete-animation');
+    this.$('.progress-indicator')!.addEventListener('animationend', () => {
+      this.$('.progress-indicator')!.classList.remove(
+          'download-complete-animation');
     });
   }
 
@@ -132,8 +132,8 @@
           this.progress = 1;
           // Only start animation if it's called from OnDownloadUpdated.
           if (this.downloadUpdated_) {
-            this.$('.progress-indicator')
-                .classList.add('download-complete-animation');
+            this.$('.progress-indicator')!.classList.add(
+                'download-complete-animation');
           }
           break;
         case DownloadState.kInterrupted:
diff --git a/chrome/browser/resources/download_shelf/download_list.ts b/chrome/browser/resources/download_shelf/download_list.ts
index 740ceea..16266ac 100644
--- a/chrome/browser/resources/download_shelf/download_list.ts
+++ b/chrome/browser/resources/download_shelf/download_list.ts
@@ -31,7 +31,7 @@
     super();
 
     this.apiProxy_ = DownloadShelfApiProxyImpl.getInstance();
-    this.listElement_ = this.$('#download-list') as HTMLElement;
+    this.listElement_ = this.$<HTMLElement>('#download-list')!;
     this.resizeObserver_ = new ResizeObserver(() => this.updateElements_());
     this.resizeObserver_.observe(this.listElement_);
 
@@ -123,7 +123,7 @@
   private clear_() {
     while (this.listenerIds_.length) {
       this.apiProxy_.getCallbackRouter().removeListener(
-          this.listenerIds_.shift());
+          this.listenerIds_.shift()!);
     }
 
     while (this.listElement_.firstChild) {
diff --git a/chrome/browser/resources/download_shelf/tsconfig_base.json b/chrome/browser/resources/download_shelf/tsconfig_base.json
index 895f374..3e71f763 100644
--- a/chrome/browser/resources/download_shelf/tsconfig_base.json
+++ b/chrome/browser/resources/download_shelf/tsconfig_base.json
@@ -4,7 +4,6 @@
     "allowJs": true,
     "noUncheckedIndexedAccess": false,
     "noUnusedLocals": false,
-    "strictNullChecks": false,
     "strictPropertyInitialization": false
   }
 }
diff --git a/chrome/browser/resources/segmentation_internals/segmentation_internals.html b/chrome/browser/resources/segmentation_internals/segmentation_internals.html
index 3a89402..a52ebb8 100644
--- a/chrome/browser/resources/segmentation_internals/segmentation_internals.html
+++ b/chrome/browser/resources/segmentation_internals/segmentation_internals.html
@@ -1,6 +1,12 @@
 <!doctype html>
 <html lang="en" dir="ltr">
   <head>
+    <style>
+      .segment {
+        border: 1px outset black;
+        margin: 2px 2px 2px 2px;
+      }
+    </style>
     <meta charset="utf-8">
     <title>Segmentation Internals</title>
     <meta name="viewport" content="width=device-width">
@@ -12,19 +18,9 @@
     <div>
       Initialized: <span id="initialized">false</span>, Service status: <span id="service-status">0</span>
     </div>
-    <h4>Get segment</h4>
-    <div>
-      <Label for="segment-key">Segment key:</Label>
-      <input type="text" id="segment-key">
-      <button id="get-segment">Get segment result</button>
+    <h4>Segment information</h4>
+    <div id="segment-container">
     </div>
-    <h4>Segment result</h4>
-    <div>
-      Is ready: <span id="is-ready"></span>
-    </div>
-    <div>
-      Optimization target: <span id="optimization-target"></span>
-      </div>
     <script type="module" src="segmentation_internals.js"></script>
   </body>
 </html>
diff --git a/chrome/browser/resources/segmentation_internals/segmentation_internals.ts b/chrome/browser/resources/segmentation_internals/segmentation_internals.ts
index f88c479..1fd81a71 100644
--- a/chrome/browser/resources/segmentation_internals/segmentation_internals.ts
+++ b/chrome/browser/resources/segmentation_internals/segmentation_internals.ts
@@ -4,20 +4,31 @@
 
 import {$} from 'chrome://resources/js/util.m.js';
 
+import {SegmentInfo} from './segmentation_internals.mojom-webui.js';
 import {SegmentationInternalsBrowserProxy} from './segmentation_internals_browser_proxy.js';
 
 function getProxy(): SegmentationInternalsBrowserProxy {
   return SegmentationInternalsBrowserProxy.getInstance();
 }
 
-function initialize() {
-  $('get-segment').onclick = async function() {
-    const {result} = await getProxy().getSegment(
-        ($('segment-key') as HTMLInputElement).value);
-    $('is-ready').textContent = String(result.isReady);
-    $('optimization-target').textContent = String(result.optimizationTarget);
-  };
+function addChildDivToParent(parent: HTMLElement, info: SegmentInfo) {
+  const div = document.createElement('div');
+  div.className = 'segment';
+  div.textContent = String(info.optimizationTarget);
+  div.setAttribute('simple', '');
+  div.addEventListener('click', () => {
+    if (div.hasAttribute('simple')) {
+      div.textContent = String(info.segmentData);
+      div.removeAttribute('simple');
+    } else {
+      div.textContent = String(info.optimizationTarget);
+      div.setAttribute('simple', '');
+    }
+  });
+  parent.appendChild(div);
+}
 
+function initialize() {
   getProxy().getCallbackRouter().onServiceStatusChanged.addListener(
       (initialized: boolean, status: number) => {
         $('initialized').textContent = String(initialized);
@@ -25,8 +36,16 @@
       });
 
   getProxy().getCallbackRouter().onSegmentInfoAvailable.addListener(
-      () => {
-          // TODO(qinmin): display the segment info on internal page.
+      (segmentInfos: Array<SegmentInfo>) => {
+        const parent = $('segment-container');
+        // Remove all current children.
+        while (parent.firstChild) {
+          parent.removeChild(parent.firstChild);
+        }
+        // Append new children.
+        for (let i = 0; i < segmentInfos.length; ++i) {
+          addChildDivToParent(parent, segmentInfos[i]!);
+        }
       });
 
   getProxy().getServiceStatus();
diff --git a/chrome/browser/resources/segmentation_internals/segmentation_internals_browser_proxy.ts b/chrome/browser/resources/segmentation_internals/segmentation_internals_browser_proxy.ts
index d9b5e018..88e5f30 100644
--- a/chrome/browser/resources/segmentation_internals/segmentation_internals_browser_proxy.ts
+++ b/chrome/browser/resources/segmentation_internals/segmentation_internals_browser_proxy.ts
@@ -17,10 +17,6 @@
         this.handler.$.bindNewPipeAndPassReceiver());
   }
 
-  getSegment(key: string) {
-    return this.handler.getSegment(key);
-  }
-
   getServiceStatus() {
     return this.handler.getServiceStatus();
   }
diff --git a/chrome/browser/resources/settings/chromeos/BUILD.gn b/chrome/browser/resources/settings/chromeos/BUILD.gn
index bb43b3b9..96623b07 100644
--- a/chrome/browser/resources/settings/chromeos/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/BUILD.gn
@@ -60,6 +60,11 @@
       "chrome://resources/cr_components/chromeos/bluetooth/cros_bluetooth_config.js",
       "chrome://resources/cr_components/chromeos/cellular_setup/mojo_interface_provider.m.js",
       "chrome://resources/cr_components/chromeos/network/mojo_interface_provider.m.js",
+      "chrome://resources/cr_components/app_management/app_management.mojom-lite.js",
+      "chrome://resources/cr_components/app_management/file_path.mojom-lite.js",
+      "chrome://resources/cr_components/app_management/image.mojom-lite.js",
+      "chrome://resources/cr_components/app_management/safe_base_name.mojom-lite.js",
+      "chrome://resources/cr_components/app_management/types.mojom-lite.js",
       "chrome://resources/js/cr.m.js",
       "chrome://resources/chromeos/colors/cros_styles.css",
       "chrome://resources/mojo/mojo/public/js/mojo_bindings_lite.js",
@@ -104,11 +109,11 @@
 # Mojo files generated by non-OS-settings targets, not bundled.
 preprocess_if_expr("preprocess_external_mojo") {
   deps = [
-    "//chrome/browser/ui/webui/app_management:mojo_bindings_js",
     "//chrome/browser/ui/webui/settings/chromeos/os_apps_page/mojom:mojom_js",
     "//components/services/app_service/public/mojom:mojom_js",
     "//mojo/public/mojom/base",
     "//ui/gfx/image/mojom:mojom_js",
+    "//ui/webui/resources/cr_components/app_management:mojo_bindings_js",
   ]
   in_folder = "$root_gen_dir"
 
@@ -121,7 +126,7 @@
     "mojo/public/mojom/base/file_path.mojom-lite.js",
     "mojo/public/mojom/base/safe_base_name.mojom-lite.js",
     "ui/gfx/image/mojom/image.mojom-lite.js",
-    "chrome/browser/ui/webui/app_management/app_management.mojom-lite.js",
+    "ui/webui/resources/cr_components/app_management/app_management.mojom-lite.js",
     "chrome/browser/ui/webui/settings/chromeos/os_apps_page/mojom/app_notification_handler.mojom-lite.js",
     "components/services/app_service/public/mojom/types.mojom-lite.js",
   ]
@@ -188,7 +193,7 @@
     "mojo/public/mojom/base/file_path.mojom-lite.js|app-management/file_path.mojom-lite.js",
     "mojo/public/mojom/base/safe_base_name.mojom-lite.js|app-management/safe_base_name.mojom-lite.js",
     "ui/gfx/image/mojom/image.mojom-lite.js|app-management/image.mojom-lite.js",
-    "chrome/browser/ui/webui/app_management/app_management.mojom-lite.js|app-management/app_management.mojom-lite.js",
+    "ui/webui/resources/cr_components/app_management/app_management.mojom-lite.js|app-management/app_management.mojom-lite.js",
     "chrome/browser/ui/webui/settings/chromeos/os_apps_page/mojom/app_notification_handler.mojom-lite.js|os_apps_page/app_notification_handler.mojom-lite.js",
     "components/services/app_service/public/mojom/types.mojom-lite.js|app-management/types.mojom-lite.js",
     "../../nearby_share/shared/nearby_share_progress_bar_dark.json|nearby_share_progress_bar_dark.json",
@@ -256,17 +261,13 @@
     "chromeos/os_apps_page/app_management_page/actions.js",
     "chromeos/os_apps_page/app_management_page/api_listener.js",
     "chromeos/os_apps_page/app_management_page/browser_proxy.js",
-    "chromeos/os_apps_page/app_management_page/constants.js",
     "chromeos/os_apps_page/app_management_page/fake_page_handler.js",
     "chromeos/os_apps_page/app_management_page/reducers.js",
     "chromeos/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_browser_proxy.js",
     "chromeos/os_apps_page/app_management_page/store.js",
     "chromeos/os_apps_page/app_management_page/store_client.js",
-    "chromeos/os_apps_page/app_management_page/types.js",
     "chromeos/os_apps_page/app_management_page/util.js",
     "chromeos/os_apps_page/app_notifications_page/mojo_interface_provider.js",
-    "chromeos/os_apps_page/permission_constants.js",
-    "chromeos/os_apps_page/permission_util.js",
     "chromeos/os_languages_page/input_method_settings.js",
     "chromeos/os_languages_page/languages_browser_proxy.js",
     "chromeos/os_languages_page/languages.js",
@@ -492,17 +493,13 @@
     "chromeos/os_apps_page/app_management_page/icons.js",
     "chromeos/os_apps_page/app_management_page/main_view.js",
     "chromeos/os_apps_page/app_management_page/more_permissions_item.js",
-    "chromeos/os_apps_page/app_management_page/permission_item.js",
     "chromeos/os_apps_page/app_management_page/pin_to_shelf_item.js",
     "chromeos/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_detail_view.js",
     "chromeos/os_apps_page/app_management_page/pwa_detail_view.js",
     "chromeos/os_apps_page/app_management_page/resize_lock_item.js",
-    "chromeos/os_apps_page/app_management_page/shared_style.js",
-    "chromeos/os_apps_page/app_management_page/shared_vars.js",
     "chromeos/os_apps_page/app_management_page/supported_links_overlapping_apps_dialog.js",
     "chromeos/os_apps_page/app_management_page/supported_links_dialog.js",
     "chromeos/os_apps_page/app_management_page/supported_links_item.js",
-    "chromeos/os_apps_page/app_management_page/toggle_row.js",
     "chromeos/os_apps_page/app_management_page/uninstall_button.js",
     "chromeos/os_apps_page/os_apps_page.js",
     "chromeos/os_files_page/os_files_page.js",
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_apps_page/BUILD.gn
index aeb1ba9..5286361 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/BUILD.gn
@@ -13,8 +13,6 @@
     ":android_apps_browser_proxy",
     ":android_apps_subpage",
     ":os_apps_page",
-    ":permission_constants",
-    ":permission_util",
   ]
 }
 
@@ -45,10 +43,10 @@
     "..:os_route.m",
     "..:prefs_behavior",
     "../../:router",
-    "./app_management_page:constants",
     "./app_management_page:store_client",
     "./app_notifications_page:mojo_interface_provider",
     "//chrome/browser/ui/webui/settings/chromeos/os_apps_page/mojom:mojom_js_library_for_compile",
+    "//ui/webui/resources/cr_components/app_management:constants",
     "//ui/webui/resources/cr_components/chromeos/localized_link:localized_link",
     "//ui/webui/resources/cr_components/chromeos/localized_link:localized_link",
     "//ui/webui/resources/js:cr.m",
@@ -60,17 +58,6 @@
   ]
 }
 
-js_library("permission_util") {
-  deps = [
-    ":permission_constants",
-    "//ui/webui/resources/js:assert.m",
-  ]
-}
-
-js_library("permission_constants") {
-  deps = [ "//chrome/browser/ui/webui/app_management:mojo_bindings_js_library_for_compile" ]
-}
-
 html_to_js("web_components") {
   js_files = [
     "android_apps_subpage.js",
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/BUILD.gn
index 56065b9..a79af07 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/BUILD.gn
@@ -18,26 +18,20 @@
     ":arc_detail_view",
     ":browser_proxy",
     ":chrome_app_detail_view",
-    ":constants",
     ":dom_switch",
     ":fake_page_handler",
     ":icons",
     ":main_view",
     ":more_permissions_item",
-    ":permission_item",
     ":pin_to_shelf_item",
     ":pwa_detail_view",
     ":reducers",
     ":resize_lock_item",
-    ":shared_style",
-    ":shared_vars",
     ":store",
     ":store_client",
     ":supported_links_dialog",
     ":supported_links_item",
     ":supported_links_overlapping_apps_dialog",
-    ":toggle_row",
-    ":types",
     ":uninstall_button",
     ":util",
   ]
@@ -45,7 +39,7 @@
 
 js_library("actions") {
   deps = [
-    "//chrome/browser/ui/webui/app_management:mojo_bindings_js_library_for_compile",
+    "//ui/webui/resources/cr_components/app_management:mojo_bindings_js_library_for_compile",
     "//ui/webui/resources/js:cr.m",
   ]
 }
@@ -96,21 +90,21 @@
 
 js_library("arc_detail_view") {
   deps = [
-    ":constants",
     ":fake_page_handler",
     ":more_permissions_item",
-    ":permission_item",
     ":pin_to_shelf_item",
     ":store_client",
     ":supported_links_item",
     ":util",
+    "//ui/webui/resources/cr_components/app_management:constants",
+    "//ui/webui/resources/cr_components/app_management:permission_item",
   ]
 }
 
 js_library("browser_proxy") {
   deps = [
     ":fake_page_handler",
-    "//chrome/browser/ui/webui/app_management:mojo_bindings_js_library_for_compile",
+    "//ui/webui/resources/cr_components/app_management:mojo_bindings_js_library_for_compile",
   ]
 }
 
@@ -125,10 +119,6 @@
   ]
 }
 
-js_library("constants") {
-  deps = [ "//chrome/browser/ui/webui/app_management:mojo_bindings_js_library_for_compile" ]
-}
-
 js_library("dom_switch") {
   deps = [
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
@@ -139,7 +129,7 @@
 
 js_library("fake_page_handler") {
   deps = [
-    "//chrome/browser/ui/webui/app_management:mojo_bindings_js_library_for_compile",
+    "//ui/webui/resources/cr_components/app_management:mojo_bindings_js_library_for_compile",
     "//ui/webui/resources/js:promise_resolver.m",
   ]
 }
@@ -152,12 +142,12 @@
   deps = [
     ":app_item",
     ":browser_proxy",
-    ":constants",
     ":store_client",
     ":util",
     "../..:os_route.m",
     "../..:route_observer_behavior",
     "../../..:router",
+    "//ui/webui/resources/cr_components/app_management:constants",
     "//ui/webui/resources/js:assert.m",
     "//ui/webui/resources/js:load_time_data.m",
   ]
@@ -167,46 +157,35 @@
   deps = [ "//ui/webui/resources/cr_elements/cr_icon_button:cr_icon_button.m" ]
 }
 
-js_library("permission_item") {
-  deps = [
-    ":browser_proxy",
-    ":fake_page_handler",
-    ":store_client",
-    ":toggle_row",
-    ":util",
-    "../..:metrics_recorder.m",
-  ]
-}
-
 js_library("pin_to_shelf_item") {
   deps = [
     ":browser_proxy",
-    ":constants",
-    ":toggle_row",
-    ":types",
     ":util",
     "../..:metrics_recorder.m",
+    "//ui/webui/resources/cr_components/app_management:constants",
+    "//ui/webui/resources/cr_components/app_management:toggle_row",
+    "//ui/webui/resources/cr_components/app_management:types",
     "//ui/webui/resources/js:assert.m",
   ]
 }
 
 js_library("pwa_detail_view") {
   deps = [
-    ":constants",
     ":fake_page_handler",
     ":more_permissions_item",
-    ":permission_item",
     ":pin_to_shelf_item",
     ":store_client",
     ":supported_links_item",
     ":util",
+    "//ui/webui/resources/cr_components/app_management:constants",
+    "//ui/webui/resources/cr_components/app_management:permission_item",
   ]
 }
 
 js_library("reducers") {
   deps = [
-    ":types",
     ":util",
+    "//ui/webui/resources/cr_components/app_management:types",
     "//ui/webui/resources/js:cr.m",
   ]
 }
@@ -214,22 +193,14 @@
 js_library("resize_lock_item") {
   deps = [
     ":browser_proxy",
-    ":constants",
-    ":toggle_row",
-    ":types",
     ":util",
+    "//ui/webui/resources/cr_components/app_management:constants",
+    "//ui/webui/resources/cr_components/app_management:toggle_row",
+    "//ui/webui/resources/cr_components/app_management:types",
     "//ui/webui/resources/js:assert.m",
   ]
 }
 
-js_library("shared_style") {
-  deps = []
-}
-
-js_library("shared_vars") {
-  deps = []
-}
-
 js_library("store") {
   deps = [
     "//ui/webui/resources/js:cr.m",
@@ -240,7 +211,7 @@
 js_library("store_client") {
   deps = [
     ":store",
-    ":types",
+    "//ui/webui/resources/cr_components/app_management:types",
     "//ui/webui/resources/js:cr.m",
     "//ui/webui/resources/js/cr/ui:store",
     "//ui/webui/resources/js/cr/ui:store_client",
@@ -268,13 +239,12 @@
 js_library("supported_links_item") {
   deps = [
     ":browser_proxy",
-    ":constants",
     ":store_client",
     ":supported_links_dialog",
     ":supported_links_overlapping_apps_dialog",
-    ":types",
     ":util",
     "../..:metrics_recorder.m",
+    "//ui/webui/resources/cr_components/app_management:constants",
     "//ui/webui/resources/cr_components/chromeos/localized_link:localized_link",
     "//ui/webui/resources/cr_elements/cr_radio_button:cr_radio_button.m",
     "//ui/webui/resources/cr_elements/cr_radio_group:cr_radio_group.m",
@@ -282,20 +252,6 @@
   ]
 }
 
-js_library("toggle_row") {
-  deps = [
-    ":browser_proxy",
-    ":store_client",
-    ":types",
-    "//ui/webui/resources/cr_elements/cr_toggle:cr_toggle.m",
-    "//ui/webui/resources/cr_elements/policy:cr_policy_indicator.m",
-  ]
-}
-
-js_library("types") {
-  deps = [ "//chrome/browser/ui/webui/app_management:mojo_bindings_js_library_for_compile" ]
-}
-
 js_library("uninstall_button") {
   deps = [
     ":store_client",
@@ -309,7 +265,6 @@
 
 js_library("util") {
   deps = [
-    "../:permission_constants",
     "../..:os_route.m",
     "../../..:router",
   ]
@@ -326,16 +281,12 @@
     "icons.js",
     "main_view.js",
     "more_permissions_item.js",
-    "permission_item.js",
     "pin_to_shelf_item.js",
     "pwa_detail_view.js",
     "resize_lock_item.js",
-    "shared_style.js",
-    "shared_vars.js",
     "supported_links_overlapping_apps_dialog.js",
     "supported_links_dialog.js",
     "supported_links_item.js",
-    "toggle_row.js",
     "uninstall_button.js",
   ]
 }
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/api_listener.js b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/api_listener.js
index eac8266..8aed5e4 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/api_listener.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/api_listener.js
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {createInitialState} from '//resources/cr_components/app_management/util.js';
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {Action} from 'chrome://resources/js/cr/ui/store.js';
 
 import {addApp, changeApp, removeApp} from './actions.js';
 import {BrowserProxy} from './browser_proxy.js';
 import {AppManagementStore} from './store.js';
-import {createInitialState} from './util.js';
 
 let initialized = false;
 
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/app_detail_view.js b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/app_detail_view.js
index 590e42f3..5919592 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/app_detail_view.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/app_detail_view.js
@@ -10,6 +10,8 @@
 import './borealis_page/borealis_detail_view.js';
 import '../../../settings_shared_css.js';
 
+import {AppManagementUserAction, AppType} from '//resources/cr_components/app_management/constants.js';
+import {getSelectedApp, recordAppManagementUserAction} from '//resources/cr_components/app_management/util.js';
 import {assert, assertNotReached} from '//resources/js/assert.m.js';
 import {afterNextRender, flush, html, Polymer, TemplateInstanceBase, Templatizer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
@@ -18,9 +20,8 @@
 import {RouteObserverBehavior} from '../../route_observer_behavior.js';
 
 import {updateSelectedAppId} from './actions.js';
-import {AppManagementUserAction, AppType} from './constants.js';
 import {AppManagementStoreClient} from './store_client.js';
-import {getSelectedApp, openMainPage, recordAppManagementUserAction} from './util.js';
+import {openMainPage} from './util.js';
 
 Polymer({
   _template: html`{__html_template__}`,
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/app_item.js b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/app_item.js
index d903c74..bd5f8ec 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/app_item.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/app_item.js
@@ -1,17 +1,18 @@
 // Copyright 2018 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
-import './shared_style.js';
-import './shared_vars.js';
+import '//resources/cr_components/app_management/shared_style.js';
+import '//resources/cr_components/app_management/shared_vars.js';
 import '//resources/cr_elements/cr_icons_css.m.js';
 
+import {AppManagementEntryPoint, AppManagementEntryPointsHistogramName, AppType} from '//resources/cr_components/app_management/constants.js';
+import {getAppIcon} from '//resources/cr_components/app_management/util.js';
 import {assert, assertNotReached} from '//resources/js/assert.m.js';
 import {afterNextRender, flush, html, Polymer, TemplateInstanceBase, Templatizer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {updateSelectedAppId} from './actions.js';
-import {AppManagementEntryPoint, AppManagementEntryPointsHistogramName, AppType} from './constants.js';
 import {AppManagementStoreClient} from './store_client.js';
-import {getAppIcon, openAppDetailPage} from './util.js';
+import {openAppDetailPage} from './util.js';
 
 Polymer({
   _template: html`{__html_template__}`,
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/arc_detail_view.html b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/arc_detail_view.html
index c03a73e..386b7de9 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/arc_detail_view.html
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/arc_detail_view.html
@@ -11,6 +11,7 @@
   </app-management-pin-to-shelf-item>
   <app-management-permission-item id="notifications-card"
       class="permission-card-row separated-row"
+      app_="[[app_]]"
       permission-label="$i18n{appManagementNotificationsLabel}"
       permission-type="kNotifications">
   </app-management-permission-item>
@@ -23,26 +24,31 @@
     <div id="subpermission-list"
         class="permission-list indented-permission-block">
       <app-management-permission-item class="subpermission-row"
+          app_="[[app_]]"
           icon="app-management:location"
           permission-label="$i18n{appManagementLocationPermissionLabel}"
           permission-type="kLocation">
       </app-management-permission-item>
       <app-management-permission-item class="subpermission-row"
+          app_="[[app_]]"
           icon="app-management:camera"
           permission-label="$i18n{appManagementCameraPermissionLabel}"
           permission-type="kCamera">
       </app-management-permission-item>
       <app-management-permission-item class="subpermission-row"
+          app_="[[app_]]"
           icon="app-management:microphone"
           permission-label="$i18n{appManagementMicrophonePermissionLabel}"
           permission-type="kMicrophone">
       </app-management-permission-item>
       <app-management-permission-item class="subpermission-row"
+          app_="[[app_]]"
           icon="app-management:contacts"
           permission-label="$i18n{appManagementContactsPermissionLabel}"
           permission-type="kContacts">
       </app-management-permission-item>
       <app-management-permission-item class="subpermission-row"
+          app_="[[app_]]"
           icon="app-management:storage"
           permission-label="$i18n{appManagementStoragePermissionLabel}"
           permission-type="kStorage">
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/arc_detail_view.js b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/arc_detail_view.js
index fb816bb2..00f14d9 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/arc_detail_view.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/arc_detail_view.js
@@ -3,18 +3,18 @@
 // found in the LICENSE file.
 import './icons.js';
 import './more_permissions_item.js';
-import './permission_item.js';
 import './pin_to_shelf_item.js';
 import './resize_lock_item.js';
-import './shared_style.js';
 import './supported_links_item.js';
+import '//resources/cr_components/app_management/shared_style.js';
+import '//resources/cr_components/app_management/permission_item.js';
 import '//resources/cr_elements/icons.m.js';
 
 import {afterNextRender, flush, html, Polymer, TemplateInstanceBase, Templatizer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {getAppIcon, getPermission, getSelectedApp} from 'chrome://resources/cr_components/app_management/util.js';
 
 import {BrowserProxy} from './browser_proxy.js';
 import {AppManagementStoreClient} from './store_client.js';
-import {getAppIcon, getPermission, getSelectedApp} from './util.js';
 
 Polymer({
   _template: html`{__html_template__}`,
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/borealis_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/borealis_page/BUILD.gn
index c9541da..1124ca5 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/borealis_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/borealis_page/BUILD.gn
@@ -14,12 +14,12 @@
 
 js_library("borealis_detail_view") {
   deps = [
-    "../:permission_item",
     "../:pin_to_shelf_item",
     "../:store_client",
     "../:util",
     "../../..:os_route.m",
     "../../../..:router",
+    "//ui/webui/resources/cr_components/app_management:permission_item",
   ]
 }
 
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/borealis_page/borealis_detail_view.html b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/borealis_page/borealis_detail_view.html
index 14534897..5011fcda 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/borealis_page/borealis_detail_view.html
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/borealis_page/borealis_detail_view.html
@@ -45,6 +45,7 @@
       </div>
       <app-management-permission-item
           id="microphone-permission"
+          app_="[[app_]]"
           class="subpermission-row" icon="app-management:microphone"
           permission-label="$i18n{appManagementMicrophonePermissionLabel}"
           permission-type="kMicrophone">
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/borealis_page/borealis_detail_view.js b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/borealis_page/borealis_detail_view.js
index 416af212..5713205 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/borealis_page/borealis_detail_view.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/borealis_page/borealis_detail_view.js
@@ -7,13 +7,13 @@
 import {Polymer, html} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import '../icons.js';
-import '../permission_item.js';
 import '../pin_to_shelf_item.js';
-import '../shared_style.js';
+import '//resources/cr_components/app_management/shared_style.js';
+import '//resources/cr_components/app_management/permission_item.js';
 import '//resources/cr_elements/icons.m.js';
 
 import {AppManagementStoreClient} from '../store_client.js';
-import {getSelectedApp} from '../util.js';
+import {getSelectedApp} from 'chrome://resources/cr_components/app_management/util.js';
 import {routes} from '../../../os_route.m.js';
 import {Router} from '../../../../router.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/browser_proxy.js b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/browser_proxy.js
index 79d9b0d4..79b4d77 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/browser_proxy.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/browser_proxy.js
@@ -12,12 +12,12 @@
 import '/app-management/types.mojom-lite.js';
 import '/app-management/app_management.mojom-lite.js';
 
+import {BrowserProxy as ComponentBrowserProxy} from '//resources/cr_components/app_management/browser_proxy.js';
+import {AppType, InstallReason} from '//resources/cr_components/app_management/constants.js';
+import {PermissionType, TriState} from '//resources/cr_components/app_management/permission_constants.js';
 import {addSingletonGetter} from 'chrome://resources/js/cr.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 
-import {PermissionType, TriState} from '../permission_constants.js';
-
-import {AppType, InstallReason} from './constants.js';
 import {FakePageHandler} from './fake_page_handler.js';
 
 export class BrowserProxy {
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/chrome_app_detail_view.js b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/chrome_app_detail_view.js
index 74228c2..8024342a 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/chrome_app_detail_view.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/chrome_app_detail_view.js
@@ -3,13 +3,13 @@
 // found in the LICENSE file.
 import './more_permissions_item.js';
 import './pin_to_shelf_item.js';
-import './shared_style.js';
+import '//resources/cr_components/app_management/shared_style.js';
 
 import {afterNextRender, flush, html, Polymer, TemplateInstanceBase, Templatizer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {getSelectedApp} from 'chrome://resources/cr_components/app_management/util.js';
 
 import {BrowserProxy} from './browser_proxy.js';
 import {AppManagementStoreClient} from './store_client.js';
-import {getSelectedApp} from './util.js';
 
 Polymer({
   _template: html`{__html_template__}`,
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/fake_page_handler.js b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/fake_page_handler.js
index ae98192..cfb5bbf 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/fake_page_handler.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/fake_page_handler.js
@@ -2,13 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {AppType, OptionalBool} from '//resources/cr_components/app_management/constants.js';
+import {PermissionType, PermissionValue, TriState} from '//resources/cr_components/app_management/permission_constants.js';
+import {createBoolPermission, createTriStatePermission, getTriStatePermissionValue} from '//resources/cr_components/app_management/permission_util.js';
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {PromiseResolver} from 'chrome://resources/js/promise_resolver.m.js';
 
-import {PermissionType, PermissionValue, TriState} from '../permission_constants.js';
-import {createBoolPermission, createTriStatePermission, getTriStatePermissionValue} from '../permission_util.js';
-
-import {AppType, OptionalBool} from './constants.js';
 import {AppManagementStore} from './store.js';
 
 /**
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/main_view.js b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/main_view.js
index 0a22ee1..4940c7ec 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/main_view.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/main_view.js
@@ -3,13 +3,14 @@
 // found in the LICENSE file.
 
 import './app_item.js';
-import './shared_style.js';
+import '//resources/cr_components/app_management/shared_style.js';
 import '//resources/cr_elements/cr_icon_button/cr_icon_button.m.js';
 import '//resources/cr_elements/shared_style_css.m.js';
 
 import {assert, assertNotReached} from '//resources/js/assert.m.js';
 import {focusWithoutInk} from '//resources/js/cr/ui/focus_without_ink.m.js';
 import {afterNextRender, flush, html, Polymer, TemplateInstanceBase, Templatizer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {alphabeticalSort} from 'chrome://resources/cr_components/app_management/util.js';
 
 import {Route, Router} from '../../../router.js';
 import {routes} from '../../os_route.m.js';
@@ -17,7 +18,6 @@
 
 import {AppManagementStore} from './store.js';
 import {AppManagementStoreClient} from './store_client.js';
-import {alphabeticalSort} from './util.js';
 
 Polymer({
   _template: html`{__html_template__}`,
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/more_permissions_item.js b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/more_permissions_item.js
index 9051707..991544b4 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/more_permissions_item.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/more_permissions_item.js
@@ -2,14 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import './shared_style.js';
+import '//resources/cr_components/app_management/shared_style.js';
 import '//resources/cr_elements/cr_icon_button/cr_icon_button.m.js';
 
+import {AppManagementUserAction} from '//resources/cr_components/app_management/constants.js';
 import {afterNextRender, flush, html, Polymer, TemplateInstanceBase, Templatizer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {recordAppManagementUserAction} from 'chrome://resources/cr_components/app_management/util.js';
 
 import {BrowserProxy} from './browser_proxy.js';
-import {AppManagementUserAction} from './constants.js';
-import {recordAppManagementUserAction} from './util.js';
 
 Polymer({
   _template: html`{__html_template__}`,
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/pin_to_shelf_item.js b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/pin_to_shelf_item.js
index 1fcbb6af..5dea01fd 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/pin_to_shelf_item.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/pin_to_shelf_item.js
@@ -1,16 +1,16 @@
 // Copyright 2019 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
-import './toggle_row.js';
+import '//resources/cr_components/app_management/toggle_row.js';
 
+import {AppManagementUserAction, OptionalBool} from '//resources/cr_components/app_management/constants.js';
 import {assert, assertNotReached} from '//resources/js/assert.m.js';
 import {afterNextRender, flush, html, Polymer, TemplateInstanceBase, Templatizer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {convertOptionalBoolToBool, recordAppManagementUserAction, toggleOptionalBool} from 'chrome://resources/cr_components/app_management/util.js';
 
 import {recordClick, recordNavigation, recordPageBlur, recordPageFocus, recordSearch, recordSettingChange, setUserActionRecorderForTesting} from '../../metrics_recorder.m.js';
 
 import {BrowserProxy} from './browser_proxy.js';
-import {AppManagementUserAction, OptionalBool} from './constants.js';
-import {convertOptionalBoolToBool, recordAppManagementUserAction, toggleOptionalBool} from './util.js';
 
 Polymer({
   _template: html`{__html_template__}`,
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/plugin_vm_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/plugin_vm_page/BUILD.gn
index be8d913..4b517b6 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/plugin_vm_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/plugin_vm_page/BUILD.gn
@@ -22,12 +22,12 @@
 js_library("plugin_vm_detail_view") {
   deps = [
     ":plugin_vm_browser_proxy",
-    "../:constants",
-    "../:permission_item",
     "../:store_client",
     "../:util",
     "../../..:os_route.m",
     "../../../..:router",
+    "//ui/webui/resources/cr_components/app_management:constants",
+    "//ui/webui/resources/cr_components/app_management:permission_item",
     "//ui/webui/resources/js:assert.m",
     "//ui/webui/resources/js:load_time_data.m",
     "//ui/webui/resources/js:web_ui_listener_behavior.m",
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_detail_view.html b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_detail_view.html
index fb39c37..4993af8 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_detail_view.html
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_detail_view.html
@@ -10,18 +10,21 @@
     </div>
     <div class="permission-list indented-permission-block">
       <app-management-permission-item id="camera-permission"
+        app_="[[app_]]"
         class="subpermission-row" icon="app-management:camera"
         sync-permission-manually
         permission-label="$i18n{appManagementCameraPermissionLabel}"
         permission-type="kCamera" on-change="onPermissionChanged_">
       </app-management-permission-item>
       <app-management-permission-item id="microphone-permission"
+        app_="[[app_]]"
         class="subpermission-row" icon="app-management:microphone"
         sync-permission-manually
         permission-label="$i18n{appManagementMicrophonePermissionLabel}"
         permission-type="kMicrophone" on-change="onPermissionChanged_">
       </app-management-permission-item>
       <app-management-permission-item class="subpermission-row" icon="cr:print"
+        app_="[[app_]]"
         permission-label="$i18n{appManagementPrintingPermissionLabel}"
         permission-type="kPrinting">
       </app-management-permission-item>
@@ -64,4 +67,4 @@
       </cr-button>
     </div>
   </cr-dialog>
-</template>
\ No newline at end of file
+</template>
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_detail_view.js b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_detail_view.js
index c5414cd3..3ea51cd3 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_detail_view.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/plugin_vm_page/plugin_vm_detail_view.js
@@ -3,9 +3,9 @@
 // found in the LICENSE file.
 
 import '../icons.js';
-import '../permission_item.js';
 import '../pin_to_shelf_item.js';
-import '../shared_style.js';
+import '//resources/cr_components/app_management/permission_item.js';
+import '//resources/cr_components/app_management/shared_style.js';
 import '//resources/cr_elements/cr_icon_button/cr_icon_button.m.js';
 import '//resources/cr_elements/icons.m.js';
 
@@ -13,11 +13,11 @@
 import {loadTimeData} from '//resources/js/load_time_data.m.js';
 import {WebUIListenerBehavior} from '//resources/js/web_ui_listener_behavior.m.js';
 import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {getSelectedApp} from 'chrome://resources/cr_components/app_management/util.js';
 
 import {Router} from '../../../../router.js';
 import {routes} from '../../../os_route.m.js';
 import {AppManagementStoreClient} from '../store_client.js';
-import {getSelectedApp} from '../util.js';
 
 import {PluginVmBrowserProxyImpl} from './plugin_vm_browser_proxy.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/pwa_detail_view.html b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/pwa_detail_view.html
index 3fa5ee8..57bbb0d 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/pwa_detail_view.html
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/pwa_detail_view.html
@@ -8,6 +8,7 @@
   </app-management-pin-to-shelf-item>
   <app-management-permission-item id="notifications-card"
       class="permission-card-row separated-row"
+      app_="[[app_]]"
       permission-label="$i18n{appManagementNotificationsLabel}"
       permission-type="kNotifications">
   </app-management-permission-item>
@@ -18,16 +19,19 @@
     <div class="permission-list indented-permission-block">
       <app-management-permission-item id="location"
           class="subpermission-row" icon="app-management:location"
+          app_="[[app_]]"
           permission-label="$i18n{appManagementLocationPermissionLabel}"
           permission-type="kLocation">
       </app-management-permission-item>
       <app-management-permission-item id="camera" class="subpermission-row"
           icon="app-management:camera"
+          app_="[[app_]]"
           permission-label="$i18n{appManagementCameraPermissionLabel}"
           permission-type="kCamera">
       </app-management-permission-item>
       <app-management-permission-item id="microphone"
           class="subpermission-row" icon="app-management:microphone"
+          app_="[[app_]]"
           permission-label="$i18n{appManagementMicrophonePermissionLabel}"
           permission-type="kMicrophone">
       </app-management-permission-item>
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/pwa_detail_view.js b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/pwa_detail_view.js
index fb81312c..6ba3ce7 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/pwa_detail_view.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/pwa_detail_view.js
@@ -3,17 +3,17 @@
 // found in the LICENSE file.
 import './icons.js';
 import './more_permissions_item.js';
-import './permission_item.js';
 import './pin_to_shelf_item.js';
-import './shared_style.js';
 import './supported_links_item.js';
+import '//resources/cr_components/app_management/shared_style.js';
+import '//resources/cr_components/app_management/permission_item.js';
 import '//resources/cr_elements/icons.m.js';
 
 import {afterNextRender, flush, html, Polymer, TemplateInstanceBase, Templatizer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {getAppIcon, getSelectedApp} from 'chrome://resources/cr_components/app_management/util.js';
 
 import {BrowserProxy} from './browser_proxy.js';
 import {AppManagementStoreClient} from './store_client.js';
-import {getAppIcon, getSelectedApp} from './util.js';
 
 Polymer({
   _template: html`{__html_template__}`,
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/resize_lock_item.js b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/resize_lock_item.js
index dd704f863..5cd6a88 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/resize_lock_item.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/resize_lock_item.js
@@ -1,16 +1,16 @@
 // Copyright 2021 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
-import './toggle_row.js';
+import '//resources/cr_components/app_management/toggle_row.js';
 
+import {AppManagementUserAction} from '//resources/cr_components/app_management/constants.js';
 import {assert, assertNotReached} from '//resources/js/assert.m.js';
 import {afterNextRender, flush, html, Polymer, TemplateInstanceBase, Templatizer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {recordAppManagementUserAction} from 'chrome://resources/cr_components/app_management/util.js';
 
 import {recordClick, recordNavigation, recordPageBlur, recordPageFocus, recordSearch, recordSettingChange, setUserActionRecorderForTesting} from '../../metrics_recorder.m.js';
 
 import {BrowserProxy} from './browser_proxy.js';
-import {AppManagementUserAction} from './constants.js';
-import {recordAppManagementUserAction} from './util.js';
 
 Polymer({
   _template: html`{__html_template__}`,
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/store.js b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/store.js
index 0a862359a..544102e7 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/store.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/store.js
@@ -2,11 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {createEmptyState} from 'chrome://resources/cr_components/app_management/util.js';
 import {addSingletonGetter} from 'chrome://resources/js/cr.m.js';
 import {Store} from 'chrome://resources/js/cr/ui/store.js';
 
 import {reduceAction} from './reducers.js';
-import {createEmptyState} from './util.js';
 
 /**
  * @fileoverview A singleton datastore for the App Management page. Page state
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/supported_links_item.js b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/supported_links_item.js
index 64e3d22e..bcd15c5 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/supported_links_item.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/supported_links_item.js
@@ -8,18 +8,18 @@
 import '//resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
 import '//resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
 
+import {AppManagementUserAction, AppType} from '//resources/cr_components/app_management/constants.js';
 import {assert} from '//resources/js/assert.m.js';
 import {focusWithoutInk} from '//resources/js/cr/ui/focus_without_ink.m.js';
 import {html, Polymer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {recordAppManagementUserAction} from 'chrome://resources/cr_components/app_management/util.js';
 import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 
 import {recordSettingChange} from '../../metrics_recorder.m.js';
 
 import {BrowserProxy} from './browser_proxy.js';
-import {AppManagementUserAction, AppType} from './constants.js';
 import {AppManagementStoreClient} from './store_client.js';
-import {recordAppManagementUserAction} from './util.js';
 
 const PREFERRED_APP_PREF = 'preferred';
 
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/uninstall_button.js b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/uninstall_button.js
index e279bbf..9215794 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/uninstall_button.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/uninstall_button.js
@@ -2,19 +2,19 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import './shared_style.js';
+import '//resources/cr_components/app_management/shared_style.js';
 import '//resources/cr_elements/cr_button/cr_button.m.js';
 import '//resources/cr_elements/policy/cr_tooltip_icon.m.js';
 
+import {AppManagementUserAction, InstallReason} from '//resources/cr_components/app_management/constants.js';
 import {assert, assertNotReached} from '//resources/js/assert.m.js';
 import {afterNextRender, flush, html, Polymer, TemplateInstanceBase, Templatizer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {getSelectedApp, recordAppManagementUserAction} from 'chrome://resources/cr_components/app_management/util.js';
 
 import {recordClick, recordNavigation, recordPageBlur, recordPageFocus, recordSearch, recordSettingChange, setUserActionRecorderForTesting} from '../../metrics_recorder.m.js';
 
 import {BrowserProxy} from './browser_proxy.js';
-import {AppManagementUserAction, InstallReason} from './constants.js';
 import {AppManagementStoreClient} from './store_client.js';
-import {getSelectedApp, recordAppManagementUserAction} from './util.js';
 
 Polymer({
   _template: html`{__html_template__}`,
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/util.js b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/util.js
index 428591b..d6f7c1d 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/util.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/util.js
@@ -2,159 +2,8 @@
 // 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.m.js';
-import {assertNotReached} from 'chrome://resources/js/assert.m.js';
-import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
-
 import {Route, Router} from '../../../router.js';
 import {routes} from '../../os_route.m.js';
-import {PermissionType, PermissionValue, TriState} from '../permission_constants.js';
-import {getBoolPermissionValue, getTriStatePermissionValue, isPermissionEnabled} from '../permission_util.js';
-
-import {AppManagementUserAction, AppType, OptionalBool, WindowMode} from './constants.js';
-
-
-/**
- * @fileoverview Utility functions for the App Management page.
- */
-
-/**
- * @return {!AppManagementPageState}
- */
-export function createEmptyState() {
-  return {
-    apps: {},
-    selectedAppId: null,
-  };
-}
-
-/**
- * @param {!Array<App>} apps
- * @return {!AppManagementPageState}
- */
-export function createInitialState(apps) {
-  const initialState = createEmptyState();
-
-  for (const app of apps) {
-    initialState.apps[app.id] = app;
-  }
-
-  return initialState;
-}
-
-/**
- * @param {App} app
- * @return {string}
- */
-export function getAppIcon(app) {
-  return `chrome://app-icon/${app.id}/64`;
-}
-
-/**
- * If the given value is not in the set, returns a new set with the value
- * added, otherwise returns the old set.
- * @template T
- * @param {!Set<T>} set
- * @param {T} value
- * @return {!Set<T>}
- */
-export function addIfNeeded(set, value) {
-  if (!set.has(value)) {
-    set = new Set(set);
-    set.add(value);
-  }
-  return set;
-}
-
-/**
- * If the given value is in the set, returns a new set without the value,
- * otherwise returns the old set.
- * @template T
- * @param {!Set<T>} set
- * @param {T} value
- * @return {!Set<T>}
- */
-export function removeIfNeeded(set, value) {
-  if (set.has(value)) {
-    set = new Set(set);
-    set.delete(value);
-  }
-  return set;
-}
-
-/**
- * @param {App} app
- * @param {string} permissionType
- * @return {boolean}
- */
-export function getPermissionValueBool(app, permissionType) {
-  const permission = getPermission(app, permissionType);
-  assert(permission);
-
-  return isPermissionEnabled(permission.value);
-}
-
-/**
- * Undefined is returned when the app does not request a permission.
- *
- * @param {App} app
- * @param {string} permissionType
- * @return {Permission|undefined}
- */
-export function getPermission(app, permissionType) {
-  return app.permissions[PermissionType[permissionType]];
-}
-
-/**
- * @param {AppManagementPageState} state
- * @return {?App}
- */
-export function getSelectedApp(state) {
-  const selectedAppId = state.selectedAppId;
-  return selectedAppId ? state.apps[selectedAppId] : null;
-}
-
-/**
- * A comparator function to sort strings alphabetically.
- *
- * @param {string} a
- * @param {string} b
- */
-export function alphabeticalSort(a, b) {
-  return a.localeCompare(b);
-}
-
-/**
- * Toggles an OptionalBool
- *
- * @param {OptionalBool} bool
- * @return {OptionalBool}
- */
-export function toggleOptionalBool(bool) {
-  switch (bool) {
-    case OptionalBool.kFalse:
-      return OptionalBool.kTrue;
-    case OptionalBool.kTrue:
-      return OptionalBool.kFalse;
-    default:
-      assertNotReached();
-  }
-}
-
-/**
- * @param {OptionalBool} optionalBool
- * @returns {boolean}
- */
-export function convertOptionalBoolToBool(optionalBool) {
-  switch (optionalBool) {
-    case OptionalBool.kTrue:
-      return true;
-    case OptionalBool.kFalse:
-      return false;
-    default:
-      assertNotReached();
-  }
-}
 
 /**
  * Navigates to the App Detail page.
@@ -173,40 +22,3 @@
 export function openMainPage() {
   Router.getInstance().navigateTo(routes.APP_MANAGEMENT);
 }
-
-/**
- * @param {AppType} appType
- * @return {string}
- * @private
- */
-export function getUserActionHistogramNameForAppType_(appType) {
-  switch (appType) {
-    case AppType.kArc:
-      return 'AppManagement.AppDetailViews.ArcApp';
-    case AppType.kChromeApp:
-    case AppType.kStandaloneBrowser:
-    case AppType.kStandaloneBrowserChromeApp:
-      // TODO(https://crbug.com/1225848): Figure out appropriate behavior for
-      // Lacros-hosted chrome-apps.
-      return 'AppManagement.AppDetailViews.ChromeApp';
-    case AppType.kWeb:
-      return 'AppManagement.AppDetailViews.WebApp';
-    case AppType.kPluginVm:
-      return 'AppManagement.AppDetailViews.PluginVmApp';
-    case AppType.kBorealis:
-      return 'AppManagement.AppDetailViews.BorealisApp';
-    default:
-      assertNotReached();
-  }
-}
-
-/**
- * @param {AppType} appType
- * @param {AppManagementUserAction} userAction
- */
-export function recordAppManagementUserAction(appType, userAction) {
-  const histogram = getUserActionHistogramNameForAppType_(appType);
-  const enumLength = Object.keys(AppManagementUserAction).length;
-  chrome.metricsPrivate.recordEnumerationValue(
-      histogram, userAction, enumLength);
-}
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/BUILD.gn
index 3e27d53d..8c5df21 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/BUILD.gn
@@ -40,10 +40,10 @@
 
 js_library("app_notification_row") {
   deps = [
-    "../:permission_constants",
-    "../:permission_util",
     "../..:metrics_recorder.m",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/cr_components/app_management:permission_constants",
+    "//ui/webui/resources/cr_components/app_management:permission_util",
   ]
   externs_list = [ "$externs_path/metrics_private.js" ]
 }
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notification_row.js b/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notification_row.js
index 9e65c43..10af3c6 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notification_row.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_notifications_page/app_notification_row.js
@@ -12,10 +12,10 @@
 import '/app-management/types.mojom-lite.js';
 import '/os_apps_page/app_notification_handler.mojom-lite.js';
 
+import {createBoolPermissionValue, createTriStatePermissionValue, isBoolValue, isPermissionEnabled, isTriStateValue} from '//resources/cr_components/app_management/permission_util.js';
 import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {recordSettingChange} from '../../metrics_recorder.m.js';
-import {createBoolPermissionValue, createTriStatePermissionValue, isBoolValue, isPermissionEnabled, isTriStateValue} from '../permission_util.js';
 
 import {getAppNotificationProvider} from './mojo_interface_provider.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/os_apps_page.js b/chrome/browser/resources/settings/chromeos/os_apps_page/os_apps_page.js
index a65019b..caecdefa 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/os_apps_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/os_apps_page.js
@@ -25,6 +25,8 @@
 import './app_management_page/uninstall_button.js';
 import '../../controls/settings_dropdown_menu.js';
 
+import {AppManagementEntryPoint, AppManagementEntryPointsHistogramName} from '//resources/cr_components/app_management/constants.js';
+import {getAppIcon, getSelectedApp} from '//resources/cr_components/app_management/util.js';
 import {I18nBehavior} from '//resources/js/i18n_behavior.m.js';
 import {afterNextRender, flush, html, Polymer, TemplateInstanceBase, Templatizer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {assert, assertNotReached} from 'chrome://resources/js/assert.m.js';
@@ -37,9 +39,7 @@
 import {RouteObserverBehavior} from '../route_observer_behavior.js';
 
 import {AndroidAppsBrowserProxyImpl, AndroidAppsInfo} from './android_apps_browser_proxy.js';
-import {AppManagementEntryPoint, AppManagementEntryPointsHistogramName} from './app_management_page/constants.js';
 import {AppManagementStoreClient} from './app_management_page/store_client.js';
-import {getAppIcon, getSelectedApp} from './app_management_page/util.js';
 import {getAppNotificationProvider} from './app_notifications_page/mojo_interface_provider.js';
 
 /**
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.js b/chrome/browser/resources/settings/chromeos/os_settings.js
index 92743b9..ed985af 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.js
+++ b/chrome/browser/resources/settings/chromeos/os_settings.js
@@ -56,16 +56,15 @@
 import './os_apps_page/app_management_page/dom_switch.js';
 import './os_apps_page/app_management_page/icons.js';
 import './os_apps_page/app_management_page/main_view.js';
-import './os_apps_page/app_management_page/permission_item.js';
 import './os_apps_page/app_management_page/pin_to_shelf_item.js';
 import './os_apps_page/app_management_page/plugin_vm_page/plugin_vm_detail_view.js';
 import './os_apps_page/app_management_page/pwa_detail_view.js';
-import './os_apps_page/app_management_page/shared_style.js';
-import './os_apps_page/app_management_page/shared_vars.js';
+import '//resources/cr_components/app_management/shared_style.js';
+import '//resources/cr_components/app_management/shared_vars.js';
 import './os_apps_page/app_management_page/supported_links_overlapping_apps_dialog.js';
 import './os_apps_page/app_management_page/supported_links_dialog.js';
 import './os_apps_page/app_management_page/supported_links_item.js';
-import './os_apps_page/app_management_page/toggle_row.js';
+import '//resources/cr_components/app_management/toggle_row.js';
 import './os_apps_page/app_management_page/uninstall_button.js';
 import './os_apps_page/app_notifications_page/mojo_interface_provider.js';
 import './os_apps_page/os_apps_page.js';
@@ -94,6 +93,11 @@
 import './parental_controls_page/parental_controls_page.js';
 import './settings_scheduler_slider/settings_scheduler_slider.js';
 
+export {BrowserProxy as AppManagementComponentBrowserProxy} from '//resources/cr_components/app_management/browser_proxy.js';
+export {PageType, WindowMode} from '//resources/cr_components/app_management/constants.js';
+export {PermissionType, TriState} from '//resources/cr_components/app_management/permission_constants.js';
+export {createBoolPermission, createTriStatePermission, getBoolPermissionValue, isBoolValue} from '//resources/cr_components/app_management/permission_util.js';
+export {convertOptionalBoolToBool, createEmptyState, createInitialState, getPermissionValueBool} from '//resources/cr_components/app_management/util.js';
 export {LifetimeBrowserProxyImpl} from '../lifetime_browser_proxy.js';
 export {ProfileInfoBrowserProxyImpl} from '../people_page/profile_info_browser_proxy.js';
 export {PageStatus, StatusAction, SyncBrowserProxyImpl} from '../people_page/sync_browser_proxy.js';
@@ -129,16 +133,12 @@
 export {AndroidAppsBrowserProxyImpl} from './os_apps_page/android_apps_browser_proxy.js';
 export {addApp, changeApp, removeApp, updateSelectedAppId} from './os_apps_page/app_management_page/actions.js';
 export {BrowserProxy} from './os_apps_page/app_management_page/browser_proxy.js';
-export {PageType, WindowMode} from './os_apps_page/app_management_page/constants.js';
 export {FakePageHandler} from './os_apps_page/app_management_page/fake_page_handler.js';
 export {PluginVmBrowserProxyImpl} from './os_apps_page/app_management_page/plugin_vm_page/plugin_vm_browser_proxy.js';
 export {AppState, reduceAction} from './os_apps_page/app_management_page/reducers.js';
 export {AppManagementStore} from './os_apps_page/app_management_page/store.js';
 export {AppManagementStoreClientImpl} from './os_apps_page/app_management_page/store_client.js';
-export {convertOptionalBoolToBool, createEmptyState, createInitialState, getPermissionValueBool} from './os_apps_page/app_management_page/util.js';
 export {setAppNotificationProviderForTesting} from './os_apps_page/app_notifications_page/mojo_interface_provider.js';
-export {PermissionType, TriState} from './os_apps_page/permission_constants.js';
-export {createBoolPermission, createTriStatePermission, getBoolPermissionValue, isBoolValue} from './os_apps_page/permission_util.js';
 export {osPageVisibility} from './os_page_visibility.m.js';
 export {AccountManagerBrowserProxy, AccountManagerBrowserProxyImpl} from './os_people_page/account_manager_browser_proxy.js';
 export {FingerprintBrowserProxyImpl, FingerprintResultType} from './os_people_page/fingerprint_browser_proxy.m.js';
diff --git a/chrome/browser/resources/tab_strip/alert_indicators.ts b/chrome/browser/resources/tab_strip/alert_indicators.ts
index 5df1ad3..e930c00 100644
--- a/chrome/browser/resources/tab_strip/alert_indicators.ts
+++ b/chrome/browser/resources/tab_strip/alert_indicators.ts
@@ -20,7 +20,7 @@
   constructor() {
     super();
 
-    this.containerEl_ = this.$('#container') as HTMLElement;
+    this.containerEl_ = this.$<HTMLElement>('#container')!;
 
     const audioIndicator = new AlertIndicatorElement();
     const recordingIndicator = new AlertIndicatorElement();
diff --git a/chrome/browser/resources/tab_strip/tab.ts b/chrome/browser/resources/tab_strip/tab.ts
index 7c6f7bd..ffeb672 100644
--- a/chrome/browser/resources/tab_strip/tab.ts
+++ b/chrome/browser/resources/tab_strip/tab.ts
@@ -61,29 +61,29 @@
     super();
 
     this.alertIndicatorsEl_ =
-        this.$('tabstrip-alert-indicators') as AlertIndicatorsElement;
-    // Normally, custom elements will get upgraded automatically once added to
-    // the DOM, but TabElement may need to update properties on
+        this.$<AlertIndicatorsElement>('tabstrip-alert-indicators')!;
+    // Normally, custom elements will get upgraded automatically once added
+    // to the DOM, but TabElement may need to update properties on
     // AlertIndicatorElement before this happens, so upgrade it manually.
     customElements.upgrade(this.alertIndicatorsEl_);
 
-    this.closeButtonEl_ = this.$('#close') as HTMLElement;
+    this.closeButtonEl_ = this.$<HTMLElement>('#close')!;
     this.closeButtonEl_.setAttribute(
         'aria-label', loadTimeData.getString('closeTab'));
 
-    this.dragImageEl_ = this.$('#dragImage') as HTMLElement;
+    this.dragImageEl_ = this.$<HTMLElement>('#dragImage')!;
 
-    this.tabEl_ = this.$('#tab') as HTMLElement;
+    this.tabEl_ = this.$<HTMLElement>('#tab')!;
 
-    this.faviconEl_ = this.$('#favicon') as HTMLElement;
+    this.faviconEl_ = this.$<HTMLElement>('#favicon')!;
 
-    this.thumbnailContainer_ = this.$('#thumbnail') as HTMLElement;
+    this.thumbnailContainer_ = this.$<HTMLElement>('#thumbnail')!;
 
-    this.thumbnail_ = this.$('#thumbnailImg') as HTMLImageElement;
+    this.thumbnail_ = this.$<HTMLImageElement>('#thumbnailImg')!;
 
     this.tabsApi_ = TabsApiProxyImpl.getInstance();
 
-    this.titleTextEl_ = this.$('#titleText') as HTMLElement;
+    this.titleTextEl_ = this.$<HTMLElement>('#titleText')!;
 
     /**
      * Flag indicating if this TabElement can accept dragover events. This
diff --git a/chrome/browser/resources/tab_strip/tab_group.ts b/chrome/browser/resources/tab_strip/tab_group.ts
index bb0be405..c2a93140 100644
--- a/chrome/browser/resources/tab_strip/tab_group.ts
+++ b/chrome/browser/resources/tab_strip/tab_group.ts
@@ -22,7 +22,7 @@
 
     this.tabsApi_ = TabsApiProxyImpl.getInstance();
 
-    this.chip_ = this.$('#chip') as HTMLElement;
+    this.chip_ = this.$<HTMLElement>('#chip')!;
     this.chip_.addEventListener('click', () => this.onClickChip_());
     this.chip_.addEventListener(
         'keydown', e => this.onKeydownChip_(/** @type {!KeyboardEvent} */ (e)));
@@ -43,13 +43,13 @@
   }
 
   getDragImage(): HTMLElement {
-    return this.$('#dragImage') as HTMLElement;
+    return this.$<HTMLElement>('#dragImage')!;
   }
 
   getDragImageCenter(): HTMLElement {
     // Since the drag handle is #dragHandle, the drag image should be
     // centered relatively to it.
-    return this.$('#dragHandle') as HTMLElement;
+    return this.$<HTMLElement>('#dragHandle')!;
   }
 
   private onClickChip_() {
@@ -96,7 +96,7 @@
   }
 
   updateVisuals(visualData: TabGroupVisualData) {
-    (this.$('#title') as HTMLElement).innerText = visualData.title;
+    this.$<HTMLElement>('#title')!.innerText = visualData.title;
     this.style.setProperty('--tabstrip-tab-group-color-rgb', visualData.color);
     this.style.setProperty(
         '--tabstrip-tab-group-text-color-rgb', visualData.textColor);
diff --git a/chrome/browser/resources/tab_strip/tab_list.ts b/chrome/browser/resources/tab_strip/tab_list.ts
index 5dee171..8487eb9 100644
--- a/chrome/browser/resources/tab_strip/tab_list.ts
+++ b/chrome/browser/resources/tab_strip/tab_list.ts
@@ -388,13 +388,12 @@
   }
 
   private findTabElement_(tabId: number): TabElement|null {
-    return this.$(`tabstrip-tab[data-tab-id="${tabId}"]`) as TabElement | null;
+    return this.$<TabElement>(`tabstrip-tab[data-tab-id="${tabId}"]`);
   }
 
   private findTabGroupElement_(groupId: string): TabGroupElement|null {
-    return this.$(`tabstrip-tab-group[data-group-id="${groupId}"]`) as
-        TabGroupElement |
-        null;
+    return this.$<TabGroupElement>(
+        `tabstrip-tab-group[data-group-id="${groupId}"]`);
   }
 
   private fetchAndUpdateColors_() {
@@ -403,8 +402,7 @@
   }
 
   private fetchAndUpdateGroupData_() {
-    const tabGroupElements =
-        this.$all('tabstrip-tab-group') as NodeListOf<TabGroupElement>;
+    const tabGroupElements = this.$all<TabGroupElement>('tabstrip-tab-group');
     this.tabsApi_.getGroupVisualData().then(({data}) => {
       tabGroupElements.forEach(tabGroupElement => {
         tabGroupElement.updateVisuals(
@@ -420,7 +418,7 @@
   }
 
   private getActiveTab_(): TabElement|null {
-    return this.$('tabstrip-tab[active]') as TabElement | null;
+    return this.$<TabElement>('tabstrip-tab[active]');
   }
 
   getIndexOfTab(tabElement: TabElement): number {
@@ -473,7 +471,7 @@
     // document. When the tab strip first gains keyboard focus, no such event
     // exists yet, so the outline needs to be explicitly set to visible.
     this.focusOutlineManager_.visible = true;
-    (this.$('tabstrip-tab') as HTMLElement).focus();
+    this.$<TabElement>('tabstrip-tab')!.focus();
   }
 
   private onTabActivated_(tabId: number) {
@@ -488,7 +486,7 @@
     // have updated a Tab to have an active state. For example, if a
     // tab is created with an already active state, there may be 2 active
     // TabElements: the newly created tab and the previously active tab.
-    (this.$all('tabstrip-tab[active]') as NodeListOf<TabElement>)
+    this.$all<TabElement>('tabstrip-tab[active]')
         .forEach((previouslyActiveTab) => {
           if (previouslyActiveTab.tab.id !== tabId) {
             previouslyActiveTab.tab = /** @type {!Tab} */ (
@@ -705,7 +703,7 @@
       index++;
     }
 
-    let elementAtIndex = this.$all('tabstrip-tab')[index];
+    let elementAtIndex = this.$all('tabstrip-tab')[index]!;
     if (elementAtIndex && elementAtIndex.parentElement &&
         isTabGroupElement(elementAtIndex.parentElement)) {
       elementAtIndex = elementAtIndex.parentElement;
@@ -796,7 +794,8 @@
           element, this.pinnedTabsElement_.childNodes[index]!);
     } else {
       let elementToInsert: TabElement|TabGroupElement = element;
-      let elementAtIndex = this.$all('tabstrip-tab').item(index);
+      let elementAtIndex: TabElement|TabGroupElement =
+          this.$all<TabElement>('tabstrip-tab').item(index);
       let parentElement = this.unpinnedTabsElement_;
 
       if (groupId) {
@@ -824,7 +823,7 @@
         // TabElement is being sandwiched between two TabElements in a group, it
         // can be assumed that the tab will eventually be inserted into the
         // group as well.
-        elementAtIndex = elementAtIndex.parentElement;
+        elementAtIndex = elementAtIndex.parentElement as TabGroupElement;
       }
 
       if (elementAtIndex && elementAtIndex.parentElement === parentElement) {
diff --git a/chrome/browser/resources/tools/rollup_plugin.js b/chrome/browser/resources/tools/rollup_plugin.js
index 1717bb4..345d743 100644
--- a/chrome/browser/resources/tools/rollup_plugin.js
+++ b/chrome/browser/resources/tools/rollup_plugin.js
@@ -62,6 +62,7 @@
   }
 
   const fullUrl = new URL(pathFromUrl, urlPrefix);
+  console.log(fullUrl.href);
   if (excludes.includes(fullUrl.href)) {
     return fullUrl.href;
   }
diff --git a/chrome/browser/resources/vr/OWNERS b/chrome/browser/resources/vr/OWNERS
index 3bdb0484..da26124 100644
--- a/chrome/browser/resources/vr/OWNERS
+++ b/chrome/browser/resources/vr/OWNERS
@@ -1,3 +1,2 @@
-alcooper@chromium.org
-bialpio@chromium.org
+file://components/webxr/OWNERS
 tiborg@chromium.org
diff --git a/chrome/browser/segmentation_platform/segmentation_platform_profile_observer_unittest.cc b/chrome/browser/segmentation_platform/segmentation_platform_profile_observer_unittest.cc
index 42529cf..7f5c039 100644
--- a/chrome/browser/segmentation_platform/segmentation_platform_profile_observer_unittest.cc
+++ b/chrome/browser/segmentation_platform/segmentation_platform_profile_observer_unittest.cc
@@ -8,6 +8,7 @@
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chrome/test/base/testing_profile_manager.h"
+#include "components/segmentation_platform/public/segment_selection_result.h"
 #include "components/segmentation_platform/public/segmentation_platform_service.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -29,6 +30,9 @@
   MOCK_METHOD(void,
               GetSelectedSegment,
               (const std::string&, SegmentSelectionCallback));
+  MOCK_METHOD(SegmentSelectionResult,
+              GetCachedSegmentResult,
+              (const std::string&));
   MOCK_METHOD(void, EnableMetrics, (bool));
   MOCK_METHOD(void, GetServiceStatus, ());
 };
diff --git a/chrome/browser/ssl/ssl_browsertest.cc b/chrome/browser/ssl/ssl_browsertest.cc
index 47d5810..ea8b348 100644
--- a/chrome/browser/ssl/ssl_browsertest.cc
+++ b/chrome/browser/ssl/ssl_browsertest.cc
@@ -4099,15 +4099,7 @@
 // --ignore-certificate-errors-spki-list. The connection should be established
 // without interstitial page showing.
 #if !BUILDFLAG(IS_CHROMEOS_ASH)  // Chrome OS does not support the flag.
-// TODO(crbug.com/1176880): Disabled on macOS because the WSS SpawnedTestServer
-// does not support modern TLS on the macOS bots.
-#if defined(OS_MAC)
-#define MAYBE_TestWSSExpired DISABLED_TestWSSExpired
-#else
-#define MAYBE_TestWSSExpired TestWSSExpired
-#endif
-IN_PROC_BROWSER_TEST_F(SSLUITestIgnoreCertErrorsBySPKIWSS,
-                       MAYBE_TestWSSExpired) {
+IN_PROC_BROWSER_TEST_F(SSLUITestIgnoreCertErrorsBySPKIWSS, TestWSSExpired) {
   ASSERT_TRUE(wss_server_expired_.Start());
 
   // Setup page title observer.
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index e621e553..1247896 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1705,7 +1705,6 @@
       "//chrome/browser/ui/color:color_headers",
       "//chrome/browser/ui/commander:fuzzy_finder",
       "//chrome/browser/ui/webui/access_code_cast:mojo_bindings",
-      "//chrome/browser/ui/webui/app_management:mojo_bindings",
       "//chrome/browser/ui/webui/app_service_internals:mojo_bindings",
       "//chrome/browser/ui/webui/image_editor:mojo_bindings",
       "//chrome/browser/ui/webui/internals/user_education:mojo_bindings",
@@ -1752,6 +1751,7 @@
       "//ui/base/dragdrop:types",
       "//ui/base/dragdrop/mojom",
       "//ui/events",
+      "//ui/webui/resources/cr_components/app_management:mojo_bindings",
       "//ui/webui/resources/js/browser_command:mojo_bindings",
     ]
     public_deps += [
@@ -2967,7 +2967,6 @@
       "//chrome/browser/ui/app_list/search/search_result_ranker:app_launch_predictor_proto",
       "//chrome/browser/ui/app_list/search/search_result_ranker:recurrence_ranker_proto",
       "//chrome/browser/ui/app_list/search/util:proto",
-      "//chrome/browser/ui/webui/app_management:mojo_bindings",
       "//chrome/browser/ui/webui/chromeos/add_supervision:mojo_bindings",
       "//chrome/browser/ui/webui/chromeos/audio:mojo_bindings",
       "//chrome/browser/ui/webui/chromeos/crostini_upgrader:mojo_bindings",
@@ -3083,6 +3082,7 @@
       "//ui/events/ozone/layout:layout",
       "//ui/file_manager:file_manager",
       "//ui/ozone",
+      "//ui/webui/resources/cr_components/app_management:mojo_bindings",
     ]
     public_deps += [
       "//chrome/browser/ui/webui/chromeos/crostini_installer:mojo_bindings",
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/AutocompleteEditTextTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/AutocompleteEditTextTest.java
index 854e45f..4b7a5558 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/AutocompleteEditTextTest.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/AutocompleteEditTextTest.java
@@ -20,6 +20,7 @@
 import android.view.inputmethod.BaseInputConnection;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
+import android.widget.EditText;
 import android.widget.LinearLayout;
 
 import org.junit.Before;
@@ -74,6 +75,67 @@
     private ShadowAccessibilityManager mShadowAccessibilityManager;
     private boolean mIsShown;
 
+    /**
+     * A flag to tweak test expectations to deal with an OS bug.
+     *
+     * <p>{@code EditableInputConnection}, which {@link AutocompleteEditText} internally rely on,
+     * has had <a href="https://issuetracker.google.com/issues/209958658">a bug</a> that it still
+     * returns {@code true} from {@link InputConnection#endBatchEdit()} when its internal batch edit
+     * count becomes {@code 0} as a result of invocation, which clearly conflicted with the spec.
+     * There are several tests in this file that are unfortunately affected by this bug.  In order
+     * to abstract out such an OS issue from the actual test expectations, we will dynamically test
+     * if the bug still exists or not in the test execution environment or not, and set {@code true}
+     * to this flag if it is still there.</p>
+     *
+     * <p>Until a new version of Android OS with
+     * <a href="https://android-review.googlesource.com/c/platform/frameworks/base/+/1923058">the
+     * fix</a> and a corresponding version of Robolectric become available in Chromium, this flag is
+     * expected to be always {@code true}. Once they become available, you can either remove this
+     * flag with assuming it's always {@code false} or test two different OS behaviors at the same
+     * time by <a href="http://robolectric.org/configuring/">specifying multiple SDK versions</a> to
+     * the test runner</a>.</p>
+     *
+     * @see #testEditableInputConnectionEndBatchEditBug(Context)
+     * @see #assertLastBatchEdit(boolean)
+     */
+    private boolean mHasEditableInputConnectionEndBatchEditBug;
+
+    /**
+     * Test if {@code EditableInputConnection} has a bug that it still returns {@code true} from
+     * {@link InputConnection#endBatchEdit()} when its internal batch edit count becomes {@code 0}
+     * as a result of invocation.
+     *
+     * <p>See https://issuetracker.google.com/issues/209958658 for details.</p>
+     *
+     * @param context The {@link Context} to be used to initialize {@link EditText}.
+     * @return {@code true} if the bug still exists. {@code false} otherwise.
+     */
+    private static boolean testEditableInputConnectionEndBatchEditBug(Context context) {
+        EditText editText = new EditText(context);
+        EditorInfo editorInfo = new EditorInfo();
+        InputConnection editableInputConnection = editText.onCreateInputConnection(editorInfo);
+        editableInputConnection.beginBatchEdit();
+        // If this returns true, yes, the bug is still there!
+        return editableInputConnection.endBatchEdit();
+    }
+
+    /**
+     * A convenient helper method to assert the return value of
+     * {@link InputConnection#endBatchEdit()} when its internal batch edit count becomes {@code 0}.
+     *
+     * @param result The return value of {@link InputConnection#endBatchEdit()}.
+     *
+     * @see #mHasEditableInputConnectionEndBatchEditBug
+     * @see #testEditableInputConnectionEndBatchEditBug(Context)
+     */
+    private void assertLastBatchEdit(boolean result) {
+        if (mHasEditableInputConnectionEndBatchEditBug) {
+            assertTrue(result);
+        } else {
+            assertFalse(result);
+        }
+    }
+
     // Limits the target of InOrder#verify.
     private static class Verifier {
         public void onAutocompleteTextStateChanged(boolean updateDisplay) {
@@ -176,6 +238,9 @@
         MockitoAnnotations.initMocks(this);
         mContext = RuntimeEnvironment.application;
 
+        mHasEditableInputConnectionEndBatchEditBug =
+                testEditableInputConnectionEndBatchEditBug(mContext);
+
         mVerifier = spy(new Verifier());
         mAutocomplete = new TestAutocompleteEditText(mContext, null);
         mFocusPlaceHolder = new LinearLayout(mContext);
@@ -388,7 +453,7 @@
             assertVerifierCallCounts(0, 2);
         }
         mInOrder.verifyNoMoreInteractions();
-        assertTrue(mInputConnection.endBatchEdit());
+        assertLastBatchEdit(mInputConnection.endBatchEdit());
 
         // Autocomplete text gets redrawn.
         assertTexts("hello ", "world");
@@ -533,7 +598,7 @@
         }
         mInOrder.verifyNoMoreInteractions();
 
-        assertTrue(mInputConnection.endBatchEdit());
+        assertLastBatchEdit(mInputConnection.endBatchEdit());
 
         if (isUsingSpannableModel()) {
             mInOrder.verify(mVerifier).onUpdateSelection(6, 6);
@@ -905,7 +970,7 @@
         mInOrder.verifyNoMoreInteractions();
         assertVerifierCallCounts(0, 0);
 
-        assertTrue(mInputConnection.endBatchEdit());
+        assertLastBatchEdit(mInputConnection.endBatchEdit());
         mInOrder.verify(mVerifier).onUpdateSelection(4, 4);
         mInOrder.verify(mVerifier).onUpdateSelection(5, 5);
         verifyOnPopulateAccessibilityEvent(
diff --git a/chrome/browser/ui/android/quickactionsearchwidget/java/res/layout/quick_action_search_widget_dino_layout.xml b/chrome/browser/ui/android/quickactionsearchwidget/java/res/layout/quick_action_search_widget_dino_layout.xml
index f62165f5..27575c0 100644
--- a/chrome/browser/ui/android/quickactionsearchwidget/java/res/layout/quick_action_search_widget_dino_layout.xml
+++ b/chrome/browser/ui/android/quickactionsearchwidget/java/res/layout/quick_action_search_widget_dino_layout.xml
@@ -3,29 +3,49 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/dino_quick_action_button"
-    android:layout_width="@dimen/quick_action_search_widget_dino_size"
-    android:layout_height="@dimen/quick_action_search_widget_dino_size"
-    android:background="@drawable/quick_action_search_widget_dino_background"
-    android:orientation="vertical">
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/dino_quick_action_area"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    tools:ignore="UselessParent">
 
-    <ImageView
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/quick_action_search_widget_dino_margin_start"
-        android:layout_marginTop="@dimen/quick_action_search_widget_dino_margin_top"
-        android:layout_marginBottom="@dimen/quick_action_search_widget_dino_margin_text"
-        android:importantForAccessibility="no"
-        android:src="@drawable/quick_action_search_widget_dino_content" />
+  <!-- Note that the parent layout is not "useless". We use it to enforce a particular
+    aspect ratio of the Dino widget. There are very few layouts that are permitted to be
+    used with RemoteViews, ConstraintLayout and is sadly not one of them.
+    Please check the QuickActionSearchWidgetProviderDelegate to see how the sizes are
+    enforced for this widget. -->
 
-    <TextView
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/quick_action_search_widget_dino_margin_start"
-        android:maxLines="1"
-        android:textAppearance="@style/TextAppearance.TextLarge.Primary.Baseline"
-        android:textAlignment="viewStart"
-        android:text="@string/dino_widget_text" />
+  <LinearLayout
+      android:id="@+id/dino_quick_action_button"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"
+      android:background="@drawable/quick_action_search_widget_dino_background"
+      android:paddingTop="@dimen/quick_action_search_widget_dino_padding_vertical"
+      android:paddingBottom="@dimen/quick_action_search_widget_dino_padding_vertical"
+      android:paddingStart="@dimen/quick_action_search_widget_dino_padding_start"
+      android:gravity="fill"
+      android:orientation="vertical">
 
-</LinearLayout>
+      <ImageView
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content"
+          android:importantForAccessibility="no"
+          android:scaleType="fitCenter"
+          android:adjustViewBounds="true"
+          android:src="@drawable/quick_action_search_widget_dino_content" />
+
+      <TextView
+          android:id="@+id/dino_quick_action_text"
+          android:layout_width="match_parent"
+          android:layout_height="0dp"
+          android:layout_weight="1"
+          android:maxLines="1"
+          android:textAppearance="@style/TextAppearance.TextLarge.Primary.Baseline"
+          android:textAlignment="viewStart"
+          android:gravity="bottom"
+          android:text="@string/dino_widget_text" />
+
+  </LinearLayout>
+</FrameLayout>
diff --git a/chrome/browser/ui/android/quickactionsearchwidget/java/res/values/dimens.xml b/chrome/browser/ui/android/quickactionsearchwidget/java/res/values/dimens.xml
index dd9e6b9..c4522f0 100644
--- a/chrome/browser/ui/android/quickactionsearchwidget/java/res/values/dimens.xml
+++ b/chrome/browser/ui/android/quickactionsearchwidget/java/res/values/dimens.xml
@@ -73,10 +73,10 @@
     <dimen name="quick_action_search_widget_xsmall_search_bar_text_size">11sp</dimen>
 
     <!-- Dino Widget Dimensions -->
-    <dimen name="quick_action_search_widget_dino_min_size">136dp</dimen>
+    <dimen name="quick_action_search_widget_dino_min_size">110dp</dimen>
     <dimen name="quick_action_search_widget_dino_size">136dp</dimen>
+    <dimen name="quick_action_search_widget_dino_text_size">13.5dp</dimen>
     <dimen name="quick_action_search_widget_dino_corner_radius">20dp</dimen>
-    <dimen name="quick_action_search_widget_dino_margin_start">12dp</dimen>
-    <dimen name="quick_action_search_widget_dino_margin_top">6dp</dimen>
-    <dimen name="quick_action_search_widget_dino_margin_text">10dp</dimen>
+    <dimen name="quick_action_search_widget_dino_padding_start">12dp</dimen>
+    <dimen name="quick_action_search_widget_dino_padding_vertical">18dp</dimen>
 </resources>
diff --git a/chrome/browser/ui/android/quickactionsearchwidget/java/src/org/chromium/chrome/browser/ui/quickactionsearchwidget/QuickActionSearchWidgetProviderDelegate.java b/chrome/browser/ui/android/quickactionsearchwidget/java/src/org/chromium/chrome/browser/ui/quickactionsearchwidget/QuickActionSearchWidgetProviderDelegate.java
index bcd8d7b..e8120ce7 100644
--- a/chrome/browser/ui/android/quickactionsearchwidget/java/src/org/chromium/chrome/browser/ui/quickactionsearchwidget/QuickActionSearchWidgetProviderDelegate.java
+++ b/chrome/browser/ui/android/quickactionsearchwidget/java/src/org/chromium/chrome/browser/ui/quickactionsearchwidget/QuickActionSearchWidgetProviderDelegate.java
@@ -9,6 +9,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
+import android.util.Size;
 import android.view.View;
 import android.widget.RemoteViews;
 
@@ -66,9 +67,9 @@
         @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
         static int getElementSizeInDP(
                 Resources res, @DimenRes int mainDimenRes, @DimenRes int marginDimenRes) {
-            float density = res.getDisplayMetrics().density;
-            assert mainDimenRes != 0;
+            if (mainDimenRes == 0) return 0;
 
+            float density = res.getDisplayMetrics().density;
             float size = res.getDimension(mainDimenRes);
             if (marginDimenRes != 0) {
                 size += 2 * res.getDimension(marginDimenRes);
@@ -177,6 +178,8 @@
     private final @NonNull WidgetVariant mSmallWidgetVariant;
     /** Widget variant describing the Extra Small widget. */
     private final @NonNull WidgetVariant mExtraSmallWidgetVariant;
+    /** Widget variant describing the Dino widget. */
+    private final @NonNull WidgetVariant mDinoWidgetVariant;
 
     /**
      * @param context Context that can be used to pre-compute values. Do not cache.
@@ -213,6 +216,10 @@
                         R.dimen.quick_action_search_widget_xsmall_height,
                         R.dimen.quick_action_search_widget_xsmall_button_width,
                         R.dimen.quick_action_search_widget_xsmall_button_horizontal_margin);
+        mDinoWidgetVariant =
+                new WidgetVariant(context, R.layout.quick_action_search_widget_dino_layout,
+                        R.dimen.quick_action_search_widget_dino_size,
+                        R.dimen.quick_action_search_widget_dino_size, 0, 0);
     }
 
     /**
@@ -253,15 +260,132 @@
     }
 
     /**
+     * Given the width and height of the widget cell area (expressed in distance points) and the
+     * screen density, compute vertical and horizontal paddings that have to be applied to make
+     * the widget retain the square aspect ratio (1:1).
+     * The returned size is expressed in pixels.
+     *
+     * @param cellAreaWidthDp Width of the cell area, expressed in distance points.
+     * @param cellAreaHeightDp Height of the cell area, expressed in distance points.
+     * @param density Screen density.
+     * @return Size object, describing required horizontal and vertical padding, expressed in
+     *         pixels.
+     */
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    Size computeWidgetAreaPaddingForDinoWidgetPx(
+            int cellAreaWidthDp, int cellAreaHeightDp, float density) {
+        int edgeLengthDp = Math.min(cellAreaWidthDp, cellAreaHeightDp);
+        int width = (int) (((cellAreaWidthDp - edgeLengthDp) / 2.f) * density);
+        int height = (int) (((cellAreaHeightDp - edgeLengthDp) / 2.f) * density);
+        return new Size((int) width, (int) height);
+    }
+
+    /**
+     * Given the width and height of the cell area (expressed in distance points) compute the scale
+     * factor (expressed as a float value) that needs to be applied to relevant dimensions to scale
+     * the widget proportionally.
+     *
+     * @param cellAreaWidthDp Width of the cell area, expressed in distance points.
+     * @param cellAreaHeightDp Height of the cell area, expressed in distance points.
+     * @param density Screen density.
+     * @return Scale factor that should be applied to relevant dimensions to resize the widget
+     *         proportionately.
+     */
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    float computeScaleFactorForDinoWidget(int cellAreaWidthDp, int cellAreaHeightDp) {
+        // Compute the paddings to better visually arrange the views inside the widget.
+        // First, compute the scale factor. The scale factor is based on the reference dimensions
+        // (ie. dimensions from the UX mocks) vs the on-screen area size (which almost certainly
+        // will be different than the reference).
+        // The scale factor reflects how much larger (or smaller) the cell area size is compared
+        // to the mocks, ie. scale == 1.2 means that edgeSize is 20% larger than the reference size.
+        final int edgeSize = Math.min(cellAreaWidthDp, cellAreaHeightDp);
+        return 1.f * edgeSize / mDinoWidgetVariant.widgetWidthDp;
+    }
+
+    /**
+     * @param resources Current resources.
+     * @return Whether widget layout direction is RTL.
+     */
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    boolean isLayoutDirectionRTL(@NonNull Resources resources) {
+        return resources.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+    }
+
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    void resizeDinoWidgetToFillTargetCellArea(
+            @NonNull Resources resources, RemoteViews views, int areaWidthDp, int areaHeightDp) {
+        float density = resources.getDisplayMetrics().density;
+
+        // Screen density is used to compute padding in each direction.
+        // The left/right and top/bottom dimensions are the same, since we want to center the view
+        // in the area where we have some non-zero padding.
+        Size paddings = computeWidgetAreaPaddingForDinoWidgetPx(areaWidthDp, areaHeightDp, density);
+        views.setViewPadding(R.id.dino_quick_action_area, paddings.getWidth(), paddings.getHeight(),
+                paddings.getWidth(), paddings.getHeight());
+
+        // Now use the scale factor to modify all the dimensions that count.
+        // We depend on the intrinsic view computations to infer a lot of these dimensions; the ones
+        // below have to be computed by us.
+        //
+        // The core dimensions that have to be scaled by us are the paddings around the core widget
+        // area (where the image is hosted), to make sure the content is properly adjusted.
+        final float scale = computeScaleFactorForDinoWidget(areaWidthDp, areaHeightDp);
+        final float contentPaddingVertical =
+                resources.getDimension(R.dimen.quick_action_search_widget_dino_padding_vertical)
+                * scale;
+        final float contentPaddingStart =
+                resources.getDimension(R.dimen.quick_action_search_widget_dino_padding_start)
+                * scale;
+
+        // Note: there is no setViewRelativePadding method available for RemoteViews. This means
+        // we have to be smart about what edge means "start".
+        final float contentPaddingLeft = isLayoutDirectionRTL(resources) ? 0 : contentPaddingStart;
+        final float contentPaddingRight = isLayoutDirectionRTL(resources) ? contentPaddingStart : 0;
+
+        views.setViewPadding(R.id.dino_quick_action_button, (int) contentPaddingLeft,
+                (int) contentPaddingVertical, (int) contentPaddingRight,
+                (int) contentPaddingVertical);
+
+        // Scale text proportionately, ignoring the system font scaling. We have to apply this to
+        // avoid cases where the system font setting leads to either:
+        // - text truncation,
+        // - overlapping the dino image with text, or
+        // - making the text so small that it leaves a lot of empty space on the widget.
+        final float textSize =
+                resources.getDimension(R.dimen.quick_action_search_widget_dino_text_size) * scale
+                / resources.getDisplayMetrics().scaledDensity;
+        views.setFloat(R.id.dino_quick_action_text, "setTextSize", textSize);
+    }
+
+    /**
      * Create {@link RemoteViews} for the Dino widget.
      *
      * @param context Current context.
      * @param prefs Structure describing current preferences and feature availability.
+     * @param portraitModeWidthDp Width of the widget area in portrait mode.
+     * @param portraitModeHeightDp Height of the widget area in portrait mode.
+     * @param landscapeModeWidthDp Width of the widget area in landscape mode.
+     * @param landscapeModeHeightDp Height of the widget area in landscape mode.
      * @return RemoteViews to be installed on the Dino widget.
      */
-    public @NonNull RemoteViews createDinoWidgetRemoteViews(
-            @NonNull Context context, @NonNull SearchActivityPreferences prefs) {
-        return createWidgetRemoteViews(context, R.layout.quick_action_search_widget_dino_layout);
+    public @NonNull RemoteViews createDinoWidgetRemoteViews(@NonNull Context context,
+            @NonNull SearchActivityPreferences prefs, int portraitModeWidthDp,
+            int portraitModeHeightDp, int landscapeModeWidthDp, int landscapeModeHeightDp) {
+        RemoteViews landscapeViews =
+                createWidgetRemoteViews(context, R.layout.quick_action_search_widget_dino_layout);
+        RemoteViews portraitViews =
+                createWidgetRemoteViews(context, R.layout.quick_action_search_widget_dino_layout);
+
+        // Dino widget is specific; we want to scale up a lot of dimensions based on the actual size
+        // of the widget area. This makes layout a lot more responsive but also a lot more
+        // complicated since we have to compute everything manually.
+        Resources res = context.getApplicationContext().getResources();
+        resizeDinoWidgetToFillTargetCellArea(
+                res, landscapeViews, landscapeModeWidthDp, landscapeModeHeightDp);
+        resizeDinoWidgetToFillTargetCellArea(
+                res, portraitViews, portraitModeWidthDp, portraitModeHeightDp);
+        return new RemoteViews(landscapeViews, portraitViews);
     }
 
     /**
diff --git a/chrome/browser/ui/android/quickactionsearchwidget/java/src/org/chromium/chrome/browser/ui/quickactionsearchwidget/QuickActionSearchWidgetProviderDelegateTest.java b/chrome/browser/ui/android/quickactionsearchwidget/java/src/org/chromium/chrome/browser/ui/quickactionsearchwidget/QuickActionSearchWidgetProviderDelegateTest.java
index 725eb13..185e6a8 100644
--- a/chrome/browser/ui/android/quickactionsearchwidget/java/src/org/chromium/chrome/browser/ui/quickactionsearchwidget/QuickActionSearchWidgetProviderDelegateTest.java
+++ b/chrome/browser/ui/android/quickactionsearchwidget/java/src/org/chromium/chrome/browser/ui/quickactionsearchwidget/QuickActionSearchWidgetProviderDelegateTest.java
@@ -4,16 +4,23 @@
 
 package org.chromium.chrome.browser.ui.quickactionsearchwidget;
 
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
 import android.app.Activity;
 import android.appwidget.AppWidgetManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.net.Uri;
 import android.support.test.InstrumentationRegistry;
+import android.util.Size;
 import android.view.View;
 import android.widget.FrameLayout;
+import android.widget.RemoteViews;
 
 import androidx.annotation.LayoutRes;
 import androidx.test.filters.SmallTest;
@@ -24,6 +31,8 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import org.chromium.base.IntentUtils;
 import org.chromium.base.test.BaseActivityTestRule;
@@ -43,6 +52,8 @@
 import org.chromium.chrome.test.util.browser.Features;
 import org.chromium.components.embedder_support.util.UrlConstants;
 
+import java.util.Locale;
+
 /**
  * Tests for the QuickActionSearchWidgetProviderDelegate.
  */
@@ -62,9 +73,14 @@
     private int mXSmallWidgetMinHeightDp;
     private int mSmallWidgetMinHeightDp;
     private int mMediumWidgetMinHeightDp;
+    private int mDinoWidgetEdgeSizeDp;
+
+    @Mock
+    RemoteViews mMockRemoteViews;
 
     @Before
     public void setUp() {
+        MockitoAnnotations.initMocks(this);
         ChromeApplicationTestUtils.setUp(InstrumentationRegistry.getTargetContext());
         mContext = InstrumentationRegistry.getInstrumentation()
                            .getTargetContext()
@@ -91,6 +107,8 @@
                         / density);
         mDefaultWidgetWidthDp =
                 (int) (res.getDimension(R.dimen.quick_action_search_widget_width) / density);
+        mDinoWidgetEdgeSizeDp =
+                (int) (res.getDimension(R.dimen.quick_action_search_widget_dino_size) / density);
 
         setUpViews();
     }
@@ -174,7 +192,10 @@
                                       mDefaultWidgetWidthDp, mMediumWidgetMinHeightDp)
                               .apply(mContext, null);
         mDinoWidgetView =
-                mDelegate.createDinoWidgetRemoteViews(mContext, prefs).apply(mContext, null);
+                mDelegate
+                        .createDinoWidgetRemoteViews(mContext, prefs, mDinoWidgetEdgeSizeDp,
+                                mDinoWidgetEdgeSizeDp, mDinoWidgetEdgeSizeDp, mDinoWidgetEdgeSizeDp)
+                        .apply(mContext, null);
     }
 
     /**
@@ -458,4 +479,155 @@
         Assert.assertFalse(settings.googleLensVisible);
         Assert.assertFalse(settings.dinoGameVisible);
     }
+
+    @Test
+    @SmallTest
+    public void computeWidgetAreaPaddingForDinoWidget_noPadding() {
+        // Dino widget has a square shape. When offered a square cell area - no cropping should be
+        // performed.
+        Size s = mDelegate.computeWidgetAreaPaddingForDinoWidgetPx(100, 100, 1.f);
+        Assert.assertEquals(0, s.getWidth());
+        Assert.assertEquals(0, s.getHeight());
+    }
+
+    @Test
+    @SmallTest
+    public void computeWidgetAreaPaddingForDinoWidget_horizontalPadding() {
+        // Dino widget has a square shape.
+        // When offered a wider area, we should crop the sides.
+        // Note that the input values are in DP, but result carries pixels.
+        Size s = mDelegate.computeWidgetAreaPaddingForDinoWidgetPx(100, 80, 2.f);
+        Assert.assertEquals(20, s.getWidth());
+        Assert.assertEquals(0, s.getHeight());
+    }
+
+    @Test
+    @SmallTest
+    public void computeWidgetAreaPaddingForDinoWidget_verticalPadding() {
+        // Dino widget has a square shape.
+        // When offered a taller area, we should crop the top and bottom.
+        // Note that the input values are in DP, but result carries pixels.
+        Size s = mDelegate.computeWidgetAreaPaddingForDinoWidgetPx(100, 120, 3.f);
+        Assert.assertEquals(0, s.getWidth());
+        Assert.assertEquals(30, s.getHeight());
+    }
+
+    @Test
+    @SmallTest
+    public void computeScaleFactorForDinoWidget() {
+        // The scale factor expresses how widget should grow or shrink to properly
+        // fill up the space. It is computed as a proportion:
+        //   scale factor = target size / reference size
+        // a scale factor of 1.0 means the area will host the widget as it was designed
+        // without any scaling.
+        Resources r = mContext.getResources();
+        Assert.assertEquals(1.f,
+                mDelegate.computeScaleFactorForDinoWidget(
+                        mDinoWidgetEdgeSizeDp, mDinoWidgetEdgeSizeDp),
+                0.001f);
+        Assert.assertEquals(1.f,
+                mDelegate.computeScaleFactorForDinoWidget(
+                        mDinoWidgetEdgeSizeDp, mDinoWidgetEdgeSizeDp * 2),
+                0.001f);
+        Assert.assertEquals(2.f,
+                mDelegate.computeScaleFactorForDinoWidget(
+                        mDinoWidgetEdgeSizeDp * 3, mDinoWidgetEdgeSizeDp * 2),
+                0.001f);
+        Assert.assertEquals(0.5f,
+                mDelegate.computeScaleFactorForDinoWidget(
+                        mDinoWidgetEdgeSizeDp, mDinoWidgetEdgeSizeDp / 2),
+                0.001f);
+    }
+
+    @Test
+    @SmallTest
+    public void resizeDinoWidgetToFillTargetCellArea_centerInWideArea() {
+        final Resources r = mContext.getResources();
+        final float density = r.getDisplayMetrics().density;
+
+        // Try to place the dino in the area that is half the reference size.
+        // We should see the widget centered and resized accordingly.
+        final int areaWidthDp = mDinoWidgetEdgeSizeDp;
+        final int areaHeightDp = mDinoWidgetEdgeSizeDp / 2;
+        mDelegate.resizeDinoWidgetToFillTargetCellArea(
+                r, mMockRemoteViews, areaWidthDp, areaHeightDp);
+
+        // The remaining area is half the size of the widget.
+        // We divide it further by two to apply the padding on each side.
+        int padSize = (int) (mDinoWidgetEdgeSizeDp * density / 2.f / 2.f);
+        verify(mMockRemoteViews)
+                .setViewPadding(R.id.dino_quick_action_area, padSize, 0, padSize, 0);
+    }
+
+    @Test
+    @SmallTest
+    public void resizeDinoWidgetToFillTargetCellArea_centerInTallArea() {
+        final Resources r = mContext.getResources();
+        final float density = r.getDisplayMetrics().density;
+
+        // Try to place the dino in the area that is half the reference size.
+        // We should see the widget centered and resized accordingly.
+        final int areaWidthDp = mDinoWidgetEdgeSizeDp / 2;
+        final int areaHeightDp = mDinoWidgetEdgeSizeDp;
+        mDelegate.resizeDinoWidgetToFillTargetCellArea(
+                r, mMockRemoteViews, areaWidthDp, areaHeightDp);
+
+        // The remaining area is half the size of the widget.
+        // We divide it further by two to apply the padding on each side.
+        int padSize = (int) (mDinoWidgetEdgeSizeDp * density / 2.f / 2.f);
+        verify(mMockRemoteViews)
+                .setViewPadding(R.id.dino_quick_action_area, 0, padSize, 0, padSize);
+    }
+
+    @Test
+    @SmallTest
+    public void resizeDinoWidgetToFillTargetCellArea_repositionContent() {
+        final Resources r = mContext.getResources();
+        final float density = r.getDisplayMetrics().density;
+
+        // Again, apply half the size of what the widget was designed for.
+        final int areaWidthDp = mDinoWidgetEdgeSizeDp / 2;
+        final int areaHeightDp = mDinoWidgetEdgeSizeDp;
+        mDelegate.resizeDinoWidgetToFillTargetCellArea(
+                r, mMockRemoteViews, areaWidthDp, areaHeightDp);
+
+        // Since widget is half the size, the button area paddings should be half the size too.
+        int imagePadLeft =
+                (int) (r.getDimension(R.dimen.quick_action_search_widget_dino_padding_start) / 2.f);
+        int imagePadVertical =
+                (int) (r.getDimension(R.dimen.quick_action_search_widget_dino_padding_vertical)
+                        / 2.f);
+
+        verify(mMockRemoteViews)
+                .setViewPadding(R.id.dino_quick_action_button, imagePadLeft, imagePadVertical, 0,
+                        imagePadVertical);
+    }
+
+    @Test
+    @SmallTest
+    public void resizeDinoWidgetToFillTargetCellArea_repositionContentRTL() {
+        final Configuration c = new Configuration(mContext.getResources().getConfiguration());
+        c.setLayoutDirection(Locale.forLanguageTag("ar")); // arabic
+
+        final Resources r = spy(mContext.getResources());
+        when(r.getConfiguration()).thenReturn(c);
+        final float density = r.getDisplayMetrics().density;
+
+        // Again, apply half the size of what the widget was designed for.
+        final int areaWidthDp = mDinoWidgetEdgeSizeDp / 4;
+        final int areaHeightDp = mDinoWidgetEdgeSizeDp;
+        mDelegate.resizeDinoWidgetToFillTargetCellArea(
+                r, mMockRemoteViews, areaWidthDp, areaHeightDp);
+
+        // Since widget is a fraction of the size, that fraction goes into appropriate paddings.
+        int imagePadRight =
+                (int) (r.getDimension(R.dimen.quick_action_search_widget_dino_padding_start) / 4.f);
+        int imagePadVertical =
+                (int) (r.getDimension(R.dimen.quick_action_search_widget_dino_padding_vertical)
+                        / 4.f);
+
+        verify(mMockRemoteViews)
+                .setViewPadding(R.id.dino_quick_action_button, 0, imagePadVertical, imagePadRight,
+                        imagePadVertical);
+    }
 }
diff --git a/chrome/browser/ui/app_list/app_list_sort_unittest.cc b/chrome/browser/ui/app_list/app_list_sort_unittest.cc
index bc64532..8947136 100644
--- a/chrome/browser/ui/app_list/app_list_sort_unittest.cc
+++ b/chrome/browser/ui/app_list/app_list_sort_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "base/test/scoped_feature_list.h"
 
+#include "ash/app_list/model/app_list_model.h"
 #include "ash/constants/ash_features.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/app_list/app_list_model_updater.h"
@@ -238,6 +239,272 @@
             std::vector<std::string>({kItemId3, kItemId4, kItemId2, kItemId1}));
 }
 
+// Verifies that moving an item within the app list resets the nominal app list
+// sort order (if the app list is sorted at the time).
+TEST_F(TemporaryAppListSortTest, MovingItemsResetsSortOrder) {
+  RemoveAllExistingItems();
+
+  std::vector<scoped_refptr<extensions::Extension>> apps;
+  for (int i = 0; i < 10; ++i) {
+    const std::string id = GenerateId(base::StringPrintf("app_id_%d", i));
+    const std::string name = base::StringPrintf("Item %d", i);
+    scoped_refptr<extensions::Extension> app =
+        MakeApp(name, id, extensions::Extension::NO_FLAGS);
+    apps.push_back(app);
+    InstallExtension(app.get());
+  }
+
+  // Sort with name alphabetical order.
+  GetChromeModelUpdater()->RequestAppListSort(
+      ash::AppListSortOrder::kNameAlphabetical);
+  Commit();
+  EXPECT_FALSE(IsUnderTemporarySort());
+  EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetSortOrderFromPrefs());
+  EXPECT_EQ(GetOrderedNamesFromSyncableService(),
+            std::vector<std::string>({"Item 0", "Item 1", "Item 2", "Item 3",
+                                      "Item 4", "Item 5", "Item 6", "Item 7",
+                                      "Item 8", "Item 9"}));
+
+  // Move an item within the app list.
+  ChromeAppListModelUpdater* model_updater = GetChromeModelUpdater();
+  const syncer::StringOrdinal target_position =
+      model_updater->FindItem(apps[1]->id())
+          ->position()
+          .CreateBetween(model_updater->FindItem(apps[2]->id())->position());
+  model_updater->RequestPositionUpdate(
+      apps[7]->id(), target_position,
+      ash::RequestPositionUpdateReason::kMoveItem);
+
+  // Verify that the app list is no longer considered sorted - new items are
+  // added to the first position within the app list.
+  EXPECT_FALSE(IsUnderTemporarySort());
+  EXPECT_EQ(ash::AppListSortOrder::kCustom, GetSortOrderFromPrefs());
+  EXPECT_EQ(GetOrderedNamesFromSyncableService(),
+            std::vector<std::string>({"Item 0", "Item 1", "Item 7", "Item 2",
+                                      "Item 3", "Item 4", "Item 5", "Item 6",
+                                      "Item 8", "Item 9"}));
+
+  scoped_refptr<extensions::Extension> new_app = MakeApp(
+      "Item 10", GenerateId("new_install"), extensions::Extension::NO_FLAGS);
+  InstallExtension(new_app.get());
+
+  EXPECT_EQ(ash::AppListSortOrder::kCustom, GetSortOrderFromPrefs());
+  EXPECT_EQ(GetOrderedNamesFromSyncableService(),
+            std::vector<std::string>({"Item 10", "Item 0", "Item 1", "Item 7",
+                                      "Item 2", "Item 3", "Item 4", "Item 5",
+                                      "Item 6", "Item 8", "Item 9"}));
+}
+
+// Verifies that moving an item from a folder to root apps grid resets the
+// nominal app list sort order (if the app list is sorted at the time).
+TEST_F(TemporaryAppListSortTest, ReparentingItemToRootResetsSortOrder) {
+  RemoveAllExistingItems();
+
+  std::vector<scoped_refptr<extensions::Extension>> apps;
+  for (int i = 0; i < 10; ++i) {
+    const std::string id = GenerateId(base::StringPrintf("app_id_%d", i));
+    const std::string name = base::StringPrintf("Item %d", i);
+    scoped_refptr<extensions::Extension> app =
+        MakeApp(name, id, extensions::Extension::NO_FLAGS);
+    apps.push_back(app);
+    InstallExtension(app.get());
+  }
+
+  // Create a folder that contains three items.
+  const std::string kFolderItemId = GenerateId("folder_id");
+  syncer::SyncDataList sync_list;
+  sync_list.push_back(CreateAppRemoteData(
+      kFolderItemId, "Folder", "",
+      syncer::StringOrdinal::CreateInitialOrdinal().ToInternalValue(), kUnset,
+      sync_pb::AppListSpecifics_AppListItemType_TYPE_FOLDER));
+  syncer::StringOrdinal child_position =
+      syncer::StringOrdinal::CreateInitialOrdinal();
+  sync_list.push_back(
+      CreateAppRemoteData(apps[1]->id(), "Item 1", kFolderItemId,
+                          child_position.ToInternalValue(), kUnset));
+  child_position = child_position.CreateAfter();
+  sync_list.push_back(
+      CreateAppRemoteData(apps[2]->id(), "Item 2", kFolderItemId,
+                          child_position.ToInternalValue(), kUnset));
+  child_position = child_position.CreateAfter();
+  sync_list.push_back(
+      CreateAppRemoteData(apps[3]->id(), "Item 3", kFolderItemId,
+                          child_position.ToInternalValue(), kUnset));
+
+  app_list_syncable_service()->MergeDataAndStartSyncing(
+      syncer::APP_LIST, sync_list,
+      std::make_unique<syncer::FakeSyncChangeProcessor>(),
+      std::make_unique<syncer::SyncErrorFactoryMock>());
+  content::RunAllTasksUntilIdle();
+
+  // Sort with name alphabetical order.
+  GetChromeModelUpdater()->RequestAppListSort(
+      ash::AppListSortOrder::kNameAlphabetical);
+  Commit();
+  EXPECT_FALSE(IsUnderTemporarySort());
+  EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetSortOrderFromPrefs());
+  EXPECT_EQ(GetOrderedNamesFromSyncableService(),
+            std::vector<std::string>({"Folder", "Item 0", "Item 1", "Item 2",
+                                      "Item 3", "Item 4", "Item 5", "Item 6",
+                                      "Item 7", "Item 8", "Item 9"}));
+
+  // Move an from the folder to root apps grid.
+  ChromeAppListModelUpdater* model_updater = GetChromeModelUpdater();
+  const syncer::StringOrdinal target_position =
+      model_updater->FindItem(apps[6]->id())
+          ->position()
+          .CreateBetween(model_updater->FindItem(apps[7]->id())->position());
+  model_updater->RequestMoveItemToRoot(apps[1]->id(), target_position);
+
+  // Verify that the app list is no longer considered sorted - new items are
+  // added to the first position within the app list.
+  EXPECT_FALSE(IsUnderTemporarySort());
+  EXPECT_EQ(ash::AppListSortOrder::kCustom, GetSortOrderFromPrefs());
+  EXPECT_EQ(GetOrderedNamesFromSyncableService(),
+            std::vector<std::string>({"Folder", "Item 0", "Item 2", "Item 3",
+                                      "Item 4", "Item 5", "Item 6", "Item 1",
+                                      "Item 7", "Item 8", "Item 9"}));
+
+  scoped_refptr<extensions::Extension> new_app = MakeApp(
+      "Item 10", GenerateId("new_install"), extensions::Extension::NO_FLAGS);
+  InstallExtension(new_app.get());
+
+  EXPECT_EQ(ash::AppListSortOrder::kCustom, GetSortOrderFromPrefs());
+  EXPECT_EQ(GetOrderedNamesFromSyncableService(),
+            std::vector<std::string>({"Item 10", "Folder", "Item 0", "Item 2",
+                                      "Item 3", "Item 4", "Item 5", "Item 6",
+                                      "Item 1", "Item 7", "Item 8", "Item 9"}));
+}
+
+// Verifies that merging two items to form a folder resets the nominal app list
+// sort order (if the app list is sorted at the time).
+TEST_F(TemporaryAppListSortTest, MergingItemsResetsSortOrder) {
+  RemoveAllExistingItems();
+
+  std::vector<scoped_refptr<extensions::Extension>> apps;
+  for (int i = 0; i < 10; ++i) {
+    const std::string id = GenerateId(base::StringPrintf("app_id_%d", i));
+    const std::string name = base::StringPrintf("Item %d", i);
+    scoped_refptr<extensions::Extension> app =
+        MakeApp(name, id, extensions::Extension::NO_FLAGS);
+    apps.push_back(app);
+    InstallExtension(app.get());
+  }
+
+  // Sort with name alphabetical order.
+  GetChromeModelUpdater()->RequestAppListSort(
+      ash::AppListSortOrder::kNameAlphabetical);
+  Commit();
+  EXPECT_FALSE(IsUnderTemporarySort());
+  EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetSortOrderFromPrefs());
+  EXPECT_EQ(GetOrderedNamesFromSyncableService(),
+            std::vector<std::string>({"Item 0", "Item 1", "Item 2", "Item 3",
+                                      "Item 4", "Item 5", "Item 6", "Item 7",
+                                      "Item 8", "Item 9"}));
+
+  // Merge two items into a folder.
+  ChromeAppListModelUpdater* model_updater = GetChromeModelUpdater();
+  model_updater->model_for_test()->MergeItems(apps[8]->id(), apps[9]->id());
+
+  // Verify that the app list is no longer considered sorted - new items are
+  // added to the first position within the app list.
+  EXPECT_FALSE(IsUnderTemporarySort());
+  EXPECT_EQ(ash::AppListSortOrder::kCustom, GetSortOrderFromPrefs());
+  EXPECT_EQ(GetOrderedNamesFromSyncableService(),
+            std::vector<std::string>({"Item 0", "Item 1", "Item 2", "Item 3",
+                                      "Item 4", "Item 5", "Item 6", "Item 7",
+                                      "Item 8", "", "Item 9"}));
+
+  scoped_refptr<extensions::Extension> new_app = MakeApp(
+      "Item 10", GenerateId("new_install"), extensions::Extension::NO_FLAGS);
+  InstallExtension(new_app.get());
+
+  EXPECT_EQ(ash::AppListSortOrder::kCustom, GetSortOrderFromPrefs());
+  EXPECT_EQ(GetOrderedNamesFromSyncableService(),
+            std::vector<std::string>({"Item 10", "Item 0", "Item 1", "Item 2",
+                                      "Item 3", "Item 4", "Item 5", "Item 6",
+                                      "Item 7", "Item 8", "", "Item 9"}));
+}
+
+// Verifies that moving an item from a folder to root apps grid resets the
+// nominal app list sort order (if the app list is sorted at the time).
+TEST_F(TemporaryAppListSortTest, ReparentingItemToFolderDoesNotResetSortOrder) {
+  RemoveAllExistingItems();
+
+  std::vector<scoped_refptr<extensions::Extension>> apps;
+  for (int i = 0; i < 10; ++i) {
+    const std::string id = GenerateId(base::StringPrintf("app_id_%d", i));
+    const std::string name = base::StringPrintf("Item %d", i);
+    scoped_refptr<extensions::Extension> app =
+        MakeApp(name, id, extensions::Extension::NO_FLAGS);
+    apps.push_back(app);
+    InstallExtension(app.get());
+  }
+
+  // Create a folder that contains three items.
+  const std::string kFolderItemId = GenerateId("folder_id");
+  syncer::SyncDataList sync_list;
+  sync_list.push_back(CreateAppRemoteData(
+      kFolderItemId, "Folder", "",
+      syncer::StringOrdinal::CreateInitialOrdinal().ToInternalValue(), kUnset,
+      sync_pb::AppListSpecifics_AppListItemType_TYPE_FOLDER));
+
+  // Add three apps to the folder.
+  syncer::StringOrdinal child_position =
+      syncer::StringOrdinal::CreateInitialOrdinal();
+  sync_list.push_back(
+      CreateAppRemoteData(apps[1]->id(), "Item 1", kFolderItemId,
+                          child_position.ToInternalValue(), kUnset));
+  child_position = child_position.CreateAfter();
+  sync_list.push_back(
+      CreateAppRemoteData(apps[2]->id(), "Item 2", kFolderItemId,
+                          child_position.ToInternalValue(), kUnset));
+  child_position = child_position.CreateAfter();
+  sync_list.push_back(
+      CreateAppRemoteData(apps[3]->id(), "Item 3", kFolderItemId,
+                          child_position.ToInternalValue(), kUnset));
+
+  app_list_syncable_service()->MergeDataAndStartSyncing(
+      syncer::APP_LIST, sync_list,
+      std::make_unique<syncer::FakeSyncChangeProcessor>(),
+      std::make_unique<syncer::SyncErrorFactoryMock>());
+  content::RunAllTasksUntilIdle();
+
+  // Sort with name alphabetical order.
+  GetChromeModelUpdater()->RequestAppListSort(
+      ash::AppListSortOrder::kNameAlphabetical);
+  Commit();
+  EXPECT_FALSE(IsUnderTemporarySort());
+  EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetSortOrderFromPrefs());
+  EXPECT_EQ(GetOrderedNamesFromSyncableService(),
+            std::vector<std::string>({"Folder", "Item 0", "Item 1", "Item 2",
+                                      "Item 3", "Item 4", "Item 5", "Item 6",
+                                      "Item 7", "Item 8", "Item 9"}));
+
+  // Move an from the folder to root apps grid.
+  ChromeAppListModelUpdater* model_updater = GetChromeModelUpdater();
+  model_updater->RequestMoveItemToFolder(
+      apps[7]->id(), kFolderItemId, ash::RequestMoveToFolderReason::kMoveItem);
+
+  // Verify that the app list is still considered sorted - new items are
+  // added to the app list to maintain sorted order.
+  EXPECT_FALSE(IsUnderTemporarySort());
+  EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetSortOrderFromPrefs());
+  EXPECT_EQ(GetOrderedNamesFromSyncableService(),
+            std::vector<std::string>({"Folder", "Item 0", "Item 1", "Item 2",
+                                      "Item 3", "Item 4", "Item 5", "Item 6",
+                                      "Item 7", "Item 8", "Item 9"}));
+
+  scoped_refptr<extensions::Extension> new_app = MakeApp(
+      "Item 10", GenerateId("new_install"), extensions::Extension::NO_FLAGS);
+  InstallExtension(new_app.get());
+
+  EXPECT_EQ(ash::AppListSortOrder::kNameAlphabetical, GetSortOrderFromPrefs());
+  EXPECT_EQ(GetOrderedNamesFromSyncableService(),
+            std::vector<std::string>({"Folder", "Item 0", "Item 1", "Item 10",
+                                      "Item 2", "Item 3", "Item 4", "Item 5",
+                                      "Item 6", "Item 7", "Item 8", "Item 9"}));
+}
 // Verifies that reverting the temporary name sort order works as expected.
 TEST_F(TemporaryAppListSortTest, RevertNameOrder) {
   RemoveAllExistingItems();
@@ -559,6 +826,84 @@
             GetOrderedNamesFromSyncableService());
 }
 
+// Verifies that the app list under temporary sort works as expected when moving
+// an item from a folder to root apps grid.
+TEST_F(TemporaryAppListSortTest, HandleMoveItemToRootGrid) {
+  RemoveAllExistingItems();
+
+  // Add one folder containing three apps.
+  const std::string kFolderItemId = GenerateId("folder_id");
+  syncer::SyncDataList sync_list;
+  sync_list.push_back(CreateAppRemoteData(
+      kFolderItemId, "Folder", "",
+      syncer::StringOrdinal::CreateInitialOrdinal().ToInternalValue(), kUnset,
+      sync_pb::AppListSpecifics_AppListItemType_TYPE_FOLDER));
+  const std::string kItemId1 = GenerateId("app_id1");
+  const std::string kItemId2 = GenerateId("app_id2");
+  const std::string kItemId3 = GenerateId("app_id3");
+
+  syncer::StringOrdinal child_position =
+      syncer::StringOrdinal::CreateInitialOrdinal();
+  sync_list.push_back(CreateAppRemoteData(
+      kItemId1, "A", kFolderItemId, child_position.ToInternalValue(), kUnset));
+  child_position = child_position.CreateAfter();
+  sync_list.push_back(CreateAppRemoteData(
+      kItemId2, "B", kFolderItemId, child_position.ToInternalValue(), kUnset));
+  child_position = child_position.CreateAfter();
+  sync_list.push_back(CreateAppRemoteData(
+      kItemId3, "C", kFolderItemId, child_position.ToInternalValue(), kUnset));
+
+  app_list_syncable_service()->MergeDataAndStartSyncing(
+      syncer::APP_LIST, sync_list,
+      std::make_unique<syncer::FakeSyncChangeProcessor>(),
+      std::make_unique<syncer::SyncErrorFactoryMock>());
+  content::RunAllTasksUntilIdle();
+
+  // Install test apps that were added to the folder.
+  scoped_refptr<extensions::Extension> app1 =
+      MakeApp("A", kItemId1, extensions::Extension::NO_FLAGS);
+  InstallExtension(app1.get());
+  scoped_refptr<extensions::Extension> app2 =
+      MakeApp("B", kItemId2, extensions::Extension::NO_FLAGS);
+  InstallExtension(app2.get());
+  scoped_refptr<extensions::Extension> app3 =
+      MakeApp("C", kItemId3, extensions::Extension::NO_FLAGS);
+  InstallExtension(app3.get());
+
+  // Install an additional app.
+  const std::string kItemId4 = GenerateId("app_id4");
+  scoped_refptr<extensions::Extension> app4 =
+      MakeApp("D", kItemId4, extensions::Extension::NO_FLAGS);
+  InstallExtension(app4.get());
+
+  // Sort with the reverse alphabetical name order and commit.
+  ChromeAppListModelUpdater* model_updater = GetChromeModelUpdater();
+  model_updater->RequestAppListSort(
+      ash::AppListSortOrder::kNameReverseAlphabetical);
+  Commit();
+  EXPECT_EQ(std::vector<std::string>({"Folder", "D", "C", "B", "A"}),
+            GetOrderedNamesFromSyncableService());
+
+  // Sort with the name alphabetical order without committing.
+  model_updater->RequestAppListSort(ash::AppListSortOrder::kNameAlphabetical);
+  EXPECT_EQ(ash::AppListSortOrder::kNameReverseAlphabetical,
+            GetSortOrderFromPrefs());
+  EXPECT_EQ(std::vector<std::string>({"Folder", "D", "C", "B", "A"}),
+            GetOrderedNamesFromSyncableService());
+
+  // Move an folder item to root apps grid.
+  model_updater->RequestMoveItemToRoot(
+      kItemId1, model_updater->FindItem(kItemId4)->position().CreateAfter());
+
+  // Verify that:
+  // (1) Temporary sort ends.
+  // (2) Sort order pref reverts to custom.
+  EXPECT_FALSE(IsUnderTemporarySort());
+  EXPECT_EQ(ash::AppListSortOrder::kCustom, GetSortOrderFromPrefs());
+  EXPECT_EQ(std::vector<std::string>({"Folder", "B", "C", "D", "A"}),
+            GetOrderedNamesFromSyncableService());
+}
+
 // Verifies the temporary sorting behavior with local app installation.
 TEST_F(TemporaryAppListSortTest, InstallAppLocally) {
   RemoveAllExistingItems();
diff --git a/chrome/browser/ui/app_list/chrome_app_list_model_updater.cc b/chrome/browser/ui/app_list/chrome_app_list_model_updater.cc
index 8fc9c2f..f63a118 100644
--- a/chrome/browser/ui/app_list/chrome_app_list_model_updater.cc
+++ b/chrome/browser/ui/app_list/chrome_app_list_model_updater.cc
@@ -793,6 +793,10 @@
     if (is_under_temporary_sort()) {
       EndTemporarySortAndTakeAction(EndAction::kCommitAndClearSort);
     } else {
+      if (order_delegate_) {
+        order_delegate_->SetAppListPreferredOrder(
+            ash::AppListSortOrder::kCustom);
+      }
       // NOTE: Committing temporary sort will also reset page breaks, so they
       // don't have to be sanitized again in that case.
       sync_model_sanitizer_->SanitizePageBreaksForProductivityLauncher(
@@ -830,8 +834,14 @@
   if (!ash::features::IsProductivityLauncherEnabled())
     ClearFolderIfItHasSingleChild(old_parent);
 
-  sync_model_sanitizer_->SanitizePageBreaksForProductivityLauncher(
-      GetTopLevelItemIds(), /*reset_page_breaks=*/false);
+  if (is_under_temporary_sort()) {
+    EndTemporarySortAndTakeAction(EndAction::kCommitAndClearSort);
+  } else {
+    if (order_delegate_)
+      order_delegate_->SetAppListPreferredOrder(ash::AppListSortOrder::kCustom);
+    sync_model_sanitizer_->SanitizePageBreaksForProductivityLauncher(
+        GetTopLevelItemIds(), /*reset_page_breaks=*/false);
+  }
 }
 
 void ChromeAppListModelUpdater::RequestAppListSort(
@@ -895,6 +905,10 @@
     if (temporary_sort_manager_) {
       EndTemporarySortAndTakeAction(EndAction::kCommitAndClearSort);
     } else {
+      if (order_delegate_) {
+        order_delegate_->SetAppListPreferredOrder(
+            ash::AppListSortOrder::kCustom);
+      }
       // NOTE: Committing temporary sort will also reset page breaks, so they
       // don't have to be sanitized again in that case.
       sync_model_sanitizer_->SanitizePageBreaksForProductivityLauncher(
diff --git a/chrome/browser/ui/autofill/edit_address_profile_dialog_controller_impl.cc b/chrome/browser/ui/autofill/edit_address_profile_dialog_controller_impl.cc
index cc95ffc..e52b3f6 100644
--- a/chrome/browser/ui/autofill/edit_address_profile_dialog_controller_impl.cc
+++ b/chrome/browser/ui/autofill/edit_address_profile_dialog_controller_impl.cc
@@ -37,6 +37,13 @@
     const AutofillProfile* original_profile,
     AutofillClient::AddressProfileSavePromptCallback
         address_profile_save_prompt_callback) {
+  // Don't show the bubble if it's already visible, and inform the backend.
+  if (dialog_view_) {
+    std::move(address_profile_save_prompt_callback)
+        .Run(AutofillClient::SaveAddressProfileOfferUserDecision::kAutoDeclined,
+             profile);
+    return;
+  }
   address_profile_to_edit_ = profile;
   original_profile_ = base::OptionalFromPtr(original_profile);
   address_profile_save_prompt_callback_ =
diff --git a/chrome/browser/ui/sharing_hub/sharing_hub_bubble_controller_chromeos_browsertest.cc b/chrome/browser/ui/sharing_hub/sharing_hub_bubble_controller_chromeos_browsertest.cc
new file mode 100644
index 0000000..ade0e5a
--- /dev/null
+++ b/chrome/browser/ui/sharing_hub/sharing_hub_bubble_controller_chromeos_browsertest.cc
@@ -0,0 +1,47 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/sharesheet/sharesheet_service.h"
+#include "chrome/browser/sharesheet/sharesheet_service_factory.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/page_action/page_action_icon_type.h"
+#include "chrome/browser/ui/sharing_hub/sharing_hub_bubble_controller.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "content/public/test/browser_test.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class SharingHubBubbleControllerChromeOsBrowserTest
+    : public InProcessBrowserTest {
+ public:
+  SharingHubBubbleControllerChromeOsBrowserTest() = default;
+  ~SharingHubBubbleControllerChromeOsBrowserTest() override = default;
+};
+
+IN_PROC_BROWSER_TEST_F(SharingHubBubbleControllerChromeOsBrowserTest,
+                       OpenSharesheet) {
+  sharesheet::SharesheetService* sharesheet_service =
+      sharesheet::SharesheetServiceFactory::GetForProfile(browser()->profile());
+  gfx::NativeWindow web_contents_containing_window_ =
+      browser()
+          ->tab_strip_model()
+          ->GetActiveWebContents()
+          ->GetTopLevelNativeWindow();
+
+  // Open the sharesheet using the sharing hub controller.
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  sharing_hub::SharingHubBubbleController::CreateOrGetFromWebContents(
+      web_contents)
+      ->ShowBubble();
+
+  // Verify that the sharesheet is open.
+  sharesheet::SharesheetController* controller =
+      sharesheet_service->GetSharesheetController(
+          web_contents_containing_window_);
+  ASSERT_TRUE(controller->IsBubbleVisible());
+}
+
+}  // namespace
diff --git a/chrome/browser/ui/sync/profile_signin_confirmation_helper_unittest.cc b/chrome/browser/ui/sync/profile_signin_confirmation_helper_unittest.cc
index 41e7f7fc..90ef393 100644
--- a/chrome/browser/ui/sync/profile_signin_confirmation_helper_unittest.cc
+++ b/chrome/browser/ui/sync/profile_signin_confirmation_helper_unittest.cc
@@ -135,7 +135,8 @@
         new sync_preferences::TestingPrefServiceSyncable(
             /*managed_prefs=*/new TestingPrefStore(),
             /*supervised_user_prefs=*/new TestingPrefStore(),
-            /*extension_prefs=*/new TestingPrefStore(), user_prefs_,
+            /*extension_prefs=*/new TestingPrefStore(),
+            /*standalone_browser_prefs=*/new TestingPrefStore(), user_prefs_,
             /*recommended_prefs=*/new TestingPrefStore(),
             new user_prefs::PrefRegistrySyncable(), new PrefNotifierImpl());
     RegisterUserProfilePrefs(pref_service->registry());
diff --git a/chrome/browser/ui/views/payments/payment_sheet_view_controller_browsertest.cc b/chrome/browser/ui/views/payments/payment_sheet_view_controller_browsertest.cc
index ce45150..1f57c1e 100644
--- a/chrome/browser/ui/views/payments/payment_sheet_view_controller_browsertest.cc
+++ b/chrome/browser/ui/views/payments/payment_sheet_view_controller_browsertest.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/ui/views/payments/payment_request_browsertest_base.h"
 #include "chrome/browser/ui/views/payments/payment_request_dialog_view_ids.h"
 #include "chrome/test/base/ui_test_utils.h"
@@ -12,6 +13,7 @@
 #include "components/autofill/core/browser/field_types.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
 #include "components/strings/grit/components_strings.h"
+#include "content/public/common/content_features.h"
 #include "content/public/test/browser_test.h"
 #include "net/dns/mock_host_resolver.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -82,6 +84,53 @@
                          DialogViewID::PAYMENT_SHEET_CONTACT_INFO_SECTION)));
 }
 
+// The tests in this class correspond to the tests of the same name in
+// PaymentSheetViewControllerNoShippingTest, with the basic-card being disabled.
+// Parameterized tests are not used because the test setup for both tests are
+// too different.
+class PaymentSheetViewControllerNoShippingBasicCardDisabledTest
+    : public PaymentSheetViewControllerNoShippingTest {
+ public:
+  PaymentSheetViewControllerNoShippingBasicCardDisabledTest(
+      const PaymentSheetViewControllerNoShippingBasicCardDisabledTest&) =
+      delete;
+  PaymentSheetViewControllerNoShippingBasicCardDisabledTest& operator=(
+      const PaymentSheetViewControllerNoShippingBasicCardDisabledTest&) =
+      delete;
+
+ protected:
+  PaymentSheetViewControllerNoShippingBasicCardDisabledTest() {
+    feature_list_.InitWithFeatures({}, {::features::kPaymentRequestBasicCard});
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+// If shipping and contact info are not requested, their rows should not be
+// present.
+IN_PROC_BROWSER_TEST_F(
+    PaymentSheetViewControllerNoShippingBasicCardDisabledTest,
+    NoShippingNoContactRows) {
+  std::string payment_method_name;
+  InstallPaymentApp("a.com", "payment_request_success_responder.js",
+                    &payment_method_name);
+
+  NavigateTo("/payment_request_no_shipping_test.html");
+  InvokePaymentRequestUIWithJs("buyWithMethods([{supportedMethods:'" +
+                               payment_method_name + "'}]);");
+
+  EXPECT_NE(nullptr, dialog_view()->GetViewByID(static_cast<int>(
+                         DialogViewID::PAYMENT_SHEET_SUMMARY_SECTION)));
+  EXPECT_EQ(nullptr,
+            dialog_view()->GetViewByID(static_cast<int>(
+                DialogViewID::PAYMENT_SHEET_SHIPPING_ADDRESS_SECTION)));
+  EXPECT_EQ(nullptr, dialog_view()->GetViewByID(static_cast<int>(
+                         DialogViewID::PAYMENT_SHEET_SHIPPING_OPTION_SECTION)));
+  EXPECT_EQ(nullptr, dialog_view()->GetViewByID(static_cast<int>(
+                         DialogViewID::PAYMENT_SHEET_CONTACT_INFO_SECTION)));
+}
+
 typedef PaymentRequestBrowserTestBase PaymentHandlerUITest;
 
 IN_PROC_BROWSER_TEST_F(PaymentHandlerUITest, BackReturnsToPaymentSheet) {
diff --git a/chrome/browser/ui/views/sharing_hub/sharing_hub_icon_view.cc b/chrome/browser/ui/views/sharing_hub/sharing_hub_icon_view.cc
index 204d1f2..d860a11 100644
--- a/chrome/browser/ui/views/sharing_hub/sharing_hub_icon_view.cc
+++ b/chrome/browser/ui/views/sharing_hub/sharing_hub_icon_view.cc
@@ -18,11 +18,18 @@
 #include "components/omnibox/browser/vector_icons.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/strings/grit/ui_strings.h"
 
 namespace sharing_hub {
 
 namespace {
 
+send_tab_to_self::SendTabToSelfBubbleController* GetSendTabToSelfController(
+    content::WebContents* web_contents) {
+  return send_tab_to_self::SendTabToSelfBubbleController::
+      CreateOrGetFromWebContents(web_contents);
+}
+
 bool IsQRCodeDialogOpen(content::WebContents* web_contents) {
   qrcode_generator::QRCodeGeneratorBubbleController* controller =
       qrcode_generator::QRCodeGeneratorBubbleController::Get(web_contents);
@@ -31,8 +38,7 @@
 
 bool IsSendTabToSelfDialogOpen(content::WebContents* web_contents) {
   send_tab_to_self::SendTabToSelfBubbleController* controller =
-      send_tab_to_self::SendTabToSelfBubbleController::
-          CreateOrGetFromWebContents(web_contents);
+      GetSendTabToSelfController(web_contents);
   return controller && controller->IsBubbleShown();
 }
 
@@ -47,6 +53,9 @@
                          icon_label_bubble_delegate,
                          page_action_icon_delegate) {
   SetVisible(false);
+  SetLabel(
+      l10n_util::GetStringUTF16(IDS_BROWSER_SHARING_OMNIBOX_SENDING_LABEL));
+  SetUpForInOutAnimation();
 }
 
 SharingHubIconView::~SharingHubIconView() = default;
@@ -78,6 +87,10 @@
       IsSendTabToSelfDialogOpen(web_contents)) {
     SetHighlighted(true);
   }
+
+  if (enabled) {
+    MaybeAnimateSendingToast();
+  }
 }
 
 void SharingHubIconView::OnExecuting(
@@ -87,10 +100,6 @@
   return GetSharingHubVectorIcon();
 }
 
-bool SharingHubIconView::ShouldShowLabel() const {
-  return false;
-}
-
 std::u16string SharingHubIconView::GetTextForTooltipAndAccessibleName() const {
   return l10n_util::GetStringUTF16(IDS_SHARING_HUB_TOOLTIP);
 }
@@ -103,6 +112,20 @@
   return SharingHubBubbleController::CreateOrGetFromWebContents(web_contents);
 }
 
+void SharingHubIconView::MaybeAnimateSendingToast() {
+  content::WebContents* web_contents = GetWebContents();
+  if (!web_contents) {
+    return;
+  }
+  send_tab_to_self::SendTabToSelfBubbleController* controller =
+      GetSendTabToSelfController(web_contents);
+
+  if (controller && controller->show_message()) {
+    controller->set_show_message(false);
+    AnimateIn(absl::nullopt);
+  }
+}
+
 BEGIN_METADATA(SharingHubIconView, PageActionIconView)
 END_METADATA
 
diff --git a/chrome/browser/ui/views/sharing_hub/sharing_hub_icon_view.h b/chrome/browser/ui/views/sharing_hub/sharing_hub_icon_view.h
index 2d895db..d807e9c 100644
--- a/chrome/browser/ui/views/sharing_hub/sharing_hub_icon_view.h
+++ b/chrome/browser/ui/views/sharing_hub/sharing_hub_icon_view.h
@@ -30,7 +30,6 @@
   // PageActionIconView:
   views::BubbleDialogDelegate* GetBubble() const override;
   void UpdateImpl() override;
-  bool ShouldShowLabel() const override;
   std::u16string GetTextForTooltipAndAccessibleName() const override;
 
  protected:
@@ -40,6 +39,9 @@
 
  private:
   SharingHubBubbleController* GetController() const;
+  // Shows a "Sending..." animation if a device was selected in the send tab to
+  // self dialog.
+  void MaybeAnimateSendingToast();
 };
 
 }  // namespace sharing_hub
diff --git a/chrome/browser/ui/views/tabs/tab_strip_scroll_container.cc b/chrome/browser/ui/views/tabs/tab_strip_scroll_container.cc
index 82b88e65..c40a55a 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_scroll_container.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip_scroll_container.cc
@@ -9,6 +9,8 @@
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/ui/views/tabs/tab_strip.h"
 #include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
+#include "chrome/grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/gfx/canvas.h"
@@ -185,14 +187,19 @@
       scroll_button_container->SetLayoutManager(
           std::make_unique<views::FlexLayout>());
   scroll_button_layout->SetOrientation(views::LayoutOrientation::kHorizontal);
+
   leading_scroll_button_ =
       scroll_button_container->AddChildView(CreateScrollButton(
           base::BindRepeating(&TabStripScrollContainer::ScrollTowardsLeadingTab,
                               base::Unretained(this))));
+  leading_scroll_button_->SetAccessibleName(
+      l10n_util::GetStringUTF16(IDS_ACCNAME_TAB_SCROLL_LEADING));
   trailing_scroll_button_ = scroll_button_container->AddChildView(
       CreateScrollButton(base::BindRepeating(
           &TabStripScrollContainer::ScrollTowardsTrailingTab,
           base::Unretained(this))));
+  trailing_scroll_button_->SetAccessibleName(
+      l10n_util::GetStringUTF16(IDS_ACCNAME_TAB_SCROLL_TRAILING));
 
   // The space in dips between the scroll buttons and the NTB.
   constexpr int kScrollButtonsTrailingMargin = 8;
diff --git a/chrome/browser/ui/web_applications/test/DEPS b/chrome/browser/ui/web_applications/test/DEPS
new file mode 100644
index 0000000..545cfc0
--- /dev/null
+++ b/chrome/browser/ui/web_applications/test/DEPS
@@ -0,0 +1,6 @@
+specific_include_rules = {
+  "system_web_app_interactive_uitest\.cc": [
+    "+ash/shell.h",
+    "+ash/wm/window_util.h",
+  ],
+}
diff --git a/chrome/browser/ui/web_applications/test/system_web_app_interactive_uitest.cc b/chrome/browser/ui/web_applications/test/system_web_app_interactive_uitest.cc
index adb5f90c..48379b84e 100644
--- a/chrome/browser/ui/web_applications/test/system_web_app_interactive_uitest.cc
+++ b/chrome/browser/ui/web_applications/test/system_web_app_interactive_uitest.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <memory>
 #include <string>
 #include <tuple>
 #include <utility>
@@ -46,15 +47,26 @@
 #include "ash/public/cpp/app_menu_constants.h"
 #include "ash/public/cpp/shelf_item_delegate.h"
 #include "ash/public/cpp/shelf_model.h"
+#include "ash/shell.h"
+#include "ash/wm/window_util.h"
+#include "base/run_loop.h"
+#include "base/scoped_observation.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
+#include "chrome/browser/ash/crosapi/url_handler_ash.h"
 #include "chrome/browser/ash/login/login_manager_test.h"
 #include "chrome/browser/ash/login/test/login_manager_mixin.h"
 #include "chrome/browser/ash/login/ui/user_adding_screen.h"
+#include "chrome/browser/ash/web_applications/os_url_handler_system_web_app_info.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
 #include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_helper.h"
+#include "chrome/browser/ui/webui/chrome_web_ui_controller_factory.h"
+#include "chrome/common/webui_url_constants.h"
+#include "content/public/test/test_utils.h"
+#include "ui/wm/public/activation_change_observer.h"  // nogncheck
+#include "ui/wm/public/activation_client.h"           // nogncheck
 #endif
 
 namespace web_app {
@@ -803,6 +815,145 @@
 }
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+
+// A one shot observer which waits for an activation of any window.
+class TestActivationObserver : public wm::ActivationChangeObserver {
+ public:
+  TestActivationObserver(const TestActivationObserver&) = delete;
+  TestActivationObserver& operator=(const TestActivationObserver&) = delete;
+
+  TestActivationObserver() {
+    activation_observer_.Observe(ash::Shell::Get()->activation_client());
+  }
+
+  ~TestActivationObserver() override = default;
+
+  void Wait() { run_loop_.Run(); }
+
+  // wm::ActivationChangeObserver:
+  void OnWindowActivated(ActivationReason reason,
+                         aura::Window* gained_active,
+                         aura::Window* lost_active) override {
+    Browser* browser = chrome::FindBrowserWithWindow(gained_active);
+    // Check that the activated window is actually a browser.
+    EXPECT_TRUE(browser);
+    // Check also that the browser is actually an app.
+    EXPECT_TRUE(browser->is_type_app());
+    run_loop_.Quit();
+  }
+
+ private:
+  // The MessageLoopRunner used to spin the message loop.
+  base::RunLoop run_loop_;
+  base::ScopedObservation<wm::ActivationClient, wm::ActivationChangeObserver>
+      activation_observer_{this};
+};
+
+// Tests which are exercising OpenUrl called by Lacros in Ash.
+class SystemWebAppOpenInAshFromLacrosTests
+    : public SystemWebAppManagerBrowserTest {
+ public:
+  SystemWebAppOpenInAshFromLacrosTests()
+      : SystemWebAppManagerBrowserTest(/*install_mock=*/false) {
+    OsUrlHandlerSystemWebAppDelegate::EnableDelegateForTesting(true);
+    url_handler_ = std::make_unique<crosapi::UrlHandlerAsh>();
+  }
+
+  ~SystemWebAppOpenInAshFromLacrosTests() {
+    OsUrlHandlerSystemWebAppDelegate::EnableDelegateForTesting(false);
+  }
+
+  // A function to wait until a window activation change was observed.
+  void LaunchAndWaitForActivationChange(const GURL& url) {
+    TestActivationObserver observer;
+    url_handler_->OpenUrl(url);
+    observer.Wait();
+  }
+
+ protected:
+  std::unique_ptr<crosapi::UrlHandlerAsh> url_handler_;
+};
+
+// This test will make sure that only accepted URLs will be allowed to create
+// applications.
+IN_PROC_BROWSER_TEST_P(SystemWebAppOpenInAshFromLacrosTests,
+                       LaunchOnlyAllowedUrls) {
+  WaitForTestSystemAppInstall();
+
+  // There might be an initial browser from the testing framework.
+  int initial_browser_count = BrowserList::GetInstance()->size();
+
+  // Test that a non descript URL gets rejected.
+  GURL url1 = GURL("http://www.foo.bar");
+  EXPECT_FALSE(ChromeWebUIControllerFactory::GetInstance()->CanHandleUrl(url1));
+  EXPECT_FALSE(url_handler_->OpenUrlInternal(url1));
+
+  // Test that an unknown internal os url gets rejected.
+  GURL url2 = GURL("os://foo-bar");
+  EXPECT_FALSE(ChromeWebUIControllerFactory::GetInstance()->CanHandleUrl(url2));
+  EXPECT_FALSE(url_handler_->OpenUrlInternal(url2));
+
+  // Test that an unknown internal chrome url gets rejected.
+  GURL url3 = GURL("chrome://foo-bar");
+  EXPECT_FALSE(ChromeWebUIControllerFactory::GetInstance()->CanHandleUrl(url3));
+  EXPECT_FALSE(url_handler_->OpenUrlInternal(url3));
+
+  // Test that a known internal url gets accepted.
+  GURL url4 = GURL("os://version");
+  EXPECT_TRUE(ChromeWebUIControllerFactory::GetInstance()->CanHandleUrl(url4));
+  LaunchAndWaitForActivationChange(url4);
+  EXPECT_EQ(initial_browser_count + 1, BrowserList::GetInstance()->size());
+  EXPECT_EQ(u"ChromeOS-URLs", ash::window_util::GetActiveWindow()->GetTitle());
+}
+
+// This test will make sure that opening the same system URL multiple times will
+// re-use the existing app.
+IN_PROC_BROWSER_TEST_P(SystemWebAppOpenInAshFromLacrosTests,
+                       LaunchLacrosDeDuplicationtest) {
+  WaitForTestSystemAppInstall();
+
+  // There might be an initial browser from the testing framework.
+  int initial_browser_count = BrowserList::GetInstance()->size();
+
+  // Start an application which uses the OS url handler.
+  LaunchAndWaitForActivationChange(GURL(chrome::kOsUICreditsURL));
+  EXPECT_EQ(initial_browser_count + 1, BrowserList::GetInstance()->size());
+  EXPECT_EQ(u"ChromeOS-URLs", ash::window_util::GetActiveWindow()->GetTitle());
+
+  // Start another application.
+  LaunchAndWaitForActivationChange(GURL(chrome::kOsUIFlagsURL));
+  EXPECT_EQ(initial_browser_count + 2, BrowserList::GetInstance()->size());
+  EXPECT_EQ(u"Flags", ash::window_util::GetActiveWindow()->GetTitle());
+
+  // Start an application of the first type and see that no new app got created.
+  LaunchAndWaitForActivationChange(GURL(chrome::kOsUICreditsURL));
+  EXPECT_EQ(initial_browser_count + 2, BrowserList::GetInstance()->size());
+  EXPECT_EQ(u"ChromeOS-URLs", ash::window_util::GetActiveWindow()->GetTitle());
+}
+
+// This test will make sure that opening a different system URL (other than
+// flags) will open different windows.
+IN_PROC_BROWSER_TEST_P(SystemWebAppOpenInAshFromLacrosTests,
+                       LaunchLacrosCreateNewAppForNewSystemUrl) {
+  WaitForTestSystemAppInstall();
+
+  // There might be an initial browser from the testing framework.
+  int initial_browser_count = BrowserList::GetInstance()->size();
+
+  // Start an application using the OS Url handler.
+  LaunchAndWaitForActivationChange(GURL(chrome::kOsUICreditsURL));
+  EXPECT_EQ(initial_browser_count + 1, BrowserList::GetInstance()->size());
+  EXPECT_EQ(u"ChromeOS-URLs", ash::window_util::GetActiveWindow()->GetTitle());
+
+  // Start another application using the OS Url handler.
+  LaunchAndWaitForActivationChange(GURL(chrome::kOsUIComponentsUrl));
+  EXPECT_EQ(initial_browser_count + 2, BrowserList::GetInstance()->size());
+  EXPECT_EQ(u"ChromeOS-URLs", ash::window_util::GetActiveWindow()->GetTitle());
+}
+
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
 class SystemWebAppManagerCloseFromScriptsTest
     : public SystemWebAppManagerBrowserTest {
  public:
@@ -979,4 +1130,8 @@
 INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
     SystemWebAppNewWindowMenuItemTest);
 #endif
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
+    SystemWebAppOpenInAshFromLacrosTests);
+#endif
 }  // namespace web_app
diff --git a/chrome/browser/ui/webui/app_management/BUILD.gn b/chrome/browser/ui/webui/app_management/BUILD.gn
deleted file mode 100644
index d96d4060..0000000
--- a/chrome/browser/ui/webui/app_management/BUILD.gn
+++ /dev/null
@@ -1,11 +0,0 @@
-# Copyright 2018 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//mojo/public/tools/bindings/mojom.gni")
-
-mojom("mojo_bindings") {
-  sources = [ "app_management.mojom" ]
-
-  public_deps = [ "//components/services/app_service/public/mojom" ]
-}
diff --git a/chrome/browser/ui/webui/app_management/app_management_page_handler.cc b/chrome/browser/ui/webui/app_management/app_management_page_handler.cc
index 3e447716..5801a25e 100644
--- a/chrome/browser/ui/webui/app_management/app_management_page_handler.cc
+++ b/chrome/browser/ui/webui/app_management/app_management_page_handler.cc
@@ -16,7 +16,6 @@
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/webui/app_management/app_management.mojom.h"
 #include "components/services/app_service/public/cpp/app_registry_cache.h"
 #include "components/services/app_service/public/cpp/intent_constants.h"
 #include "components/services/app_service/public/cpp/intent_filter_util.h"
@@ -32,6 +31,7 @@
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
+#include "ui/webui/resources/cr_components/app_management/app_management.mojom.h"
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ash/components/arc/session/connection_holder.h"
@@ -210,6 +210,7 @@
 void AppManagementPageHandler::SetPermission(
     const std::string& app_id,
     apps::mojom::PermissionPtr permission) {
+  DLOG(ERROR) << "set permission";
   apps::AppServiceProxyFactory::GetForProfile(profile_)->SetPermission(
       app_id, std::move(permission));
 }
diff --git a/chrome/browser/ui/webui/app_management/app_management_page_handler.h b/chrome/browser/ui/webui/app_management/app_management_page_handler.h
index 81e50cf1..60047d3 100644
--- a/chrome/browser/ui/webui/app_management/app_management_page_handler.h
+++ b/chrome/browser/ui/webui/app_management/app_management_page_handler.h
@@ -8,7 +8,6 @@
 #include "base/memory/raw_ptr.h"
 #include "base/scoped_observation.h"
 #include "build/chromeos_buildflags.h"
-#include "chrome/browser/ui/webui/app_management/app_management.mojom-forward.h"
 #include "chrome/browser/ui/webui/app_management/app_management_shelf_delegate_chromeos.h"
 #include "components/services/app_service/public/cpp/app_registry_cache.h"
 #include "components/services/app_service/public/cpp/preferred_apps_list_handle.h"
@@ -16,6 +15,7 @@
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
+#include "ui/webui/resources/cr_components/app_management/app_management.mojom-forward.h"
 
 class Profile;
 
diff --git a/chrome/browser/ui/webui/app_management/app_management_shelf_delegate_chromeos.h b/chrome/browser/ui/webui/app_management/app_management_shelf_delegate_chromeos.h
index a8c16ff..1af9a2d3 100644
--- a/chrome/browser/ui/webui/app_management/app_management_shelf_delegate_chromeos.h
+++ b/chrome/browser/ui/webui/app_management/app_management_shelf_delegate_chromeos.h
@@ -9,7 +9,7 @@
 
 #include "ash/public/cpp/shelf_model_observer.h"
 #include "base/memory/raw_ptr.h"
-#include "chrome/browser/ui/webui/app_management/app_management.mojom.h"
+#include "ui/webui/resources/cr_components/app_management/app_management.mojom.h"
 
 class AppManagementPageHandler;
 class ShelfControllerHelper;
diff --git a/chrome/browser/ui/webui/segmentation_internals/segmentation_internals.mojom b/chrome/browser/ui/webui/segmentation_internals/segmentation_internals.mojom
index fdbc1400..ced4e99 100644
--- a/chrome/browser/ui/webui/segmentation_internals/segmentation_internals.mojom
+++ b/chrome/browser/ui/webui/segmentation_internals/segmentation_internals.mojom
@@ -4,17 +4,6 @@
 
 module segmentation_internals.mojom;
 
-// Encapsulates information related to a selected segment. The information
-// includes whether the segment is ready and its optimization target.
-struct SegmentData {
-  // Whether the segment is ready.
-  bool is_ready;
-
-  // Enum value from components/optimization_guide/proto/models.proto. Or -1
-  // if the optimization target is empty.
-  int32 optimization_target;
-};
-
 // Status information about the segmentation service.
 struct ServiceStatus {
   // Whether the service is initialized.
@@ -24,6 +13,15 @@
   int32 intialization_status;
 };
 
+// Information about a segment
+struct SegmentInfo {
+  // Target of the optimization.
+  string optimization_target;
+
+  // Detailed segmentation information.
+  string segment_data;
+};
+
 // Used by the WebUI page to bootstrap bidirectional communication.
 interface PageHandlerFactory {
   // The WebUI calls this method when the page is first initialized.
@@ -33,9 +31,6 @@
 
 // Browser-side handler for requests from WebUI page.
 interface PageHandler {
-  // Gets the segment result for a given key.
-  GetSegment(string key) => (SegmentData result);
-
   // Gets the segmentation service status.
   GetServiceStatus();
 };
@@ -51,6 +46,7 @@
                          int32 status_flag);
 
   // Notifies the page when all segment info becomes available from the service.
-  // |segment_info| is an array of serialized string for all the segment,
-  OnSegmentInfoAvailable(array<string> segment_info);
+  // |segment_info| is an array of all segment information stored in the
+  // database,
+  OnSegmentInfoAvailable(array<SegmentInfo> segment_info);
 };
diff --git a/chrome/browser/ui/webui/segmentation_internals/segmentation_internals_page_handler_impl.cc b/chrome/browser/ui/webui/segmentation_internals/segmentation_internals_page_handler_impl.cc
index 0fa02fc3..cfcca43 100644
--- a/chrome/browser/ui/webui/segmentation_internals/segmentation_internals_page_handler_impl.cc
+++ b/chrome/browser/ui/webui/segmentation_internals/segmentation_internals_page_handler_impl.cc
@@ -7,6 +7,7 @@
 #include "base/bind.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/segmentation_platform/segmentation_platform_service_factory.h"
+#include "components/segmentation_platform/public/segmentation_platform_service.h"
 
 SegmentationInternalsPageHandlerImpl::SegmentationInternalsPageHandlerImpl(
     mojo::PendingReceiver<segmentation_internals::mojom::PageHandler> receiver,
@@ -26,30 +27,6 @@
     service_proxy_->RemoveObserver(this);
 }
 
-void SegmentationInternalsPageHandlerImpl::GetSegment(
-    const std::string& key,
-    GetSegmentCallback callback) {
-  if (!service_proxy_) {
-    OnGetSelectedSegmentDone(std::move(callback),
-                             segmentation_platform::SegmentSelectionResult());
-    return;
-  }
-  service_proxy_->GetSelectedSegment(
-      key, base::BindOnce(
-               &SegmentationInternalsPageHandlerImpl::OnGetSelectedSegmentDone,
-               weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
-}
-
-void SegmentationInternalsPageHandlerImpl::OnGetSelectedSegmentDone(
-    GetSegmentCallback callback,
-    const segmentation_platform::SegmentSelectionResult& result) {
-  auto segment_data = segmentation_internals::mojom::SegmentData::New();
-  segment_data->is_ready = result.is_ready;
-  segment_data->optimization_target =
-      result.segment ? result.segment.value() : -1;
-  std::move(callback).Run(std::move(segment_data));
-}
-
 void SegmentationInternalsPageHandlerImpl::GetServiceStatus() {
   if (service_proxy_) {
     service_proxy_->GetServiceStatus();
@@ -63,6 +40,13 @@
 }
 
 void SegmentationInternalsPageHandlerImpl::OnSegmentInfoAvailable(
-    const std::vector<std::string>& segment_info) {
-  page_->OnSegmentInfoAvailable(segment_info);
+    const std::vector<std::pair<std::string, std::string>>& segment_info) {
+  std::vector<segmentation_internals::mojom::SegmentInfoPtr> available_segments;
+  for (const auto& info : segment_info) {
+    auto segment_data = segmentation_internals::mojom::SegmentInfo::New();
+    segment_data->optimization_target = info.first;
+    segment_data->segment_data = info.second;
+    available_segments.push_back(std::move(segment_data));
+  }
+  page_->OnSegmentInfoAvailable(std::move(available_segments));
 }
diff --git a/chrome/browser/ui/webui/segmentation_internals/segmentation_internals_page_handler_impl.h b/chrome/browser/ui/webui/segmentation_internals/segmentation_internals_page_handler_impl.h
index 6347027..01c96c58 100644
--- a/chrome/browser/ui/webui/segmentation_internals/segmentation_internals_page_handler_impl.h
+++ b/chrome/browser/ui/webui/segmentation_internals/segmentation_internals_page_handler_impl.h
@@ -34,19 +34,14 @@
       const SegmentationInternalsPageHandlerImpl&) = delete;
 
   // segmentation_internals::mojom::PageHandler:
-  void GetSegment(const std::string& key, GetSegmentCallback callback) override;
   void GetServiceStatus() override;
 
  private:
-  // Called when segment result is retrieved.
-  void OnGetSelectedSegmentDone(
-      GetSegmentCallback callback,
-      const segmentation_platform::SegmentSelectionResult& result);
-
   // segmentation_platform::ServiceProxy::Observer overrides.
   void OnServiceStatusChanged(bool is_initialized, int status_flag) override;
   void OnSegmentInfoAvailable(
-      const std::vector<std::string>& segment_info) override;
+      const std::vector<std::pair<std::string, std::string>>& segment_info)
+      override;
 
   mojo::Receiver<segmentation_internals::mojom::PageHandler> receiver_;
   mojo::Remote<segmentation_internals::mojom::Page> page_;
diff --git a/chrome/browser/ui/webui/settings/chromeos/BUILD.gn b/chrome/browser/ui/webui/settings/chromeos/BUILD.gn
index 7452b05..c86ea04 100644
--- a/chrome/browser/ui/webui/settings/chromeos/BUILD.gn
+++ b/chrome/browser/ui/webui/settings/chromeos/BUILD.gn
@@ -6,7 +6,7 @@
   public_deps = [
     "constants:mojom_js",
     "search:mojo_bindings_js",
-    "//chrome/browser/ui/webui/app_management:mojo_bindings_js",
     "//chrome/browser/ui/webui/nearby_share/public/mojom:mojom_js",
+    "//ui/webui/resources/cr_components/app_management:mojo_bindings_js",
   ]
 }
diff --git a/chrome/browser/ui/webui/settings/chromeos/app_management/app_management_page_handler_factory.cc b/chrome/browser/ui/webui/settings/chromeos/app_management/app_management_page_handler_factory.cc
index d4f851d6..fb66843 100644
--- a/chrome/browser/ui/webui/settings/chromeos/app_management/app_management_page_handler_factory.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/app_management/app_management_page_handler_factory.cc
@@ -11,7 +11,6 @@
 #include "base/feature_list.h"
 #include "chrome/browser/apps/app_service/app_icon/app_icon_source.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/webui/app_management/app_management.mojom.h"
 #include "chrome/browser/ui/webui/app_management/app_management_page_handler.h"
 #include "chrome/grit/browser_resources.h"
 #include "chrome/grit/chromium_strings.h"
@@ -24,6 +23,7 @@
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "ui/base/resource/resource_bundle.h"
+#include "ui/webui/resources/cr_components/app_management/app_management.mojom.h"
 
 AppManagementPageHandlerFactory::AppManagementPageHandlerFactory(
     Profile* profile)
diff --git a/chrome/browser/ui/webui/settings/chromeos/app_management/app_management_page_handler_factory.h b/chrome/browser/ui/webui/settings/chromeos/app_management/app_management_page_handler_factory.h
index 948daf715..38af42a 100644
--- a/chrome/browser/ui/webui/settings/chromeos/app_management/app_management_page_handler_factory.h
+++ b/chrome/browser/ui/webui/settings/chromeos/app_management/app_management_page_handler_factory.h
@@ -7,10 +7,10 @@
 
 #include <memory>
 
-#include "chrome/browser/ui/webui/app_management/app_management.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
+#include "ui/webui/resources/cr_components/app_management/app_management.mojom.h"
 
 class Profile;
 
diff --git a/chrome/browser/ui/webui/settings/chromeos/os_settings_ui.h b/chrome/browser/ui/webui/settings/chromeos/os_settings_ui.h
index fc3ecec..67ed6ed7 100644
--- a/chrome/browser/ui/webui/settings/chromeos/os_settings_ui.h
+++ b/chrome/browser/ui/webui/settings/chromeos/os_settings_ui.h
@@ -8,7 +8,6 @@
 #include <memory>
 
 #include "base/time/time.h"
-#include "chrome/browser/ui/webui/app_management/app_management.mojom-forward.h"
 #include "chrome/browser/ui/webui/nearby_share/nearby_share.mojom.h"
 #include "chrome/browser/ui/webui/nearby_share/public/mojom/nearby_share_settings.mojom.h"
 #include "chrome/browser/ui/webui/settings/chromeos/app_management/app_management_page_handler_factory.h"
@@ -22,6 +21,7 @@
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
 #include "ui/webui/mojo_web_ui_controller.h"
+#include "ui/webui/resources/cr_components/app_management/app_management.mojom-forward.h"
 
 namespace user_prefs {
 class PrefRegistrySyncable;
diff --git a/chrome/browser/vr/OWNERS b/chrome/browser/vr/OWNERS
index b3e65b53..87d7a56 100644
--- a/chrome/browser/vr/OWNERS
+++ b/chrome/browser/vr/OWNERS
@@ -2,11 +2,4 @@
 tiborg@chromium.org
 vollick@chromium.org
 
-# WebXR or Browser Test-related
-alcooper@chromium.org
-
-# WebXR-related.
-klausw@chromium.org
-
-# Browser Test-related.
-bsheedy@chromium.org
+file://components/webxr/OWNERS
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index f5fa5a9..d080071d 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1639655564-a5a5c0d33ae436f2a30693fdb9d2306eb3190a81.profdata
+chrome-linux-main-1639677539-99948628de3a78f947b1b4d59843ea500cf91b69.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index defc11df..459c953 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1639655564-52e53ef7b47860477f68cddce2543fb594359e09.profdata
+chrome-mac-main-1639677539-997cb311ab2c03187a535e6bd85a4c81ef7ed1a7.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 87c7f25..8e5ee8c 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1639666549-84a7c26e97b827581984ac991aa2569f15b40ef8.profdata
+chrome-win32-main-1639677539-d3b89857331ec31b3fb9a2f0baec586c99a0d583.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index db20818..5e79c5e 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1639655564-53960f02f5072242e6980160d511646ba3082008.profdata
+chrome-win64-main-1639677539-e35fba550ec3048b103171c0e33ceac25ceda995.profdata
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index d52f939..7e90011 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3443,6 +3443,8 @@
         "../browser/chromeos/policy/dlp/dlp_rules_manager_impl_browsertest.cc",
         "../browser/chromeos/policy/dlp/dlp_rules_manager_test_utils.cc",
         "../browser/chromeos/policy/dlp/dlp_rules_manager_test_utils.h",
+        "../browser/chromeos/policy/dlp/mock_dlp_content_manager.cc",
+        "../browser/chromeos/policy/dlp/mock_dlp_content_manager.h",
         "../browser/chromeos/policy/dlp/mock_dlp_content_observer.cc",
         "../browser/chromeos/policy/dlp/mock_dlp_content_observer.h",
         "../browser/chromeos/policy/dlp/mock_dlp_rules_manager.cc",
@@ -3511,6 +3513,7 @@
         "../browser/ui/browser_navigator_browsertest_chromeos.cc",
         "../browser/ui/extensions/application_launch_browsertest.cc",
         "../browser/ui/settings_window_manager_browsertest_chromeos.cc",
+        "../browser/ui/sharing_hub/sharing_hub_bubble_controller_chromeos_browsertest.cc",
         "../browser/ui/views/apps/app_dialog/app_dialog_view_browsertest.cc",
         "../browser/ui/views/apps/app_dialog/app_uninstall_dialog_view_browsertest.cc",
         "../browser/ui/views/apps/chrome_native_app_window_views_aura_ash_browsertest.cc",
@@ -6484,6 +6487,8 @@
       "../browser/chromeos/policy/dlp/dlp_rules_manager_impl_unittest.cc",
       "../browser/chromeos/policy/dlp/dlp_rules_manager_test_utils.cc",
       "../browser/chromeos/policy/dlp/dlp_rules_manager_test_utils.h",
+      "../browser/chromeos/policy/dlp/mock_dlp_content_manager.cc",
+      "../browser/chromeos/policy/dlp/mock_dlp_content_manager.h",
       "../browser/chromeos/policy/dlp/mock_dlp_content_observer.cc",
       "../browser/chromeos/policy/dlp/mock_dlp_content_observer.h",
       "../browser/chromeos/policy/dlp/mock_dlp_rules_manager.cc",
@@ -8607,7 +8612,13 @@
           "../browser/ui/views/keyboard_access_browsertest.cc",
         ]
       }
-      if (!is_chromeos_ash) {
+      if (is_chromeos_ash) {
+        deps += [
+          "//ash:ash",
+          "//chrome/browser/ash/crosapi:crosapi",
+          "//ui/wm/public:public",
+        ]
+      } else {
         sources += [ "../browser/ui/views/global_media_controls/media_dialog_view_interactive_browsertest.cc" ]
         deps += [
           "../browser/media/router:test_support",
@@ -8618,7 +8629,6 @@
       if (!is_android) {
         sources += [ "../browser/ui/web_applications/test/system_web_app_interactive_uitest.cc" ]
       }
-
       if (use_aura) {
         sources += [ "../browser/ui/views/tooltip/tooltip_browsertest.cc" ]
       }
diff --git a/chrome/test/base/testing_profile.cc b/chrome/test/base/testing_profile.cc
index 6abe13e..c7dadb2 100644
--- a/chrome/test/base/testing_profile.cc
+++ b/chrome/test/base/testing_profile.cc
@@ -750,6 +750,7 @@
   testing_prefs_ = new sync_preferences::TestingPrefServiceSyncable(
       /*managed_prefs=*/new TestingPrefStore, supervised_user_pref_store_,
       /*extension_prefs=*/new TestingPrefStore,
+      /*standalone_browser_prefs=*/new TestingPrefStore,
       /*user_prefs=*/new TestingPrefStore,
       /*recommended_prefs=*/new TestingPrefStore,
       new user_prefs::PrefRegistrySyncable, new PrefNotifierImpl);
diff --git a/chrome/test/data/webui/chromeos/diagnostics/input_list_test.js b/chrome/test/data/webui/chromeos/diagnostics/input_list_test.js
index b0919b7e..5e73a2c 100644
--- a/chrome/test/data/webui/chromeos/diagnostics/input_list_test.js
+++ b/chrome/test/data/webui/chromeos/diagnostics/input_list_test.js
@@ -4,7 +4,7 @@
 
 import 'chrome://diagnostics/input_list.js';
 
-import {ConnectionType, KeyboardInfo, MechanicalLayout, NumberPadPresence, PhysicalLayout, TouchDeviceInfo, TouchDeviceType} from 'chrome://diagnostics/diagnostics_types.js';
+import {ConnectionType, KeyboardInfo, MechanicalLayout, NumberPadPresence, PhysicalLayout, TopRowKey, TouchDeviceInfo, TouchDeviceType} from 'chrome://diagnostics/diagnostics_types.js';
 import {fakeKeyboards, fakeTouchDevices} from 'chrome://diagnostics/fake_data.js';
 import {FakeInputDataProvider} from 'chrome://diagnostics/fake_input_data_provider.js';
 import {setInputDataProviderForTesting} from 'chrome://diagnostics/mojo_interface_provider.js';
@@ -87,6 +87,12 @@
       mechanicalLayout: MechanicalLayout.kUnknown,
       hasAssistantKey: false,
       numberPadPresent: NumberPadPresence.kUnknown,
+      topRowKeys: [
+        TopRowKey.kBack, TopRowKey.kForward, TopRowKey.kRefresh,
+        TopRowKey.kFullscreen, TopRowKey.kOverview,
+        TopRowKey.kScreenBrightnessDown, TopRowKey.kScreenBrightnessUp,
+        TopRowKey.kVolumeMute, TopRowKey.kVolumeDown, TopRowKey.kVolumeUp
+      ],
     };
     let keyboardCard;
     return initializeInputList()
diff --git a/chrome/test/data/webui/chromeos/shimless_rma/reimaging_provisioning_page_test.js b/chrome/test/data/webui/chromeos/shimless_rma/reimaging_provisioning_page_test.js
index 4b2b57be..78320d8 100644
--- a/chrome/test/data/webui/chromeos/shimless_rma/reimaging_provisioning_page_test.js
+++ b/chrome/test/data/webui/chromeos/shimless_rma/reimaging_provisioning_page_test.js
@@ -6,11 +6,19 @@
 import {FakeShimlessRmaService} from 'chrome://shimless-rma/fake_shimless_rma_service.js';
 import {setShimlessRmaServiceForTesting} from 'chrome://shimless-rma/mojo_interface_provider.js';
 import {ReimagingProvisioningPage} from 'chrome://shimless-rma/reimaging_provisioning_page.js';
+import {ShimlessRma} from 'chrome://shimless-rma/shimless_rma.js';
 import {ProvisioningStatus} from 'chrome://shimless-rma/shimless_rma_types.js';
 import {assertDeepEquals, assertEquals, assertFalse, assertTrue} from '../../chai_assert.js';
 import {flushTasks} from '../../test_util.js';
 
 export function reimagingProvisioningPageTest() {
+  /**
+   * ShimlessRma is needed to handle the 'transition-state' event used
+   * when handling calibration overall progress signals.
+   * @type {?ShimlessRma}
+   */
+  let shimless_rma_component = null;
+
   /** @type {?ReimagingProvisioningPage} */
   let component = null;
 
@@ -27,6 +35,8 @@
   });
 
   teardown(() => {
+    shimless_rma_component.remove();
+    shimless_rma_component = null;
     component.remove();
     component = null;
     service.reset();
@@ -38,6 +48,11 @@
   function initializeWaitForProvisioningPage() {
     assertFalse(!!component);
 
+    shimless_rma_component =
+        /** @type {!ShimlessRma} */ (document.createElement('shimless-rma'));
+    assertTrue(!!shimless_rma_component);
+    document.body.appendChild(shimless_rma_component);
+
     component = /** @type {!ReimagingProvisioningPage} */ (
         document.createElement('reimaging-provisioning-page'));
     assertTrue(!!component);
@@ -103,4 +118,52 @@
 
     assertDeepEquals(savedResult, expectedResult);
   });
+
+  test('ProvisioningFailedBlockingRetry', async () => {
+    const resolver = new PromiseResolver();
+    await initializeWaitForProvisioningPage();
+
+    const retryButton =
+        component.shadowRoot.querySelector('#retryProvisioningButton');
+    assertTrue(retryButton.hidden);
+
+    let callCount = 0;
+    service.retryProvisioning = () => {
+      callCount++;
+      return resolver.promise;
+    };
+    service.triggerProvisioningObserver(
+        ProvisioningStatus.kFailedBlocking, 1.0, 0);
+    await flushTasks();
+
+    assertFalse(retryButton.hidden);
+    retryButton.click();
+
+    await flushTasks();
+    assertEquals(1, callCount);
+  });
+
+  test('ProvisioningFailedNonBlockingRetry', async () => {
+    const resolver = new PromiseResolver();
+    await initializeWaitForProvisioningPage();
+
+    const retryButton =
+        component.shadowRoot.querySelector('#retryProvisioningButton');
+    assertTrue(retryButton.hidden);
+
+    let callCount = 0;
+    service.retryProvisioning = () => {
+      callCount++;
+      return resolver.promise;
+    };
+    service.triggerProvisioningObserver(
+        ProvisioningStatus.kFailedNonBlocking, 1.0, 0);
+    await flushTasks();
+
+    assertFalse(retryButton.hidden);
+    retryButton.click();
+
+    await flushTasks();
+    assertEquals(1, callCount);
+  });
 }
diff --git a/chrome/test/data/webui/settings/chromeos/app_management/permission_item_test.js b/chrome/test/data/webui/settings/chromeos/app_management/permission_item_test.js
index a6289d31..73ede3af 100644
--- a/chrome/test/data/webui/settings/chromeos/app_management/permission_item_test.js
+++ b/chrome/test/data/webui/settings/chromeos/app_management/permission_item_test.js
@@ -31,12 +31,11 @@
       ])
     };
 
-    // Add an arc app, and make it the currently selected app.
+    // Add an arc app, and pass it to permissionItem.
     const app = await fakeHandler.addApp(null, arcOptions);
-    app_management.AppManagementStore.getInstance().dispatch(
-        app_management.actions.updateSelectedAppId(app.id));
 
     permissionItem = document.createElement('app-management-permission-item');
+    permissionItem.app_ = app;
   });
 
   test('Toggle permission', async () => {
@@ -44,13 +43,15 @@
 
     replaceBody(permissionItem);
     await fakeHandler.flushPipesForTesting();
-    assertTrue(app_management.util.getPermissionValueBool(
+    assertTrue(getPermissionValueBool(
         permissionItem.app_, permissionItem.permissionType));
 
     permissionItem.click();
     await test_util.flushTasks();
     await fakeHandler.flushPipesForTesting();
-    assertFalse(app_management.util.getPermissionValueBool(
-        permissionItem.app_, permissionItem.permissionType));
+    // Store gets updated permission.
+    const storeData = app_management.AppManagementStore.getInstance().data;
+    assertFalse(getPermissionValueBool(
+      storeData.apps[permissionItem.app_.id], permissionItem.permissionType));
   });
 });
diff --git a/chrome/test/data/webui/settings/chromeos/app_management/test_util.js b/chrome/test/data/webui/settings/chromeos/app_management/test_util.js
index ed67cae4..27ae275 100644
--- a/chrome/test/data/webui/settings/chromeos/app_management/test_util.js
+++ b/chrome/test/data/webui/settings/chromeos/app_management/test_util.js
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 // clang-format off
-// #import {BrowserProxy, FakePageHandler} from 'chrome://os-settings/chromeos/os_settings.js';
+// #import {BrowserProxy, FakePageHandler, AppManagementComponentBrowserProxy} from 'chrome://os-settings/chromeos/os_settings.js';
 // #import {TestAppManagementStore} from './test_store.m.js';
 // clang-format on
 
@@ -28,6 +28,9 @@
       browserProxy.callbackRouter.$.bindNewPipeAndPassRemote());
   browserProxy.handler = fakeHandler.getRemote();
 
+  const componentBrowserProxy =
+      AppManagementComponentBrowserProxy.getInstance();
+  componentBrowserProxy.handler = fakeHandler;
   return fakeHandler;
 }
 
@@ -120,4 +123,4 @@
   }
   // The element is rendered and display !== 'none'
   return false;
-}
\ No newline at end of file
+}
diff --git a/chrome/updater/updater.cc b/chrome/updater/updater.cc
index 5b44c0b..f5f5622 100644
--- a/chrome/updater/updater.cc
+++ b/chrome/updater/updater.cc
@@ -213,8 +213,8 @@
   VLOG(1) << "Version " << kUpdaterVersion
           << ", command line: " << command_line->GetCommandLineString();
   const int retval = HandleUpdaterCommands(updater_scope, command_line);
-  DVLOG(1) << __func__ << " (--" << GetUpdaterCommand(command_line) << ")"
-           << " returned " << retval << ".";
+  VLOG(1) << __func__ << " (--" << GetUpdaterCommand(command_line) << ")"
+          << " returned " << retval << ".";
   return retval;
 }
 
diff --git a/chromeos/chromeos_strings.grd b/chromeos/chromeos_strings.grd
index 994662f5..22a5581 100644
--- a/chromeos/chromeos_strings.grd
+++ b/chromeos/chromeos_strings.grd
@@ -545,7 +545,7 @@
       <message name="IDS_SCANNING_APP_PNG_OPTION_TEXT" desc="The text displayed for the PNG file type option.">
         PNG
       </message>
-      <!-- TODO (kmoed): Remove "translateable="false"" once string is confirmed. -->
+      <!-- TODO (gavinwill): Remove "translateable="false"" once string is confirmed. -->
       <message translateable="false" name="IDS_SCANNING_APP_SEARCHABLE_PDF_OPTION_TEXT" desc="The text displayed for the Searchable PDF file type option. This option utilizes the Object Character Recognition (OCR) service to generate a PDF file whose text has been added as a searchable layer.">
         Searchable PDF
       </message>
@@ -2264,6 +2264,9 @@
       <message name="IDS_SHIMLESS_RMA_PROVISIONING_FAILED_NON_BLOCKING" translateable="false" desc="Message to display when provisioning failed, but RMA can continue.">
         Failed, non blocking.
       </message>
+      <message name="IDS_SHIMLESS_RMA_PROVISIONING_FAILED_RETRY_BUTTON_LABEL" translateable="false" desc="The label for the button to retry provisioning.">
+        Retry provisioning
+      </message>
       <!-- Repair complete page -->
       <message name="IDS_SHIMLESS_RMA_REPAIR_COMPLETED" desc="Title for the page shown when the RMA process repair process is completed.">
         Repair completed
diff --git a/chromeos/profiles/orderfile.newest.txt b/chromeos/profiles/orderfile.newest.txt
index 05233f5..ef4ad5d 100644
--- a/chromeos/profiles/orderfile.newest.txt
+++ b/chromeos/profiles/orderfile.newest.txt
@@ -1 +1 @@
-chromeos-chrome-orderfile-field-98-4744.1-1639393599-benchmark-98.0.4758.7-r1.orderfile.xz
+chromeos-chrome-orderfile-field-98-4744.1-1639393599-benchmark-98.0.4758.8-r1.orderfile.xz
diff --git a/chromeos/services/bluetooth_config/BUILD.gn b/chromeos/services/bluetooth_config/BUILD.gn
index 1e23d45..c8ffa64 100644
--- a/chromeos/services/bluetooth_config/BUILD.gn
+++ b/chromeos/services/bluetooth_config/BUILD.gn
@@ -157,6 +157,7 @@
     "//ash/constants",
     "//base",
     "//base/test:test_support",
+    "//chromeos/services/bluetooth_config/public/cpp:unit_tests",
     "//components/session_manager/core",
     "//components/sync_preferences:test_support",
     "//components/user_manager:test_support",
diff --git a/chromeos/services/bluetooth_config/device_pairing_handler.cc b/chromeos/services/bluetooth_config/device_pairing_handler.cc
index 39002a0..878c39d 100644
--- a/chromeos/services/bluetooth_config/device_pairing_handler.cc
+++ b/chromeos/services/bluetooth_config/device_pairing_handler.cc
@@ -6,7 +6,10 @@
 
 #include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/time/default_clock.h"
 #include "components/device_event_log/device_event_log.h"
+#include "device/bluetooth/bluetooth_common.h"
+#include "device/bluetooth/chromeos/bluetooth_utils.h"
 
 namespace chromeos {
 namespace bluetooth_config {
@@ -29,6 +32,47 @@
        passkey_string});
 }
 
+device::BluetoothTransport GetBluetoothTransport(
+    device::BluetoothTransport type) {
+  switch (type) {
+    case device::BLUETOOTH_TRANSPORT_CLASSIC:
+      return device::BLUETOOTH_TRANSPORT_CLASSIC;
+    case device::BLUETOOTH_TRANSPORT_LE:
+      return device::BLUETOOTH_TRANSPORT_LE;
+    case device::BLUETOOTH_TRANSPORT_DUAL:
+      return device::BLUETOOTH_TRANSPORT_DUAL;
+    default:
+      return device::BLUETOOTH_TRANSPORT_INVALID;
+  }
+}
+
+mojom::PairingResult GetPairingResult(
+    absl::optional<device::ConnectionFailureReason> failure_reason) {
+  if (!failure_reason) {
+    return mojom::PairingResult::kSuccess;
+  }
+
+  switch (failure_reason.value()) {
+    case device::ConnectionFailureReason::kAuthTimeout:
+      FALLTHROUGH;
+    case device::ConnectionFailureReason::kAuthFailed:
+      return mojom::PairingResult::kAuthFailed;
+
+    case device::ConnectionFailureReason::kUnknownError:
+      FALLTHROUGH;
+    case device::ConnectionFailureReason::kSystemError:
+      FALLTHROUGH;
+    case device::ConnectionFailureReason::kFailed:
+      FALLTHROUGH;
+    case device::ConnectionFailureReason::kUnknownConnectionError:
+      FALLTHROUGH;
+    case device::ConnectionFailureReason::kUnsupportedDevice:
+      FALLTHROUGH;
+    case device::ConnectionFailureReason::kNotConnectable:
+      return mojom::PairingResult::kNonAuthFailure;
+  }
+}
+
 }  // namespace
 
 DevicePairingHandler::DevicePairingHandler(
@@ -50,7 +94,7 @@
         << "Could not cancel pairing for device to due device no longer being "
            "found, identifier: "
         << current_pairing_device_id_;
-    FinishCurrentPairingRequest(mojom::PairingResult::kAuthFailed);
+    FinishCurrentPairingRequest(device::ConnectionFailureReason::kAuthFailed);
     return;
   }
   device->CancelPairing();
@@ -72,6 +116,7 @@
   // There should only be one PairDevice request at a time.
   CHECK(current_pairing_device_id_.empty());
 
+  pairing_start_timestamp_ = base::Time();
   pair_device_callback_ = std::move(callback);
 
   delegate_.reset();
@@ -84,7 +129,7 @@
     BLUETOOTH_LOG(ERROR) << "Pairing failed due to Bluetooth not being "
                             "enabled, device identifier: "
                          << device_id;
-    FinishCurrentPairingRequest(mojom::PairingResult::kNonAuthFailure);
+    FinishCurrentPairingRequest(device::ConnectionFailureReason::kFailed);
     return;
   }
 
@@ -95,7 +140,7 @@
     BLUETOOTH_LOG(ERROR) << "Pairing failed due to device not being "
                             "found, identifier: "
                          << device_id;
-    FinishCurrentPairingRequest(mojom::PairingResult::kNonAuthFailure);
+    FinishCurrentPairingRequest(device::ConnectionFailureReason::kFailed);
     return;
   }
 
@@ -168,7 +213,7 @@
 void DevicePairingHandler::OnDeviceConnect(
     absl::optional<device::BluetoothDevice::ConnectErrorCode> error_code) {
   if (!error_code.has_value()) {
-    FinishCurrentPairingRequest(mojom::PairingResult::kSuccess);
+    FinishCurrentPairingRequest(absl::nullopt);
     NotifyFinished();
     return;
   }
@@ -182,21 +227,26 @@
     case ErrorCode::ERROR_AUTH_FAILED:
       FALLTHROUGH;
     case ErrorCode::ERROR_AUTH_REJECTED:
-      FALLTHROUGH;
+      FinishCurrentPairingRequest(device::ConnectionFailureReason::kAuthFailed);
+      return;
     case ErrorCode::ERROR_AUTH_TIMEOUT:
-      FinishCurrentPairingRequest(mojom::PairingResult::kAuthFailed);
+      FinishCurrentPairingRequest(
+          device::ConnectionFailureReason::kAuthTimeout);
       return;
 
     case ErrorCode::ERROR_FAILED:
       FALLTHROUGH;
     case ErrorCode::ERROR_INPROGRESS:
-      FALLTHROUGH;
-    case ErrorCode::ERROR_UNKNOWN:
-      FALLTHROUGH;
-    case ErrorCode::ERROR_UNSUPPORTED_DEVICE:
-      FinishCurrentPairingRequest(mojom::PairingResult::kNonAuthFailure);
+      FinishCurrentPairingRequest(device::ConnectionFailureReason::kFailed);
       return;
-
+    case ErrorCode::ERROR_UNKNOWN:
+      FinishCurrentPairingRequest(
+          device::ConnectionFailureReason::kUnknownError);
+      return;
+    case ErrorCode::ERROR_UNSUPPORTED_DEVICE:
+      FinishCurrentPairingRequest(
+          device::ConnectionFailureReason::kUnsupportedDevice);
+      return;
     default:
       BLUETOOTH_LOG(ERROR) << "Error code is invalid.";
       break;
@@ -210,7 +260,7 @@
         << "OnRequestPinCode failed due to device no longer being "
            "found, identifier: "
         << current_pairing_device_id_;
-    FinishCurrentPairingRequest(mojom::PairingResult::kNonAuthFailure);
+    FinishCurrentPairingRequest(device::ConnectionFailureReason::kFailed);
     return;
   }
 
@@ -224,7 +274,7 @@
         << "OnRequestPasskey failed due to device no longer being "
            "found, identifier: "
         << current_pairing_device_id_;
-    FinishCurrentPairingRequest(mojom::PairingResult::kNonAuthFailure);
+    FinishCurrentPairingRequest(device::ConnectionFailureReason::kFailed);
     return;
   }
 
@@ -245,7 +295,7 @@
         << "OnConfirmPairing failed due to device no longer being "
            "found, identifier: "
         << current_pairing_device_id_;
-    FinishCurrentPairingRequest(mojom::PairingResult::kNonAuthFailure);
+    FinishCurrentPairingRequest(device::ConnectionFailureReason::kFailed);
     return;
   }
 
@@ -256,9 +306,19 @@
 }
 
 void DevicePairingHandler::FinishCurrentPairingRequest(
-    mojom::PairingResult result) {
+    absl::optional<device::ConnectionFailureReason> failure_reason) {
+  device::BluetoothDevice* device = FindDevice(current_pairing_device_id_);
   current_pairing_device_id_.clear();
-  std::move(pair_device_callback_).Run(result);
+
+  device::BluetoothTransport transport =
+      device ? device->GetType()
+             : device::BluetoothTransport::BLUETOOTH_TRANSPORT_INVALID;
+
+  device::RecordPairingResult(
+      failure_reason, GetBluetoothTransport(transport),
+      base::DefaultClock::GetInstance()->Now() - pairing_start_timestamp_);
+
+  std::move(pair_device_callback_).Run(GetPairingResult(failure_reason));
 }
 
 void DevicePairingHandler::OnDelegateDisconnect() {
diff --git a/chromeos/services/bluetooth_config/device_pairing_handler.h b/chromeos/services/bluetooth_config/device_pairing_handler.h
index 61e5572..c9d0e42 100644
--- a/chromeos/services/bluetooth_config/device_pairing_handler.h
+++ b/chromeos/services/bluetooth_config/device_pairing_handler.h
@@ -11,6 +11,7 @@
 #include "chromeos/services/bluetooth_config/adapter_state_controller.h"
 #include "chromeos/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom.h"
 #include "device/bluetooth/bluetooth_device.h"
+#include "device/bluetooth/chromeos/bluetooth_utils.h"
 #include "mojo/public/cpp/bindings/remote.h"
 
 namespace chromeos {
@@ -86,9 +87,10 @@
   void OnRequestPasskey(const std::string& passkey);
   void OnConfirmPairing(bool confirmed);
 
-  // Invokes |pair_device_callback_| with |result| and resets
-  // this class' state to be ready for another pairing request.
-  void FinishCurrentPairingRequest(mojom::PairingResult result);
+  // Invokes |pair_device_callback_| and resets this class' state to be ready
+  // for another pairing request.
+  void FinishCurrentPairingRequest(
+      absl::optional<device::ConnectionFailureReason> failure_reason);
 
   void OnDelegateDisconnect();
 
@@ -97,6 +99,8 @@
   // Flushes queued Mojo messages in unit tests.
   void FlushForTesting();
 
+  base::Time pairing_start_timestamp_;
+
   // The identifier of the device currently being paired with. This is null if
   // there is no in-progress pairing attempt.
   std::string current_pairing_device_id_;
diff --git a/chromeos/services/bluetooth_config/device_pairing_handler_impl_unittest.cc b/chromeos/services/bluetooth_config/device_pairing_handler_impl_unittest.cc
index 2cd20d4..171f039 100644
--- a/chromeos/services/bluetooth_config/device_pairing_handler_impl_unittest.cc
+++ b/chromeos/services/bluetooth_config/device_pairing_handler_impl_unittest.cc
@@ -6,10 +6,12 @@
 
 #include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
 #include "chromeos/services/bluetooth_config/fake_adapter_state_controller.h"
 #include "chromeos/services/bluetooth_config/fake_device_pairing_delegate.h"
 #include "chromeos/services/bluetooth_config/fake_key_entered_handler.h"
+#include "device/bluetooth/chromeos/bluetooth_utils.h"
 #include "device/bluetooth/test/mock_bluetooth_adapter.h"
 #include "device/bluetooth/test/mock_bluetooth_device.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -70,6 +72,18 @@
 
   void DestroyHandler() { device_pairing_handler_.reset(); }
 
+  void CheckPairingHistograms(device::BluetoothTransportType type,
+                              int type_count,
+                              int failure_count,
+                              int success_count) {
+    histogram_tester.ExpectBucketCount(
+        "Bluetooth.ChromeOS.Pairing.TransportType", type, type_count);
+    histogram_tester.ExpectBucketCount("Bluetooth.ChromeOS.Pairing.Result",
+                                       false, failure_count);
+    histogram_tester.ExpectBucketCount("Bluetooth.ChromeOS.Pairing.Result",
+                                       true, success_count);
+  }
+
   void AddDevice(std::string* id_out,
                  AuthType auth_type,
                  uint32_t passkey = kDefaultPinCodeNum) {
@@ -141,6 +155,10 @@
         .WillByDefault(testing::Invoke(
             [this](const uint32_t passkey) { received_passkey_ = passkey; }));
 
+    ON_CALL(*mock_device, GetType()).WillByDefault(testing::Invoke([]() {
+      return device::BluetoothTransport::BLUETOOTH_TRANSPORT_CLASSIC;
+    }));
+
     mock_devices_.push_back(std::move(mock_device));
   }
 
@@ -200,6 +218,7 @@
   }
   std::string received_pin_code() const { return received_pin_code_; }
   uint32_t received_passkey() const { return received_passkey_; }
+  base::HistogramTester histogram_tester;
 
  private:
   std::vector<const device::BluetoothDevice*> GetMockDevices() {
@@ -256,6 +275,10 @@
   EXPECT_EQ(pairing_result(), mojom::PairingResult::kNonAuthFailure);
   EXPECT_EQ(num_pairing_attempt_finished_calls(), 0u);
 
+  CheckPairingHistograms(device::BluetoothTransportType::kClassic,
+                         /*type_count=*/1, /*failure_count=*/1,
+                         /*success_count=*/0);
+
   std::unique_ptr<FakeDevicePairingDelegate> delegate2 = PairDevice(device_id2);
   EXPECT_TRUE(delegate2->IsMojoPipeConnected());
 
@@ -263,6 +286,10 @@
   InvokePendingConnectCallback(/*success=*/true);
   EXPECT_EQ(pairing_result(), mojom::PairingResult::kSuccess);
   EXPECT_EQ(num_pairing_attempt_finished_calls(), 1u);
+
+  CheckPairingHistograms(device::BluetoothTransportType::kClassic,
+                         /*type_count=*/2, /*failure_count=*/1,
+                         /*success_count=*/1);
 }
 
 TEST_F(DevicePairingHandlerImplTest, DisableBluetoothBeforePairing) {
@@ -278,6 +305,11 @@
   EXPECT_FALSE(HasPendingConnectCallback());
   EXPECT_EQ(pairing_result(), mojom::PairingResult::kNonAuthFailure);
   EXPECT_EQ(num_pairing_attempt_finished_calls(), 0u);
+
+  // Pairing result metric is only recorded for valid transport types.
+  CheckPairingHistograms(device::BluetoothTransportType::kInvalid,
+                         /*type_count=*/1, /*failure_count=*/0,
+                         /*success_count=*/0);
 }
 
 TEST_F(DevicePairingHandlerImplTest, DisableBluetoothDuringPairing) {
@@ -295,6 +327,9 @@
   EXPECT_EQ(num_cancel_pairing_calls(), 1u);
   EXPECT_EQ(pairing_result(), mojom::PairingResult::kAuthFailed);
   EXPECT_EQ(num_pairing_attempt_finished_calls(), 0u);
+  CheckPairingHistograms(device::BluetoothTransportType::kClassic,
+                         /*type_count=*/1, /*failure_count=*/1,
+                         /*success_count=*/0);
 }
 
 TEST_F(DevicePairingHandlerImplTest, DestroyHandlerBeforeConnectFinishes) {
@@ -311,6 +346,9 @@
 
   // Destroying the handler should call OnPairingAttemptFinished();
   EXPECT_EQ(num_pairing_attempt_finished_calls(), 1u);
+  CheckPairingHistograms(device::BluetoothTransportType::kClassic,
+                         /*type_count=*/1, /*failure_count=*/1,
+                         /*success_count=*/0);
 }
 
 TEST_F(DevicePairingHandlerImplTest, DestroyHandlerAfterConnectFinishes) {
@@ -332,6 +370,9 @@
 
   // Destroying the handler shouldn't call OnPairingAttemptFinished();
   EXPECT_EQ(num_pairing_attempt_finished_calls(), 1u);
+  CheckPairingHistograms(device::BluetoothTransportType::kClassic,
+                         /*type_count=*/1, /*failure_count=*/0,
+                         /*success_count=*/1);
 }
 
 TEST_F(DevicePairingHandlerImplTest, DisconnectDelegateBeforeConnectFinishes) {
@@ -349,6 +390,9 @@
 
   // Disconnecting the pipe should not call OnPairingAttemptFinished().
   EXPECT_EQ(num_pairing_attempt_finished_calls(), 0u);
+  CheckPairingHistograms(device::BluetoothTransportType::kClassic,
+                         /*type_count=*/1, /*failure_count=*/1,
+                         /*success_count=*/0);
 }
 
 TEST_F(DevicePairingHandlerImplTest,
@@ -369,6 +413,9 @@
 
   // Disconnecting the pipe should not call OnPairingAttemptFinished().
   EXPECT_EQ(num_pairing_attempt_finished_calls(), 0u);
+  CheckPairingHistograms(device::BluetoothTransportType::kInvalid,
+                         /*type_count=*/1, /*failure_count=*/0,
+                         /*success_count=*/0);
 }
 
 TEST_F(DevicePairingHandlerImplTest,
@@ -389,6 +436,9 @@
 
   // Disconnecting the pipe should not call OnPairingAttemptFinished().
   EXPECT_EQ(num_pairing_attempt_finished_calls(), 0u);
+  CheckPairingHistograms(device::BluetoothTransportType::kClassic,
+                         /*type_count=*/1, /*failure_count=*/1,
+                         /*success_count=*/0);
 }
 
 TEST_F(DevicePairingHandlerImplTest, PairDeviceNotFound) {
@@ -396,6 +446,9 @@
 
   EXPECT_FALSE(HasPendingConnectCallback());
   EXPECT_EQ(pairing_result(), mojom::PairingResult::kNonAuthFailure);
+  CheckPairingHistograms(device::BluetoothTransportType::kInvalid,
+                         /*type_count=*/1, /*failure_count=*/0,
+                         /*success_count=*/0);
 }
 
 TEST_F(DevicePairingHandlerImplTest, PairAuthRequestPinCode) {
@@ -410,6 +463,9 @@
 
   InvokePendingConnectCallback(/*success=*/false);
   EXPECT_EQ(pairing_result(), mojom::PairingResult::kAuthFailed);
+  CheckPairingHistograms(device::BluetoothTransportType::kClassic,
+                         /*type_count=*/1, /*failure_count=*/1,
+                         /*success_count=*/0);
 }
 
 TEST_F(DevicePairingHandlerImplTest, PairAuthRequestPinCodeRemoveDevice) {
@@ -427,6 +483,9 @@
   // Failure result should be returned.
   EXPECT_TRUE(received_pin_code().empty());
   EXPECT_EQ(pairing_result(), mojom::PairingResult::kNonAuthFailure);
+  CheckPairingHistograms(device::BluetoothTransportType::kInvalid,
+                         /*type_count=*/1, /*failure_count=*/0,
+                         /*success_count=*/0);
 }
 
 TEST_F(DevicePairingHandlerImplTest, PairAuthRequestPasskey) {
@@ -441,6 +500,9 @@
 
   InvokePendingConnectCallback(/*success=*/false);
   EXPECT_EQ(pairing_result(), mojom::PairingResult::kAuthFailed);
+  CheckPairingHistograms(device::BluetoothTransportType::kClassic,
+                         /*type_count=*/1, /*failure_count=*/1,
+                         /*success_count=*/0);
 }
 
 TEST_F(DevicePairingHandlerImplTest, PairAuthRequestPasskeyRemoveDevice) {
@@ -458,6 +520,9 @@
   // Failure result should be returned.
   EXPECT_EQ(received_passkey(), kUninitializedPasskey);
   EXPECT_EQ(pairing_result(), mojom::PairingResult::kNonAuthFailure);
+  CheckPairingHistograms(device::BluetoothTransportType::kInvalid,
+                         /*type_count=*/1, /*failure_count=*/0,
+                         /*success_count=*/0);
 }
 
 TEST_F(DevicePairingHandlerImplTest, PairAuthRequestPasskeyInvalidKey) {
@@ -479,6 +544,9 @@
   // CancelPairing() should not be called again since we already cancelled the
   // pairing.
   EXPECT_EQ(num_cancel_pairing_calls(), 1u);
+  CheckPairingHistograms(device::BluetoothTransportType::kClassic,
+                         /*type_count=*/1, /*failure_count=*/1,
+                         /*success_count=*/0);
 }
 
 TEST_F(DevicePairingHandlerImplTest, PairAuthDisplayPinCode) {
@@ -497,6 +565,9 @@
 
   InvokePendingConnectCallback(/*success=*/false);
   EXPECT_EQ(pairing_result(), mojom::PairingResult::kAuthFailed);
+  CheckPairingHistograms(device::BluetoothTransportType::kClassic,
+                         /*type_count=*/1, /*failure_count=*/1,
+                         /*success_count=*/0);
 }
 
 TEST_F(DevicePairingHandlerImplTest, PairAuthDisplayPinCodeDisconnectHandler) {
@@ -515,6 +586,11 @@
   EnterKeys(device_id, /*num_keys_entered=*/6u);
   // Number of keys entered should still be zero since pipe is disconnected.
   EXPECT_EQ(delegate->key_entered_handler()->num_keys_entered(), 0);
+
+  // Metrics is not recorded because pairing did not finish.
+  CheckPairingHistograms(device::BluetoothTransportType::kInvalid,
+                         /*type_count=*/0, /*failure_count=*/0,
+                         /*success_count=*/0);
 }
 
 TEST_F(DevicePairingHandlerImplTest, PairAuthDisplayPasskey) {
@@ -533,6 +609,9 @@
 
   InvokePendingConnectCallback(/*success=*/false);
   EXPECT_EQ(pairing_result(), mojom::PairingResult::kAuthFailed);
+  CheckPairingHistograms(device::BluetoothTransportType::kClassic,
+                         /*type_count=*/1, /*failure_count=*/1,
+                         /*success_count=*/0);
 }
 
 TEST_F(DevicePairingHandlerImplTest, PairAuthDisplayPasskeyPadZeroes) {
@@ -547,6 +626,9 @@
 
   InvokePendingConnectCallback(/*success=*/false);
   EXPECT_EQ(pairing_result(), mojom::PairingResult::kAuthFailed);
+  CheckPairingHistograms(device::BluetoothTransportType::kClassic,
+                         /*type_count=*/1, /*failure_count=*/1,
+                         /*success_count=*/0);
 
   // Pair a new device.
   std::string device_id2;
@@ -557,6 +639,10 @@
   // Passkey displayed should be a 6-digit number, padded with zeroes if needed.
   EXPECT_EQ(delegate2->displayed_passkey(), "000000");
   EXPECT_TRUE(delegate2->key_entered_handler()->IsMojoPipeConnected());
+  // Expect value to be 1 since pairing was not finished.
+  CheckPairingHistograms(device::BluetoothTransportType::kClassic,
+                         /*type_count=*/1, /*failure_count=*/1,
+                         /*success_count=*/0);
 }
 
 TEST_F(DevicePairingHandlerImplTest, PairAuthConfirmPasskey) {
@@ -574,6 +660,9 @@
 
   InvokePendingConnectCallback(/*success=*/false);
   EXPECT_EQ(pairing_result(), mojom::PairingResult::kAuthFailed);
+  CheckPairingHistograms(device::BluetoothTransportType::kClassic,
+                         /*type_count=*/1, /*failure_count=*/1,
+                         /*success_count=*/0);
 
   // Pair a new device.
   std::string device_id2;
@@ -592,6 +681,9 @@
   // ConfirmPairing() should not be called again, CancelPairing() should.
   EXPECT_EQ(num_confirm_pairing_calls(), 1u);
   EXPECT_EQ(num_cancel_pairing_calls(), 1u);
+  CheckPairingHistograms(device::BluetoothTransportType::kClassic,
+                         /*type_count=*/2, /*failure_count=*/2,
+                         /*success_count=*/0);
 }
 
 TEST_F(DevicePairingHandlerImplTest, PairAuthConfirmPasskeyRemoveDevice) {
@@ -611,6 +703,9 @@
   // Failure result should be returned.
   EXPECT_EQ(num_confirm_pairing_calls(), 0u);
   EXPECT_EQ(pairing_result(), mojom::PairingResult::kNonAuthFailure);
+  CheckPairingHistograms(device::BluetoothTransportType::kInvalid,
+                         /*type_count=*/1, /*failure_count=*/0,
+                         /*success_count=*/0);
 }
 
 TEST_F(DevicePairingHandlerImplTest, PairAuthAuthorizePairing) {
@@ -626,6 +721,9 @@
 
   InvokePendingConnectCallback(/*success=*/false);
   EXPECT_EQ(pairing_result(), mojom::PairingResult::kAuthFailed);
+  CheckPairingHistograms(device::BluetoothTransportType::kClassic,
+                         /*type_count=*/1, /*failure_count=*/1,
+                         /*success_count=*/0);
 }
 
 }  // namespace bluetooth_config
diff --git a/chromeos/services/bluetooth_config/public/cpp/BUILD.gn b/chromeos/services/bluetooth_config/public/cpp/BUILD.gn
index 99e7b17..21e33f0 100644
--- a/chromeos/services/bluetooth_config/public/cpp/BUILD.gn
+++ b/chromeos/services/bluetooth_config/public/cpp/BUILD.gn
@@ -8,6 +8,8 @@
   sources = [
     "cros_bluetooth_config_util.cc",
     "cros_bluetooth_config_util.h",
+    "device_image_info.cc",
+    "device_image_info.h",
   ]
 
   deps = [
@@ -15,3 +17,16 @@
     "//chromeos/services/bluetooth_config/public/mojom",
   ]
 }
+
+source_set("unit_tests") {
+  testonly = true
+
+  sources = [ "device_image_info_unittest.cc" ]
+
+  deps = [
+    ":cpp",
+    "//base",
+    "//base/test:test_support",
+    "//testing/gtest",
+  ]
+}
diff --git a/chromeos/services/bluetooth_config/public/cpp/device_image_info.cc b/chromeos/services/bluetooth_config/public/cpp/device_image_info.cc
new file mode 100644
index 0000000..d333769
--- /dev/null
+++ b/chromeos/services/bluetooth_config/public/cpp/device_image_info.cc
@@ -0,0 +1,75 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/services/bluetooth_config/public/cpp/device_image_info.h"
+
+#include "base/values.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace chromeos {
+namespace bluetooth_config {
+
+namespace {
+
+constexpr char kDefaultImageKey[] = "Default";
+constexpr char kLeftBudImageKey[] = "LeftBud";
+constexpr char kRightBudImageKey[] = "RightBud";
+constexpr char kCaseImageKey[] = "Case";
+
+}  // namespace
+
+DeviceImageInfo::DeviceImageInfo(const std::string& default_image,
+                                 const std::string& left_bud_image,
+                                 const std::string& right_bud_image,
+                                 const std::string& case_image)
+    : default_image_(default_image),
+      left_bud_image_(left_bud_image),
+      right_bud_image_(right_bud_image),
+      case_image_(case_image) {}
+
+DeviceImageInfo::DeviceImageInfo() = default;
+
+DeviceImageInfo::DeviceImageInfo(const DeviceImageInfo&) = default;
+
+DeviceImageInfo& DeviceImageInfo::operator=(const DeviceImageInfo&) = default;
+
+DeviceImageInfo::~DeviceImageInfo() = default;
+
+// static
+absl::optional<DeviceImageInfo> DeviceImageInfo::FromDictionaryValue(
+    const base::Value& value) {
+  if (!value.is_dict())
+    return absl::nullopt;
+
+  const std::string* default_image = value.FindStringKey(kDefaultImageKey);
+  if (!default_image)
+    return absl::nullopt;
+
+  const std::string* left_bud_image = value.FindStringKey(kLeftBudImageKey);
+  if (!left_bud_image)
+    return absl::nullopt;
+
+  const std::string* right_bud_image = value.FindStringKey(kRightBudImageKey);
+  if (!right_bud_image)
+    return absl::nullopt;
+
+  const std::string* case_image = value.FindStringKey(kCaseImageKey);
+  if (!case_image)
+    return absl::nullopt;
+
+  return DeviceImageInfo(*default_image, *left_bud_image, *right_bud_image,
+                         *case_image);
+}
+
+base::Value DeviceImageInfo::ToDictionaryValue() const {
+  base::Value dictionary(base::Value::Type::DICTIONARY);
+  dictionary.SetKey(kDefaultImageKey, base::Value(default_image_));
+  dictionary.SetKey(kLeftBudImageKey, base::Value(left_bud_image_));
+  dictionary.SetKey(kRightBudImageKey, base::Value(right_bud_image_));
+  dictionary.SetKey(kCaseImageKey, base::Value(case_image_));
+  return dictionary;
+}
+
+}  // namespace bluetooth_config
+}  // namespace chromeos
\ No newline at end of file
diff --git a/chromeos/services/bluetooth_config/public/cpp/device_image_info.h b/chromeos/services/bluetooth_config/public/cpp/device_image_info.h
new file mode 100644
index 0000000..694484a
--- /dev/null
+++ b/chromeos/services/bluetooth_config/public/cpp/device_image_info.h
@@ -0,0 +1,62 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_SERVICES_BLUETOOTH_CONFIG_PUBLIC_CPP_DEVICE_IMAGE_INFO_H_
+#define CHROMEOS_SERVICES_BLUETOOTH_CONFIG_PUBLIC_CPP_DEVICE_IMAGE_INFO_H_
+
+#include <string>
+
+#include "base/values.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace ash {
+namespace quick_pair {
+
+class DeviceImageStore;
+
+}  // namespace quick_pair
+}  // namespace ash
+
+namespace chromeos {
+namespace bluetooth_config {
+
+// Stores images as base64 encoded data URLs that can be displayed in UX.
+// Provides convenience methods to convert to and from a dictionary so that
+// it can be stored on disk.
+class DeviceImageInfo {
+ public:
+  // Returns null if the provided value does not have the required dictionary
+  // properties. Should be provided a dictionary created via
+  // ToDictionaryValue().
+  static absl::optional<DeviceImageInfo> FromDictionaryValue(
+      const base::Value& value);
+
+  DeviceImageInfo(const std::string& default_image,
+                  const std::string& left_bud_image,
+                  const std::string& right_bud_image,
+                  const std::string& case_image);
+  DeviceImageInfo();
+  DeviceImageInfo(const DeviceImageInfo&);
+  DeviceImageInfo& operator=(const DeviceImageInfo&);
+  ~DeviceImageInfo();
+
+  base::Value ToDictionaryValue() const;
+
+  const std::string& default_image() const { return default_image_; }
+  const std::string& left_bud_image() const { return left_bud_image_; }
+  const std::string& right_bud_image() const { return right_bud_image_; }
+  const std::string& case_image() const { return case_image_; }
+
+ private:
+  friend class ash::quick_pair::DeviceImageStore;
+  std::string default_image_;
+  std::string left_bud_image_;
+  std::string right_bud_image_;
+  std::string case_image_;
+};
+
+}  // namespace bluetooth_config
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_BLUETOOTH_CONFIG_PUBLIC_CPP_DEVICE_IMAGE_INFO_H_
\ No newline at end of file
diff --git a/chromeos/services/bluetooth_config/public/cpp/device_image_info_unittest.cc b/chromeos/services/bluetooth_config/public/cpp/device_image_info_unittest.cc
new file mode 100644
index 0000000..443fe9ac
--- /dev/null
+++ b/chromeos/services/bluetooth_config/public/cpp/device_image_info_unittest.cc
@@ -0,0 +1,65 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/services/bluetooth_config/public/cpp/device_image_info.h"
+
+#include "base/values.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace chromeos {
+namespace bluetooth_config {
+
+namespace {
+
+constexpr char kTestDefaultImage[] = "default";
+constexpr char kTestLeftBudImage[] = "left_bud";
+constexpr char kTestRightBudImage[] = "right_bud";
+constexpr char kTestCaseImage[] = "case_image";
+
+}  // namespace
+
+TEST(DeviceImageInfoTest, ToAndFromDictionaryValueValid) {
+  DeviceImageInfo image_info(kTestDefaultImage, kTestLeftBudImage,
+                             kTestRightBudImage, kTestCaseImage);
+
+  base::Value image_info_dict = image_info.ToDictionaryValue();
+  absl::optional<DeviceImageInfo> image_info_copy =
+      DeviceImageInfo::FromDictionaryValue(image_info_dict);
+  EXPECT_TRUE(image_info_copy);
+
+  EXPECT_EQ(kTestDefaultImage, image_info_copy->default_image());
+  EXPECT_EQ(kTestLeftBudImage, image_info_copy->left_bud_image());
+  EXPECT_EQ(kTestRightBudImage, image_info_copy->right_bud_image());
+  EXPECT_EQ(kTestCaseImage, image_info_copy->case_image());
+}
+
+TEST(DeviceImageInfoTest, ToAndFromDictionaryValueValidDefaultConstructor) {
+  // Ensure the default construction of DeviceImageInfo works with the
+  // to/from dictionary methods.
+  DeviceImageInfo image_info;
+
+  base::Value image_info_dict = image_info.ToDictionaryValue();
+  absl::optional<DeviceImageInfo> image_info_copy =
+      DeviceImageInfo::FromDictionaryValue(image_info_dict);
+  EXPECT_TRUE(image_info_copy);
+
+  EXPECT_TRUE(image_info_copy->default_image().empty());
+  EXPECT_TRUE(image_info_copy->left_bud_image().empty());
+  EXPECT_TRUE(image_info_copy->right_bud_image().empty());
+  EXPECT_TRUE(image_info_copy->case_image().empty());
+}
+
+TEST(DeviceImageInfoTest, FromDictionaryValueInvalid) {
+  // Should correctly handle dictionaries with missing fields.
+  base::Value invalid_dict(base::Value::Type::DICTIONARY);
+  EXPECT_FALSE(DeviceImageInfo::FromDictionaryValue(invalid_dict));
+
+  // Should correctly handle non-dictionary values.
+  base::Value not_a_dict(777);
+  EXPECT_FALSE(DeviceImageInfo::FromDictionaryValue(not_a_dict));
+}
+
+}  // namespace bluetooth_config
+}  // namespace chromeos
\ No newline at end of file
diff --git a/components/content_creation/reactions/core/reaction_list_factory.cc b/components/content_creation/reactions/core/reaction_list_factory.cc
index 8ade3aca..3e33692 100644
--- a/components/content_creation/reactions/core/reaction_list_factory.cc
+++ b/components/content_creation/reactions/core/reaction_list_factory.cc
@@ -32,8 +32,12 @@
 std::vector<ReactionMetadata> BuildReactionMetadata() {
   return {
       ReactionMetadata(
+          ReactionType::LAUGH_CRY,
+          l10n_util::GetStringUTF8(IDS_LIGHTWEIGHT_REACTIONS_TEARS_OF_JOY),
+          MakeThumbnailUrl("laughcry"), MakeReactionUrl("laughcry"), 48),
+      ReactionMetadata(
           ReactionType::HEART,
-          l10n_util::GetStringUTF8(IDS_LIGHTWEIGHT_REACTIONS_HEART),
+          l10n_util::GetStringUTF8(IDS_LIGHTWEIGHT_REACTIONS_BEATING_HEART),
           MakeThumbnailUrl("heart"), MakeReactionUrl("heart"), 48),
       ReactionMetadata(
           ReactionType::EMOTIONAL,
diff --git a/components/content_creation/reactions/core/reaction_types.h b/components/content_creation/reactions/core/reaction_types.h
index d8cd5c7..b2a00f65 100644
--- a/components/content_creation/reactions/core/reaction_types.h
+++ b/components/content_creation/reactions/core/reaction_types.h
@@ -23,8 +23,9 @@
   THANKS = 6,
   UNSURE = 7,
   HEART = 8,
+  LAUGH_CRY = 9,
 
-  MAX_VALUE = HEART
+  MAX_VALUE = LAUGH_CRY
 };
 
 }  // namespace content_creation
diff --git a/components/content_creation_strings.grdp b/components/content_creation_strings.grdp
index d01cad0..e6d92e6a 100644
--- a/components/content_creation_strings.grdp
+++ b/components/content_creation_strings.grdp
@@ -35,8 +35,12 @@
   </message>
 
   <!-- Lightweight Reactions names, currently only used on Android. -->
-  <message name="IDS_LIGHTWEIGHT_REACTIONS_HEART" is_accessibility_with_no_ui="true" desc="The accessibility text to read when the heart reaction is selected.">
-    Heart
+  <message name="IDS_LIGHTWEIGHT_REACTIONS_TEARS_OF_JOY" is_accessibility_with_no_ui="true" desc="The accessibility text to read when the tears of joy reaction is selected.">
+    Tears of Joy
+  </message>
+
+  <message name="IDS_LIGHTWEIGHT_REACTIONS_BEATING_HEART" is_accessibility_with_no_ui="true" desc="The accessibility text to read when the beating heart reaction is selected.">
+    Beating Heart
   </message>
 
   <message name="IDS_LIGHTWEIGHT_REACTIONS_CLAPPING" is_accessibility_with_no_ui="true" desc="The accessibility text to read when the clapping reaction is selected.">
diff --git a/components/omnibox/browser/omnibox_field_trial.cc b/components/omnibox/browser/omnibox_field_trial.cc
index aa92466..ec18cfe 100644
--- a/components/omnibox/browser/omnibox_field_trial.cc
+++ b/components/omnibox/browser/omnibox_field_trial.cc
@@ -963,6 +963,10 @@
         3);
 
 // Zero Suggest
+const base::FeatureParam<bool> kZeroSuggestCacheCounterfactual(
+    &omnibox::kZeroSuggestPrefetching,
+    "ZeroSuggestCacheCounterfactual",
+    false);
 const base::FeatureParam<int> kZeroSuggestCacheDurationSec(
     &omnibox::kZeroSuggestPrefetching,
     "ZeroSuggestCacheDurationSec",
diff --git a/components/omnibox/browser/omnibox_field_trial.h b/components/omnibox/browser/omnibox_field_trial.h
index 9f5b032..95498c9 100644
--- a/components/omnibox/browser/omnibox_field_trial.h
+++ b/components/omnibox/browser/omnibox_field_trial.h
@@ -552,6 +552,13 @@
     kShortBookmarkSuggestionsByTotalInputLengthThreshold;
 
 // Zero Suggest
+// Indicates whether the user is in the counterfactual group in the experiment
+// for prefetching zero prefix suggestions on the NTP. Users in the
+// counterfactual group issue a follow-up non-cacheable request if the response
+// is loaded from the HTTP cache in order to determine HTTP cache validity.
+// This param is tied to omnibox::kZeroSuggestPrefetching and is to be used when
+// a valid HTTP cache duration is provided via kZeroSuggestCacheDurationSec.
+extern const base::FeatureParam<bool> kZeroSuggestCacheCounterfactual;
 // Specifies the HTTP cache duration for the zero prefix suggest responses. If
 // the provided value is a positive number, the cache duration will be sent as a
 // query string parameter in the zero suggest requests and relayed back in the
diff --git a/components/omnibox/browser/zero_suggest_provider.cc b/components/omnibox/browser/zero_suggest_provider.cc
index 402c279..30e6903 100644
--- a/components/omnibox/browser/zero_suggest_provider.cc
+++ b/components/omnibox/browser/zero_suggest_provider.cc
@@ -71,7 +71,8 @@
 
 // Keeps track of how many Suggest requests are sent, how many requests were
 // invalidated, e.g., due to user starting to type, how many responses were
-// received, and how many of those responses were loaded from the HTTP cache.
+// received, how many of those responses were loaded from the HTTP cache, and of
+// those cached responses, how many were out-of-date.
 // These values are written to logs.  New enum values can be added, but existing
 // enums must never be renumbered or deleted and reused.
 enum ZeroSuggestRequestsHistogramValue {
@@ -79,6 +80,7 @@
   ZERO_SUGGEST_REQUEST_INVALIDATED = 2,
   ZERO_SUGGEST_RESPONSE_RECEIVED = 3,
   ZERO_SUGGEST_RESPONSE_LOADED_FROM_HTTP_CACHE = 4,
+  ZERO_SUGGEST_RESPONSE_LOADED_FROM_HTTP_CACHE_IS_OUT_OF_DATE = 5,
   ZERO_SUGGEST_MAX_REQUEST_HISTOGRAM_VALUE
 };
 
@@ -222,7 +224,8 @@
               weak_ptr_factory_.GetWeakPtr(), is_prefetch),
           base::BindOnce(&ZeroSuggestProvider::OnURLLoadComplete,
                          base::Unretained(this) /* this owns SimpleURLLoader */,
-                         is_prefetch, base::TimeTicks::Now()));
+                         search_terms_args, is_prefetch,
+                         base::TimeTicks::Now()));
 }
 
 void ZeroSuggestProvider::Stop(bool clear_cached_results,
@@ -233,6 +236,7 @@
   }
   loader_.reset();
   is_prefetch_loader_ = false;
+  counterfactual_loader_.reset();
   done_ = true;
   result_type_running_ = NONE;
 
@@ -342,6 +346,7 @@
 }
 
 void ZeroSuggestProvider::OnURLLoadComplete(
+    TemplateURLRef::SearchTermsArgs search_terms_args,
     bool is_prefetch,
     base::TimeTicks request_time,
     const network::SimpleURLLoader* source,
@@ -352,9 +357,33 @@
   LogOmniboxZeroSuggestRequestRoundTripTime(
       base::TimeTicks::Now() - request_time, is_prefetch);
   LogOmniboxZeroSuggestRequest(ZERO_SUGGEST_RESPONSE_RECEIVED, is_prefetch);
+
   if (source->LoadedFromCache()) {
     LogOmniboxZeroSuggestRequest(ZERO_SUGGEST_RESPONSE_LOADED_FROM_HTTP_CACHE,
                                  is_prefetch);
+
+    // Issue a follow-up non-cacheable request in the counterfactual arm if the
+    // response is loaded from the HTTP cache. The new response is compared
+    // against the original cached response to determine HTTP cache validity.
+    if (OmniboxFieldTrial::kZeroSuggestCacheCounterfactual.Get() &&
+        OmniboxFieldTrial::kZeroSuggestCacheDurationSec.Get() > 0) {
+      // Make sure the request is not cacheable.
+      search_terms_args.zero_suggest_cache_duration_sec = 0;
+
+      const std::string& original_response = *response_body;
+      client()
+          ->GetRemoteSuggestionsService(/*create_if_necessary=*/true)
+          ->CreateSuggestionsRequest(
+              search_terms_args, client()->GetTemplateURLService(),
+              base::BindOnce(
+                  &ZeroSuggestProvider::
+                      OnRemoteSuggestionsCounterfactualLoaderAvailable,
+                  weak_ptr_factory_.GetWeakPtr()),
+              base::BindOnce(
+                  &ZeroSuggestProvider::OnCounterfactualURLLoadComplete,
+                  base::Unretained(this) /* this owns SimpleURLLoader */,
+                  is_prefetch, original_response));
+    }
   }
 
   const bool results_updated =
@@ -374,6 +403,22 @@
   }
 }
 
+void ZeroSuggestProvider::OnCounterfactualURLLoadComplete(
+    bool original_is_prefetch,
+    const std::string& original_response,
+    const network::SimpleURLLoader* source,
+    std::unique_ptr<std::string> response) {
+  DCHECK(!source->LoadedFromCache());
+
+  if (original_response != *response) {
+    LogOmniboxZeroSuggestRequest(
+        ZERO_SUGGEST_RESPONSE_LOADED_FROM_HTTP_CACHE_IS_OUT_OF_DATE,
+        original_is_prefetch);
+  }
+
+  counterfactual_loader_.reset();
+}
+
 bool ZeroSuggestProvider::UpdateResults(const std::string& json_data) {
   std::unique_ptr<base::Value> data(
       SearchSuggestionParser::DeserializeJsonData(json_data));
@@ -442,6 +487,11 @@
   LogOmniboxZeroSuggestRequest(ZERO_SUGGEST_REQUEST_SENT, is_prefetch);
 }
 
+void ZeroSuggestProvider::OnRemoteSuggestionsCounterfactualLoaderAvailable(
+    std::unique_ptr<network::SimpleURLLoader> loader) {
+  counterfactual_loader_ = std::move(loader);
+}
+
 void ZeroSuggestProvider::ConvertResultsToAutocompleteMatches() {
   matches_.clear();
 
diff --git a/components/omnibox/browser/zero_suggest_provider.h b/components/omnibox/browser/zero_suggest_provider.h
index 82d6314..8320e36 100644
--- a/components/omnibox/browser/zero_suggest_provider.h
+++ b/components/omnibox/browser/zero_suggest_provider.h
@@ -130,11 +130,22 @@
   // Called when the network request for suggestions has completed.
   // `is_prefetch` and `request_time` are bound to this callback and indicate if
   // the request is a prefetch one and the time it was issued respectively.
-  void OnURLLoadComplete(bool is_prefetch,
+  void OnURLLoadComplete(TemplateURLRef::SearchTermsArgs search_terms_args,
+                         bool is_prefetch,
                          base::TimeTicks request_time,
                          const network::SimpleURLLoader* source,
                          std::unique_ptr<std::string> response_body);
 
+  // Called when the counterfactual network request for suggestions has
+  // completed. `original_is_prefetch` and `original_response` are bound to this
+  // callback and indicate if the original request was a prefetch one and the
+  // original cached response received in OnURLLoadComplete() respectively.
+  void OnCounterfactualURLLoadComplete(
+      bool original_is_prefetch,
+      const std::string& original_response,
+      const network::SimpleURLLoader* source,
+      std::unique_ptr<std::string> response_body);
+
   // The function updates |results_| with data parsed from |json_data|.
   //
   // * The update is not performed if |json_data| is invalid.
@@ -170,6 +181,11 @@
       bool is_prefetch,
       std::unique_ptr<network::SimpleURLLoader> loader);
 
+  // Serves the same purpose as OnRemoteSuggestionsLoaderAvailable for the
+  // counterfactual requests.
+  void OnRemoteSuggestionsCounterfactualLoaderAvailable(
+      std::unique_ptr<network::SimpleURLLoader> loader);
+
   // Whether zero suggest suggestions are allowed in the given context.
   // Invoked early, confirms all the external conditions for ZeroSuggest are
   // met.
@@ -220,6 +236,9 @@
   // when the provider is stopped.
   bool is_prefetch_loader_;
 
+  // Loader used to retrieve counterfactual results.
+  std::unique_ptr<network::SimpleURLLoader> counterfactual_loader_;
+
   // The verbatim match for the current text, which is always a URL.
   AutocompleteMatch current_text_match_;
 
diff --git a/components/optimization_guide/core/hints_manager.cc b/components/optimization_guide/core/hints_manager.cc
index 394d4c18..800f4243 100644
--- a/components/optimization_guide/core/hints_manager.cc
+++ b/components/optimization_guide/core/hints_manager.cc
@@ -302,6 +302,7 @@
       hint_cache_(
           std::make_unique<HintCache>(hint_store,
                                       features::MaxHostKeyedHintCacheSize())),
+      batch_update_hints_fetchers_(features::MaxConcurrentBatchUpdateFetches()),
       page_navigation_hints_fetchers_(
           features::MaxConcurrentPageNavigationFetches()),
       hints_fetcher_factory_(std::make_unique<HintsFetcherFactory>(
@@ -671,11 +672,6 @@
   if (top_hosts.empty() && active_tab_urls_to_refresh.empty())
     return;
 
-  if (!batch_update_hints_fetcher_) {
-    DCHECK(hints_fetcher_factory_);
-    batch_update_hints_fetcher_ = hints_fetcher_factory_->BuildInstance();
-  }
-
   // Add hosts of active tabs to list of hosts to fetch for. Since we are mainly
   // fetching for updated information on tabs, add those to the front of the
   // list.
@@ -695,7 +691,12 @@
                              registered_optimization_types_,
                              active_tab_urls_to_refresh, top_hosts);
 
-  batch_update_hints_fetcher_->FetchOptimizationGuideServiceHints(
+  if (!active_tabs_batch_update_hints_fetcher_) {
+    DCHECK(hints_fetcher_factory_);
+    active_tabs_batch_update_hints_fetcher_ =
+        hints_fetcher_factory_->BuildInstance();
+  }
+  active_tabs_batch_update_hints_fetcher_->FetchOptimizationGuideServiceHints(
       top_hosts, active_tab_urls_to_refresh, registered_optimization_types_,
       proto::CONTEXT_BATCH_UPDATE_ACTIVE_TABS, application_locale_,
       base::BindOnce(&HintsManager::OnHintsForActiveTabsFetched,
@@ -729,9 +730,12 @@
     const base::flat_set<std::string>& page_navigation_hosts_requested,
     absl::optional<std::unique_ptr<proto::GetHintsResponse>>
         get_hints_response) {
+  if (navigation_url) {
+    CleanUpFetcherForNavigation(*navigation_url);
+  }
+
   if (!get_hints_response.has_value() || !get_hints_response.value()) {
     if (navigation_url) {
-      CleanUpFetcherForNavigation(*navigation_url);
       PrepareToInvokeRegisteredCallbacks(*navigation_url);
     }
     return;
@@ -776,7 +780,6 @@
       navigation_data_weak_ptr.MaybeValid());
 
   if (navigation_url) {
-    CleanUpFetcherForNavigation(*navigation_url);
     PrepareToInvokeRegisteredCallbacks(*navigation_url);
   }
 }
@@ -809,7 +812,7 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (!url.has_host()) {
-    std::move(callback).Run();
+    MaybeRunUpdateClosure(std::move(callback));
     return;
   }
 
@@ -825,34 +828,44 @@
                                              std::move(callback)));
 }
 
-void HintsManager::FetchHintsForURLs(std::vector<GURL> target_urls,
+void HintsManager::FetchHintsForURLs(const std::vector<GURL>& urls,
                                      proto::RequestContext request_context) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   // Collect hosts, stripping duplicates, but preserving the ordering.
+  InsertionOrderedSet<GURL> target_urls;
   InsertionOrderedSet<std::string> target_hosts;
-  for (const auto& url : target_urls) {
+  for (const auto& url : urls) {
     target_hosts.insert(url.host());
+    target_urls.insert(url);
   }
 
   if (target_hosts.empty() && target_urls.empty())
     return;
 
-  if (!batch_update_hints_fetcher_) {
-    DCHECK(hints_fetcher_factory_);
-    batch_update_hints_fetcher_ = hints_fetcher_factory_->BuildInstance();
-  }
+  MaybeLogGetHintRequestInfo(request_context, registered_optimization_types_,
+                             target_urls.vector(), target_hosts.vector());
+
+  std::pair<int32_t, HintsFetcher*> request_id_and_fetcher =
+      CreateAndTrackBatchUpdateHintsFetcher();
 
   // Use the batch update hints fetcher for fetches off the SRP since we are
-  // not fetching for the current navigation, even though we are fetching using
-  // the page navigation context. However, since we do want to load the hints
-  // returned, we pass this through to the page navigation callback.
-  batch_update_hints_fetcher_->FetchOptimizationGuideServiceHints(
-      target_hosts.vector(), target_urls, registered_optimization_types_,
-      request_context, application_locale_,
-      base::BindOnce(&HintsManager::OnPageNavigationHintsFetched,
-                     weak_ptr_factory_.GetWeakPtr(), nullptr, absl::nullopt,
-                     target_urls, target_hosts.set()));
+  // not fetching for the current navigation.
+  //
+  // Caller does not expect to be notified when relevant hints have been fetched
+  // and stored.
+  request_id_and_fetcher.second->FetchOptimizationGuideServiceHints(
+      target_hosts.vector(), target_urls.vector(),
+      registered_optimization_types_, request_context, application_locale_,
+      base::BindOnce(
+          &HintsManager::OnBatchUpdateHintsFetched,
+          weak_ptr_factory_.GetWeakPtr(), request_id_and_fetcher.first,
+          request_context, target_hosts.set(), target_urls.set(),
+          registered_optimization_types_,
+          base::DoNothingAs<void(
+              const GURL&, const base::flat_map<
+                               proto::OptimizationType,
+                               OptimizationGuideDecisionWithMetadata>&)>()));
 }
 
 void HintsManager::OnHintLoaded(base::OnceClosure callback,
@@ -864,7 +877,7 @@
 
   // Run the callback now that the hint is loaded. This is used as a signal by
   // tests.
-  std::move(callback).Run();
+  MaybeRunUpdateClosure(std::move(callback));
 }
 
 void HintsManager::RegisterOptimizationTypes(
@@ -1015,46 +1028,51 @@
                              urls_to_fetch.vector(), hosts_to_fetch.vector());
 
   // Fetch the data for the entries we don't have all information for.
-  if (!batch_update_hints_fetcher_) {
-    DCHECK(hints_fetcher_factory_);
-    batch_update_hints_fetcher_ = hints_fetcher_factory_->BuildInstance();
-  }
-  batch_update_hints_fetcher_->FetchOptimizationGuideServiceHints(
+  std::pair<int32_t, HintsFetcher*> request_id_and_fetcher =
+      CreateAndTrackBatchUpdateHintsFetcher();
+  request_id_and_fetcher.second->FetchOptimizationGuideServiceHints(
       hosts_to_fetch.vector(), urls_to_fetch.vector(),
       registered_optimization_types_, request_context, application_locale_,
-      base::BindOnce(&HintsManager::OnOnDemandHintsFetched,
-                     weak_ptr_factory_.GetWeakPtr(), hosts_to_fetch.set(),
-                     urls_to_fetch.set(), optimization_types, callback));
+      base::BindOnce(&HintsManager::OnBatchUpdateHintsFetched,
+                     weak_ptr_factory_.GetWeakPtr(),
+                     request_id_and_fetcher.first, request_context,
+                     hosts_to_fetch.set(), urls_to_fetch.set(),
+                     optimization_types, callback));
 }
 
-void HintsManager::OnOnDemandHintsFetched(
+void HintsManager::OnBatchUpdateHintsFetched(
+    int32_t request_id,
+    proto::RequestContext request_context,
     const base::flat_set<std::string>& hosts_requested,
     const base::flat_set<GURL>& urls_requested,
     const base::flat_set<proto::OptimizationType>& optimization_types,
     OnDemandOptimizationGuideDecisionRepeatingCallback callback,
     absl::optional<std::unique_ptr<proto::GetHintsResponse>>
         get_hints_response) {
+  CleanUpBatchUpdateHintsFetcher(request_id);
+
   if (!get_hints_response.has_value() || !get_hints_response.value()) {
-    OnReadyToInvokeOnDemandHintsCallbackForURLs(urls_requested,
-                                                optimization_types, callback);
+    OnBatchUpdateHintsStored(urls_requested, optimization_types, callback);
     return;
   }
 
-  // TODO(crbug/1278015: Figure out if the update time duration is the right
+  // TODO(crbug/1278015): Figure out if the update time duration is the right
   // one.
   hint_cache_->UpdateFetchedHints(
       std::move(*get_hints_response),
       clock_->Now() + features::GetActiveTabsFetchRefreshDuration(),
       hosts_requested, urls_requested,
-      base::BindOnce(&HintsManager::OnReadyToInvokeOnDemandHintsCallbackForURLs,
+      base::BindOnce(&HintsManager::OnBatchUpdateHintsStored,
                      weak_ptr_factory_.GetWeakPtr(), urls_requested,
                      optimization_types, callback));
 
-  if (switches::IsDebugLogsEnabled())
-    DVLOG(0) << "OptimizationGuide: OnOnDemandHintsFetched complete";
+  if (switches::IsDebugLogsEnabled()) {
+    DVLOG(0) << "OptimizationGuide: OnBatchUpdateHintsFetched for "
+             << proto::RequestContext_Name(request_context) << " complete";
+  }
 }
 
-void HintsManager::OnReadyToInvokeOnDemandHintsCallbackForURLs(
+void HintsManager::OnBatchUpdateHintsStored(
     const base::flat_set<GURL>& urls,
     const base::flat_set<proto::OptimizationType>& optimization_types,
     OnDemandOptimizationGuideDecisionRepeatingCallback callback) {
@@ -1071,6 +1089,27 @@
   }
 }
 
+std::pair<int32_t, HintsFetcher*>
+HintsManager::CreateAndTrackBatchUpdateHintsFetcher() {
+  DCHECK(hints_fetcher_factory_);
+  std::unique_ptr<HintsFetcher> hints_fetcher =
+      hints_fetcher_factory_->BuildInstance();
+  HintsFetcher* hints_fetcher_ptr = hints_fetcher.get();
+  batch_update_hints_fetchers_.Put(batch_update_hints_fetcher_request_id_++,
+                                   std::move(hints_fetcher));
+  UMA_HISTOGRAM_COUNTS_100(
+      "OptimizationGuide.HintsManager.ConcurrentBatchUpdateFetches",
+      batch_update_hints_fetchers_.size());
+  return std::make_pair(batch_update_hints_fetcher_request_id_,
+                        hints_fetcher_ptr);
+}
+
+void HintsManager::CleanUpBatchUpdateHintsFetcher(int32_t request_id) {
+  auto it = batch_update_hints_fetchers_.Peek(request_id);
+  if (it != batch_update_hints_fetchers_.end())
+    batch_update_hints_fetchers_.Erase(it);
+}
+
 void HintsManager::InvokeOnDemandHintsCallbackForURL(
     const GURL& url,
     const base::flat_set<proto::OptimizationType>& optimization_types,
diff --git a/components/optimization_guide/core/hints_manager.h b/components/optimization_guide/core/hints_manager.h
index 549652e..94aaa3d 100644
--- a/components/optimization_guide/core/hints_manager.h
+++ b/components/optimization_guide/core/hints_manager.h
@@ -134,11 +134,6 @@
   // and in memory ones.
   void ClearHostKeyedHints();
 
-  // Returns the current batch update hints fetcher.
-  HintsFetcher* batch_update_hints_fetcher() const {
-    return batch_update_hints_fetcher_.get();
-  }
-
   // Overrides |hints_fetcher_factory| for testing.
   void SetHintsFetcherFactoryForTesting(
       std::unique_ptr<HintsFetcherFactory> hints_fetcher_factory);
@@ -164,7 +159,7 @@
   void OnDeferredStartup();
 
   // Fetch the hints for the given URLs with the provided |request_context|.
-  void FetchHintsForURLs(std::vector<GURL> target_urls,
+  void FetchHintsForURLs(const std::vector<GURL>& target_urls,
                          proto::RequestContext request_context);
 
   // PushNotificationManager::Delegate:
@@ -194,6 +189,7 @@
 
  private:
   friend class ::OptimizationGuideTestAppInterfaceWrapper;
+  friend class HintsManagerTest;
 
   // Processes the optimization filters contained in the hints component.
   void ProcessOptimizationFilters(
@@ -249,10 +245,12 @@
       absl::optional<std::unique_ptr<proto::GetHintsResponse>>
           get_hints_response);
 
-  // Called when the on demand hints have been fetched from the remote
+  // Called when the batch update hints have been fetched from the remote
   // Optimization Guide Service and are ready for parsing. This is used when
-  // fetching hints on demand.
-  void OnOnDemandHintsFetched(
+  // fetching hints on demand or from SRP.
+  void OnBatchUpdateHintsFetched(
+      int32_t request_id,
+      proto::RequestContext request_context,
       const base::flat_set<std::string>& hosts_fetched,
       const base::flat_set<GURL>& urls_fetched,
       const base::flat_set<proto::OptimizationType>& optimization_types,
@@ -260,13 +258,26 @@
       absl::optional<std::unique_ptr<proto::GetHintsResponse>>
           get_hints_response);
 
-  // Called when information is ready such that we can invoke the on-demand
-  // hints callback.
-  void OnReadyToInvokeOnDemandHintsCallbackForURLs(
+  // Called when information is ready such that we can invoke any callbacks that
+  // require returning decisions to consumer features.
+  //
+  // TODO(crbug/1279536): Clean this up when we clean up some of the existing
+  // interfaces.
+  void OnBatchUpdateHintsStored(
       const base::flat_set<GURL>& urls_fetched,
       const base::flat_set<proto::OptimizationType>& optimization_types,
       OnDemandOptimizationGuideDecisionRepeatingCallback callback);
 
+  // Creates a hints fetcher and stores it in |batch_update_hints_fetchers_| and
+  // returns the request ID associated with the fetch. It is expected to call
+  // `CleanUpBatchUpdateHintsFetcher` with the returned request ID once the
+  // fetch has finished.
+  std::pair<int32_t, HintsFetcher*> CreateAndTrackBatchUpdateHintsFetcher();
+
+  // Called to inform |this| that the batch fetcher with |request_id| is no
+  // longer needed removes the request from |batch_update_hints_fetchers_|
+  void CleanUpBatchUpdateHintsFetcher(int32_t request_id);
+
   // Returns decisions for |url| and |optimization_types| based on what's cached
   // locally.
   base::flat_map<proto::OptimizationType, OptimizationGuideDecisionWithMetadata>
@@ -366,6 +377,20 @@
 
   HintsFetcherFactory* GetHintsFetcherFactory();
 
+  // Returns the number of batch update hints fetches initiated.
+  //
+  // Exposed here for testing.
+  int32_t num_batch_update_hints_fetches_initiated() const {
+    return batch_update_hints_fetcher_request_id_;
+  }
+
+  // Returns the current active tabs batch update hints fetcher.
+  //
+  // Exposed here for testing.
+  HintsFetcher* active_tabs_batch_update_hints_fetcher() const {
+    return active_tabs_batch_update_hints_fetcher_.get();
+  }
+
   // The information of the latest component delivered by
   // |optimization_guide_service_|.
   absl::optional<HintsComponentInfo> hints_component_info_;
@@ -412,9 +437,15 @@
   // fetched from the remote Optimization Guide Service.
   std::unique_ptr<HintCache> hint_cache_;
 
-  // The fetcher that handles making requests for hints for multiple hosts from
+  // The fetcher that handles making requests for hints for active tabs from
   // the remote Optimization Guide Service.
-  std::unique_ptr<HintsFetcher> batch_update_hints_fetcher_;
+  std::unique_ptr<HintsFetcher> active_tabs_batch_update_hints_fetcher_;
+
+  // A map from request ID to the fetcher that handles making requests for hints
+  // for multiple hosts from the remote Optimization Guide Service.
+  base::LRUCache<int32_t, std::unique_ptr<HintsFetcher>>
+      batch_update_hints_fetchers_;
+  int32_t batch_update_hints_fetcher_request_id_ = 0;
 
   // A cache keyed by navigation URL to the fetcher making a request for a hint
   // for that URL and/or host to the remote Optimization Guide Service that
diff --git a/components/optimization_guide/core/hints_manager_unittest.cc b/components/optimization_guide/core/hints_manager_unittest.cc
index c405660..8dd0d40 100644
--- a/components/optimization_guide/core/hints_manager_unittest.cc
+++ b/components/optimization_guide/core/hints_manager_unittest.cc
@@ -453,9 +453,13 @@
 
   HintsManager* hints_manager() const { return hints_manager_.get(); }
 
-  TestHintsFetcher* batch_update_hints_fetcher() const {
+  int32_t num_batch_update_hints_fetches_initiated() const {
+    return hints_manager()->num_batch_update_hints_fetches_initiated();
+  }
+
+  TestHintsFetcher* active_tabs_batch_update_hints_fetcher() const {
     return static_cast<TestHintsFetcher*>(
-        hints_manager()->batch_update_hints_fetcher());
+        hints_manager()->active_tabs_batch_update_hints_fetcher());
   }
 
   GURL url_with_hints() const {
@@ -1565,7 +1569,7 @@
   MoveClockForwardBy(base::Seconds(kUpdateFetchHintsTimeSecs));
   EXPECT_EQ(0, top_host_provider->get_num_top_hosts_called());
   // Hints fetcher should not even be created.
-  EXPECT_FALSE(batch_update_hints_fetcher());
+  EXPECT_FALSE(active_tabs_batch_update_hints_fetcher());
 }
 
 TEST_F(HintsManagerTest,
@@ -1794,7 +1798,8 @@
         {
             {
                 features::kRemoteOptimizationGuideFetching,
-                {{"max_concurrent_page_navigation_fetches", "2"}},
+                {{"max_concurrent_page_navigation_fetches", "2"},
+                 {"max_concurrent_batch_update_fetches", "2"}},
             },
         },
         {features::kRemoteOptimizationGuideFetchingAnonymousDataConsent});
@@ -1820,7 +1825,7 @@
   // Force timer to expire and schedule a hints fetch.
   MoveClockForwardBy(base::Seconds(kUpdateFetchHintsTimeSecs));
   // Hints fetcher should not even be created.
-  EXPECT_FALSE(batch_update_hints_fetcher());
+  EXPECT_FALSE(active_tabs_batch_update_hints_fetcher());
 }
 
 TEST_F(HintsManagerFetchingTest,
@@ -1841,8 +1846,8 @@
   // Force timer to expire and schedule a hints fetch but the fetch is not made.
   MoveClockForwardBy(base::Seconds(kUpdateFetchHintsTimeSecs));
   EXPECT_EQ(0, top_host_provider->get_num_top_hosts_called());
-  // Hints fetcher should not be created.
-  EXPECT_FALSE(batch_update_hints_fetcher());
+  // Hints fetcher should not even be created.
+  EXPECT_FALSE(active_tabs_batch_update_hints_fetcher());
 }
 
 TEST_F(HintsManagerFetchingTest,
@@ -1873,8 +1878,8 @@
   // Force timer to expire after random delay and schedule a hints fetch.
   MoveClockForwardBy(base::Seconds(60 * 2));
   EXPECT_EQ(0, top_host_provider->get_num_top_hosts_called());
-  // Hints fetcher should not be created.
-  EXPECT_FALSE(batch_update_hints_fetcher());
+  // Hints fetcher should not even be created.
+  EXPECT_FALSE(active_tabs_batch_update_hints_fetcher());
 }
 
 TEST_F(HintsManagerFetchingTest, HintsFetcherEnabledNoHostsOrUrlsToFetch) {
@@ -1901,15 +1906,15 @@
   MoveClockForwardBy(base::Seconds(60 * 2));
   EXPECT_EQ(1, top_host_provider->get_num_top_hosts_called());
   EXPECT_EQ(1, tab_url_provider()->get_num_urls_called());
-  // Hints fetcher should not be even created.
-  EXPECT_FALSE(batch_update_hints_fetcher());
+  // Hints fetcher should not even be created.
+  EXPECT_FALSE(active_tabs_batch_update_hints_fetcher());
 
   // Move it forward again to make sure timer is scheduled.
   MoveClockForwardBy(base::Seconds(kUpdateFetchHintsTimeSecs));
   EXPECT_EQ(2, top_host_provider->get_num_top_hosts_called());
   EXPECT_EQ(2, tab_url_provider()->get_num_urls_called());
-  // Still no hosts or URLs, so hints fetcher should still not be even created.
-  EXPECT_FALSE(batch_update_hints_fetcher());
+  // Hints fetcher should not even be created.
+  EXPECT_FALSE(active_tabs_batch_update_hints_fetcher());
 }
 
 TEST_F(HintsManagerFetchingTest, HintsFetcherEnabledNoHostsButHasUrlsToFetch) {
@@ -1940,10 +1945,13 @@
   MoveClockForwardBy(base::Seconds(60 * 2));
   EXPECT_EQ(1, top_host_provider->get_num_top_hosts_called());
   EXPECT_EQ(1, tab_url_provider()->get_num_urls_called());
-  EXPECT_EQ(1, batch_update_hints_fetcher()->num_fetches_requested());
-  EXPECT_EQ("en-US", batch_update_hints_fetcher()->locale_requested());
-  EXPECT_EQ(proto::RequestContext::CONTEXT_BATCH_UPDATE_ACTIVE_TABS,
-            batch_update_hints_fetcher()->request_context_requested());
+  EXPECT_EQ(1,
+            active_tabs_batch_update_hints_fetcher()->num_fetches_requested());
+  EXPECT_EQ("en-US",
+            active_tabs_batch_update_hints_fetcher()->locale_requested());
+  EXPECT_EQ(
+      proto::RequestContext::CONTEXT_BATCH_UPDATE_ACTIVE_TABS,
+      active_tabs_batch_update_hints_fetcher()->request_context_requested());
   histogram_tester.ExpectBucketCount(
       "OptimizationGuide.HintsManager.ActiveTabUrlsToFetchFor", 2, 1);
 
@@ -1952,7 +1960,8 @@
   EXPECT_EQ(2, top_host_provider->get_num_top_hosts_called());
   EXPECT_EQ(2, tab_url_provider()->get_num_urls_called());
   // Urls didn't change and we have all URLs cached in store.
-  EXPECT_EQ(1, batch_update_hints_fetcher()->num_fetches_requested());
+  EXPECT_EQ(1,
+            active_tabs_batch_update_hints_fetcher()->num_fetches_requested());
   histogram_tester.ExpectBucketCount(
       "OptimizationGuide.HintsManager.ActiveTabUrlsToFetchFor", 0, 1);
 }
@@ -1982,7 +1991,7 @@
   RunUntilIdle();
   histogram_tester.ExpectTotalCount(
       "OptimizationGuide.HintsManager.ActiveTabUrlsToFetchFor", 0);
-  EXPECT_EQ(nullptr, batch_update_hints_fetcher());
+  EXPECT_FALSE(active_tabs_batch_update_hints_fetcher());
   EXPECT_EQ(0, tab_url_provider()->get_num_urls_called());
 
   // Force timer to expire after random delay and schedule a hints fetch that
@@ -1991,14 +2000,16 @@
   histogram_tester.ExpectBucketCount(
       "OptimizationGuide.HintsManager.ActiveTabUrlsToFetchFor", 2, 1);
   EXPECT_EQ(1, tab_url_provider()->get_num_urls_called());
-  EXPECT_EQ(1, batch_update_hints_fetcher()->num_fetches_requested());
+  EXPECT_EQ(1,
+            active_tabs_batch_update_hints_fetcher()->num_fetches_requested());
 
   // Move it forward again to make sure timer is scheduled.
   MoveClockForwardBy(base::Seconds(kUpdateFetchHintsTimeSecs));
   histogram_tester.ExpectBucketCount(
       "OptimizationGuide.HintsManager.ActiveTabUrlsToFetchFor", 0, 1);
   EXPECT_EQ(2, tab_url_provider()->get_num_urls_called());
-  EXPECT_EQ(1, batch_update_hints_fetcher()->num_fetches_requested());
+  EXPECT_EQ(1,
+            active_tabs_batch_update_hints_fetcher()->num_fetches_requested());
 }
 
 // Verifies the deferred startup mode that fetches hints for active tab URLs on
@@ -2035,14 +2046,16 @@
   histogram_tester.ExpectBucketCount(
       "OptimizationGuide.HintsManager.ActiveTabUrlsToFetchFor", 2, 1);
   EXPECT_EQ(1, tab_url_provider()->get_num_urls_called());
-  EXPECT_EQ(1, batch_update_hints_fetcher()->num_fetches_requested());
+  EXPECT_EQ(1,
+            active_tabs_batch_update_hints_fetcher()->num_fetches_requested());
 
   // Move it forward again to make sure timer is scheduled.
   MoveClockForwardBy(base::Seconds(kUpdateFetchHintsTimeSecs));
   histogram_tester.ExpectBucketCount(
       "OptimizationGuide.HintsManager.ActiveTabUrlsToFetchFor", 0, 1);
   EXPECT_EQ(2, tab_url_provider()->get_num_urls_called());
-  EXPECT_EQ(1, batch_update_hints_fetcher()->num_fetches_requested());
+  EXPECT_EQ(1,
+            active_tabs_batch_update_hints_fetcher()->num_fetches_requested());
 }
 
 TEST_F(HintsManagerFetchingTest,
@@ -3172,6 +3185,54 @@
           },
           run_loop.get()));
   run_loop->Run();
+
+  histogram_tester.ExpectUniqueSample(
+      "OptimizationGuide.HintsManager.ConcurrentBatchUpdateFetches", 1, 1);
+}
+
+TEST_F(HintsManagerFetchingTest, BatchUpdateCalledMoreThanMaxConcurrent) {
+  base::HistogramTester histogram_tester;
+
+  hints_manager()->RegisterOptimizationTypes({proto::COMPRESS_PUBLIC_IMAGES});
+  InitializeWithDefaultConfig("1.0.0.0");
+
+  // Set to online so fetch is activated.
+  SetConnectionOnline();
+
+  hints_manager()->SetHintsFetcherFactoryForTesting(
+      BuildTestHintsFetcherFactory(
+          {HintsFetcherEndState::kFetchSuccessWithURLHints}));
+
+  // Call this over the max count.
+  hints_manager()->CanApplyOptimizationOnDemand(
+      {url_with_url_keyed_hint()}, {proto::COMPRESS_PUBLIC_IMAGES},
+      proto::RequestContext::CONTEXT_BOOKMARKS,
+      base::DoNothingAs<void(
+          const GURL&,
+          const base::flat_map<proto::OptimizationType,
+                               OptimizationGuideDecisionWithMetadata>&)>());
+  hints_manager()->CanApplyOptimizationOnDemand(
+      {url_with_url_keyed_hint()}, {proto::COMPRESS_PUBLIC_IMAGES},
+      proto::RequestContext::CONTEXT_BOOKMARKS,
+      base::DoNothingAs<void(
+          const GURL&,
+          const base::flat_map<proto::OptimizationType,
+                               OptimizationGuideDecisionWithMetadata>&)>());
+  hints_manager()->CanApplyOptimizationOnDemand(
+      {url_with_url_keyed_hint()}, {proto::COMPRESS_PUBLIC_IMAGES},
+      proto::RequestContext::CONTEXT_BOOKMARKS,
+      base::DoNothingAs<void(
+          const GURL&,
+          const base::flat_map<proto::OptimizationType,
+                               OptimizationGuideDecisionWithMetadata>&)>());
+
+  // The third one is over the max and should evict another one.
+  histogram_tester.ExpectTotalCount(
+      "OptimizationGuide.HintsManager.ConcurrentBatchUpdateFetches", 3);
+  histogram_tester.ExpectBucketCount(
+      "OptimizationGuide.HintsManager.ConcurrentBatchUpdateFetches", 1, 1);
+  histogram_tester.ExpectBucketCount(
+      "OptimizationGuide.HintsManager.ConcurrentBatchUpdateFetches", 2, 2);
 }
 
 TEST_F(
@@ -3286,7 +3347,7 @@
   // Force timer to expire and schedule a hints fetch.
   MoveClockForwardBy(base::Seconds(kUpdateFetchHintsTimeSecs));
   // Hints fetcher should not even be created.
-  EXPECT_FALSE(batch_update_hints_fetcher());
+  EXPECT_FALSE(active_tabs_batch_update_hints_fetcher());
 }
 
 }  // namespace optimization_guide
diff --git a/components/optimization_guide/core/optimization_guide_features.cc b/components/optimization_guide/core/optimization_guide_features.cc
index 66f80905..cff2fd0 100644
--- a/components/optimization_guide/core/optimization_guide_features.cc
+++ b/components/optimization_guide/core/optimization_guide_features.cc
@@ -233,6 +233,15 @@
       "active_tabs_staleness_tolerance_in_days", 90));
 }
 
+size_t MaxConcurrentBatchUpdateFetches() {
+  // If overridden, this needs to be large enough where we do not thrash the
+  // inflight batch update fetches since if we approach the limit here, we will
+  // abort the oldest batch update fetch that is in flight.
+  return GetFieldTrialParamByFeatureAsInt(kRemoteOptimizationGuideFetching,
+                                          "max_concurrent_batch_update_fetches",
+                                          20);
+}
+
 size_t MaxConcurrentPageNavigationFetches() {
   // If overridden, this needs to be large enough where we do not thrash the
   // inflight page navigations since if we approach the limit here, we will
@@ -258,14 +267,15 @@
       "max_store_duration_for_host_model_features_in_days", 7));
 }
 
-base::TimeDelta StoredModelsInactiveDuration() {
+base::TimeDelta StoredModelsValidDuration() {
   // TODO(crbug.com/1234054) This field should not be changed without VERY
-  // careful consideration. Any model that is on device and expires will be
-  // removed and triggered to refetch so any feature relying on the model could
-  // have a period of time without a valid model.
+  // careful consideration. This is the default duration for models that do not
+  // specify retention, so changing this can cause models to be removed and
+  // refetch would only apply to newer models. Any feature relying on the model
+  // would have a period of time without a valid model, and would need to push a
+  // new version.
   return base::Days(GetFieldTrialParamByFeatureAsInt(
-      kOptimizationTargetPrediction, "inactive_duration_for_models_in_days",
-      30));
+      kOptimizationTargetPrediction, "valid_duration_for_models_in_days", 30));
 }
 
 base::TimeDelta URLKeyedHintValidCacheDuration() {
diff --git a/components/optimization_guide/core/optimization_guide_features.h b/components/optimization_guide/core/optimization_guide_features.h
index 80010d3..1596ac1 100644
--- a/components/optimization_guide/core/optimization_guide_features.h
+++ b/components/optimization_guide/core/optimization_guide_features.h
@@ -107,7 +107,11 @@
 base::TimeDelta GetActiveTabsStalenessTolerance();
 
 // Returns the max number of concurrent fetches to the remote Optimization Guide
-// Service that should be allowed.
+// Service that should be allowed for batch updates
+size_t MaxConcurrentBatchUpdateFetches();
+
+// Returns the max number of concurrent fetches to the remote Optimization Guide
+// Service that should be allowed for navigations.
 size_t MaxConcurrentPageNavigationFetches();
 
 // Returns the minimum number of seconds to randomly delay before starting to
@@ -130,7 +134,7 @@
 
 // The maximum duration for which models can remain in the
 // OptimizationGuideStore without being loaded.
-base::TimeDelta StoredModelsInactiveDuration();
+base::TimeDelta StoredModelsValidDuration();
 
 // The amount of time URL-keyed hints within the hint cache will be
 // allowed to be used and not be purged.
diff --git a/components/optimization_guide/core/optimization_guide_store.cc b/components/optimization_guide/core/optimization_guide_store.cc
index 97a50f7..635cac8 100644
--- a/components/optimization_guide/core/optimization_guide_store.cc
+++ b/components/optimization_guide/core/optimization_guide_store.cc
@@ -3,10 +3,13 @@
 // found in the LICENSE file.
 
 #include "components/optimization_guide/core/optimization_guide_store.h"
+#include <memory>
+#include <string>
 
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/files/file_util.h"
+#include "base/logging.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/sequence_checker.h"
@@ -20,6 +23,7 @@
 #include "components/optimization_guide/core/optimization_guide_features.h"
 #include "components/optimization_guide/core/optimization_guide_util.h"
 #include "components/optimization_guide/proto/hint_cache.pb.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace optimization_guide {
 
@@ -62,9 +66,7 @@
 // recorded when it goes out of scope and its destructor is called.
 class ScopedLoadMetadataResultRecorder {
  public:
-  ScopedLoadMetadataResultRecorder()
-      : result_(OptimizationGuideHintCacheLevelDBStoreLoadMetadataResult::
-                    kSuccess) {}
+  ScopedLoadMetadataResultRecorder() = default;
   ~ScopedLoadMetadataResultRecorder() {
     UMA_HISTOGRAM_ENUMERATION(
         "OptimizationGuide.HintCacheLevelDBStore.LoadMetadataResult", result_);
@@ -76,7 +78,8 @@
   }
 
  private:
-  OptimizationGuideHintCacheLevelDBStoreLoadMetadataResult result_;
+  OptimizationGuideHintCacheLevelDBStoreLoadMetadataResult result_ =
+      OptimizationGuideHintCacheLevelDBStoreLoadMetadataResult::kSuccess;
 };
 
 void RecordStatusChange(OptimizationGuideStore::Status status) {
@@ -963,14 +966,30 @@
   bool had_entries_to_update_or_remove =
       !update_vector->empty() || !remove_vector->empty();
   for (const auto& entry : *entries) {
-    bool should_delete_download_file = had_entries_to_update_or_remove;
+    absl::optional<std::string> delete_download_file;
+    if (had_entries_to_update_or_remove &&
+        entry.second.has_prediction_model() &&
+        !entry.second.prediction_model().model().download_url().empty()) {
+      delete_download_file =
+          entry.second.prediction_model().model().download_url();
+    }
+
     // Only check expiry if we weren't explicitly passed in entries to update or
     // remove.
     if (!had_entries_to_update_or_remove) {
+      if (entry.second.keep_beyond_valid_duration()) {
+        continue;
+      }
       if (entry.second.has_expiry_time_secs()) {
         if (entry.second.expiry_time_secs() <= now_since_epoch) {
+          // Update the entry to remove the model.
+          if (entry.second.has_prediction_model() &&
+              !entry.second.prediction_model().model().download_url().empty()) {
+            delete_download_file =
+                entry.second.prediction_model().model().download_url();
+          }
+
           remove_vector->push_back(entry.first);
-          should_delete_download_file = true;
           proto::OptimizationTarget optimization_target =
               GetOptimizationTargetFromPredictionModelEntryKey(entry.first);
           base::UmaHistogramBoolean(
@@ -984,20 +1003,17 @@
         update_vector->push_back(entry);
         update_vector->back().second.set_expiry_time_secs(
             now_since_epoch +
-            features::StoredModelsInactiveDuration().InSeconds());
+            features::StoredModelsValidDuration().InSeconds());
       }
     }
 
     // Delete files (the model itself and any additional files) that are
     // provided by the model in its directory.
-    if (should_delete_download_file && entry.second.has_prediction_model() &&
-        !entry.second.prediction_model().model().download_url().empty()) {
-      // |GetFilePathFromPredictionModel| only returns nullopt when
-      // |model().download_url()| is empty.
+    if (delete_download_file) {
+      // |StringToFilePath| only returns nullopt when
+      // |delete_download_file| is empty.
       base::FilePath model_file_path =
-          StringToFilePath(
-              entry.second.prediction_model().model().download_url())
-              .value();
+          StringToFilePath(*delete_download_file).value();
       base::FilePath path_to_delete;
 
       // Backwards compatibility: Once upon a time (<M93), model files were
@@ -1043,9 +1059,8 @@
   *out_prediction_model_entry_key =
       GetPredictionModelEntryKeyPrefix() +
       base::NumberToString(static_cast<int>(optimization_target));
-  if (entry_keys_->find(*out_prediction_model_entry_key) != entry_keys_->end())
-    return true;
-  return false;
+  return entry_keys_->find(*out_prediction_model_entry_key) !=
+         entry_keys_->end();
 }
 
 bool OptimizationGuideStore::RemovePredictionModelFromEntryKey(
@@ -1107,6 +1122,17 @@
     std::move(callback).Run(std::move(loaded_prediction_model));
     return;
   }
+  // Also ensure that nothing is returned if the model is expired.
+  int64_t now_since_epoch =
+      base::Time::Now().ToDeltaSinceWindowsEpoch().InSeconds();
+  if (!entry->keep_beyond_valid_duration() &&
+      entry->expiry_time_secs() <= now_since_epoch) {
+    // Leave the actual model deletions to |PurgeInactiveModels| and return
+    // early.
+    std::unique_ptr<proto::PredictionModel> loaded_prediction_model(nullptr);
+    std::move(callback).Run(std::move(loaded_prediction_model));
+    return;
+  }
 
   std::unique_ptr<proto::PredictionModel> loaded_prediction_model(
       entry->release_prediction_model());
diff --git a/components/optimization_guide/core/optimization_guide_store.h b/components/optimization_guide/core/optimization_guide_store.h
index 76c36ce..3aebca22 100644
--- a/components/optimization_guide/core/optimization_guide_store.h
+++ b/components/optimization_guide/core/optimization_guide_store.h
@@ -179,6 +179,7 @@
 
   // Removes all models that have not been loaded in the max inactive duration
   // configured. |entry_keys| is updated after the inactive models are removed.
+  // Respects models' |keep_beyond_valid_duration| setting.
   void PurgeInactiveModels();
 
   // Creates and returns a StoreUpdateData object for Prediction Models. This
@@ -196,9 +197,9 @@
       std::unique_ptr<StoreUpdateData> prediction_models_update_data,
       base::OnceClosure callback);
 
-  // Finds the entry key for the prediction model if it is known to the store.
-  // Returns true if an entry key is found and |out_prediction_model_entry_key|
-  // is populated with the matching key.
+  // Finds the entry key for the prediction model if it is still valid in the
+  // store. Returns true if an entry key is valid and
+  // |out_prediction_model_entry_key| is populated with any matching key.
   // Virtualized for testing.
   virtual bool FindPredictionModelEntryKey(
       proto::OptimizationTarget optimization_target,
diff --git a/components/optimization_guide/core/optimization_guide_store_unittest.cc b/components/optimization_guide/core/optimization_guide_store_unittest.cc
index b1ea475..9bd9d7e 100644
--- a/components/optimization_guide/core/optimization_guide_store_unittest.cc
+++ b/components/optimization_guide/core/optimization_guide_store_unittest.cc
@@ -176,20 +176,24 @@
       StoreUpdateData* update_data,
       optimization_guide::proto::OptimizationTarget optimization_target,
       absl::optional<base::FilePath> model_file_path = absl::nullopt,
-      base::flat_set<base::FilePath> additional_file_paths = {}) {
+      absl::optional<proto::ModelInfo> info = absl::nullopt,
+      absl::optional<base::Time> expiry_time = absl::nullopt) {
     std::unique_ptr<optimization_guide::proto::PredictionModel>
         prediction_model = CreatePredictionModel();
+    if (info)
+      prediction_model->mutable_model_info()->MergeFrom(*info);
     prediction_model->mutable_model_info()->set_optimization_target(
         optimization_target);
+    if (expiry_time) {
+      auto diff = expiry_time.value() - base::Time::Now();
+      prediction_model->mutable_model_info()
+          ->mutable_valid_duration()
+          ->set_seconds(diff.InSeconds());
+    }
     if (model_file_path) {
       prediction_model->mutable_model()->set_download_url(
           FilePathToString(*model_file_path));
     }
-    for (const base::FilePath& additional_file : additional_file_paths) {
-      prediction_model->mutable_model_info()
-          ->add_additional_files()
-          ->set_file_path(FilePathToString(additional_file));
-    }
     update_data->CopyPredictionModelIntoUpdateData(*prediction_model);
   }
 
@@ -1945,7 +1949,7 @@
   std::unique_ptr<StoreUpdateData> update_data =
       guide_store()->CreateUpdateDataForPredictionModels(
           update_time +
-          optimization_guide::features::StoredModelsInactiveDuration());
+          optimization_guide::features::StoredModelsValidDuration());
   ASSERT_TRUE(update_data);
   SeedPredictionModelUpdateData(update_data.get(),
                                 proto::OPTIMIZATION_TARGET_UNKNOWN);
@@ -1971,7 +1975,7 @@
   std::unique_ptr<StoreUpdateData> update_data =
       guide_store()->CreateUpdateDataForPredictionModels(
           update_time +
-          optimization_guide::features::StoredModelsInactiveDuration());
+          optimization_guide::features::StoredModelsValidDuration());
   ASSERT_TRUE(update_data);
   SeedPredictionModelUpdateData(update_data.get(),
                                 proto::OPTIMIZATION_TARGET_UNKNOWN);
@@ -1994,7 +1998,7 @@
   std::unique_ptr<StoreUpdateData> update_data =
       guide_store()->CreateUpdateDataForPredictionModels(
           update_time +
-          optimization_guide::features::StoredModelsInactiveDuration());
+          optimization_guide::features::StoredModelsValidDuration());
   ASSERT_TRUE(update_data);
   SeedPredictionModelUpdateData(update_data.get(),
                                 proto::OPTIMIZATION_TARGET_UNKNOWN);
@@ -2034,7 +2038,7 @@
   std::unique_ptr<StoreUpdateData> update_data =
       guide_store()->CreateUpdateDataForPredictionModels(
           update_time +
-          optimization_guide::features::StoredModelsInactiveDuration());
+          optimization_guide::features::StoredModelsValidDuration());
   ASSERT_TRUE(update_data);
   SeedPredictionModelUpdateData(update_data.get(),
                                 proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
@@ -2076,6 +2080,46 @@
   EXPECT_FALSE(last_loaded_prediction_model());
 }
 
+TEST_F(OptimizationGuideStoreTest, LoadPredictionModelOnExpiredModel) {
+  base::HistogramTester histogram_tester;
+  size_t initial_hint_count = 10;
+  MetadataSchemaState schema_state = MetadataSchemaState::kValid;
+  SeedInitialData(schema_state, initial_hint_count);
+  CreateDatabase();
+  InitializeStore(schema_state);
+
+  // Add an update with models that are "inactive".
+  base::Time update_time = base::Time().Now();
+  std::unique_ptr<StoreUpdateData> update_data =
+      guide_store()->CreateUpdateDataForPredictionModels(
+          update_time -
+          optimization_guide::features::StoredModelsValidDuration());
+  ASSERT_TRUE(update_data);
+  SeedPredictionModelUpdateData(
+      update_data.get(), proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD,
+      /*model_file_path=*/absl::nullopt,
+      /*info=*/{},
+      update_time - optimization_guide::features::StoredModelsValidDuration());
+  UpdatePredictionModels(std::move(update_data));
+  // Since models haven't been purged yet it will initially show up as valid.
+  OptimizationGuideStore::EntryKey entry_key;
+  EXPECT_TRUE(guide_store()->FindPredictionModelEntryKey(
+      proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD, &entry_key));
+  guide_store()->LoadPredictionModel(
+      entry_key,
+      base::BindOnce(&OptimizationGuideStoreTest::OnPredictionModelLoaded,
+                     base::Unretained(this)));
+  // OnPredictionModelLoaded callback
+  db()->GetCallback(true);
+  // After load completes, the key will still exist until after purge.
+  EXPECT_TRUE(guide_store()->FindPredictionModelEntryKey(
+      proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD, &entry_key));
+
+  // Verify that the OnPredictionModelLoaded callback runs when the store is
+  // unavailable and that the prediction model was correctly not available.
+  EXPECT_FALSE(last_loaded_prediction_model());
+}
+
 TEST_F(OptimizationGuideStoreTest, LoadPredictionModelWithUpdateInFlight) {
   base::HistogramTester histogram_tester;
   MetadataSchemaState schema_state = MetadataSchemaState::kValid;
@@ -2087,7 +2131,7 @@
   std::unique_ptr<StoreUpdateData> update_data =
       guide_store()->CreateUpdateDataForPredictionModels(
           update_time +
-          optimization_guide::features::StoredModelsInactiveDuration());
+          optimization_guide::features::StoredModelsValidDuration());
   ASSERT_TRUE(update_data);
   SeedPredictionModelUpdateData(update_data.get(),
                                 proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
@@ -2117,7 +2161,7 @@
   std::unique_ptr<StoreUpdateData> update_data =
       guide_store()->CreateUpdateDataForPredictionModels(
           update_time +
-          optimization_guide::features::StoredModelsInactiveDuration());
+          optimization_guide::features::StoredModelsValidDuration());
   ASSERT_TRUE(update_data);
   SeedPredictionModelUpdateData(update_data.get(),
                                 proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD,
@@ -2164,7 +2208,7 @@
   std::unique_ptr<StoreUpdateData> update_data =
       guide_store()->CreateUpdateDataForPredictionModels(
           update_time +
-          optimization_guide::features::StoredModelsInactiveDuration());
+          optimization_guide::features::StoredModelsValidDuration());
   ASSERT_TRUE(update_data);
 
   base::FilePath model_file_path = temp_dir().AppendASCII("model.tflite");
@@ -2173,9 +2217,12 @@
 
   base::FilePath additional_file_path = temp_dir().AppendASCII("doesntexist");
 
-  SeedPredictionModelUpdateData(
-      update_data.get(), proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD,
-      temp_dir().AppendASCII("doesntexist"), {additional_file_path});
+  proto::ModelInfo info;
+  info.add_additional_files()->set_file_path(
+      FilePathToString(additional_file_path));
+  SeedPredictionModelUpdateData(update_data.get(),
+                                proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD,
+                                temp_dir().AppendASCII("doesntexist"), info);
   UpdatePredictionModels(std::move(update_data));
 
   OptimizationGuideStore::EntryKey entry_key;
@@ -2221,7 +2268,7 @@
   std::unique_ptr<StoreUpdateData> update_data =
       guide_store()->CreateUpdateDataForPredictionModels(
           update_time +
-          optimization_guide::features::StoredModelsInactiveDuration());
+          optimization_guide::features::StoredModelsValidDuration());
   ASSERT_TRUE(update_data);
 
   base::FilePath model_file_path = temp_dir().AppendASCII("model.tflite");
@@ -2232,10 +2279,12 @@
       temp_dir().AppendASCII("additional_file.txt");
   ASSERT_EQ(static_cast<int32_t>(3),
             base::WriteFile(additional_file_path, "ah!", 3));
-
+  proto::ModelInfo info;
+  info.add_additional_files()->set_file_path(
+      FilePathToString(additional_file_path));
   SeedPredictionModelUpdateData(update_data.get(),
                                 proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD,
-                                model_file_path, {additional_file_path});
+                                model_file_path, info);
   UpdatePredictionModels(std::move(update_data));
 
   OptimizationGuideStore::EntryKey entry_key;
@@ -2277,7 +2326,7 @@
   std::unique_ptr<StoreUpdateData> update_data =
       guide_store()->CreateUpdateDataForPredictionModels(
           update_time +
-          optimization_guide::features::StoredModelsInactiveDuration());
+          optimization_guide::features::StoredModelsValidDuration());
   ASSERT_TRUE(update_data);
   base::FilePath file_path = temp_dir().AppendASCII("file");
   ASSERT_EQ(static_cast<int32_t>(3), base::WriteFile(file_path, "boo", 3));
@@ -2324,7 +2373,7 @@
   std::unique_ptr<StoreUpdateData> update_data =
       guide_store()->CreateUpdateDataForPredictionModels(
           update_time +
-          optimization_guide::features::StoredModelsInactiveDuration());
+          optimization_guide::features::StoredModelsValidDuration());
   ASSERT_TRUE(update_data);
   base::FilePath old_file_path = old_dir.AppendASCII("model.tflite");
   ASSERT_EQ(static_cast<int32_t>(3), base::WriteFile(old_file_path, "boo", 3));
@@ -2341,7 +2390,7 @@
   std::unique_ptr<StoreUpdateData> update_data2 =
       guide_store()->CreateUpdateDataForPredictionModels(
           update_time +
-          optimization_guide::features::StoredModelsInactiveDuration());
+          optimization_guide::features::StoredModelsValidDuration());
   ASSERT_TRUE(update_data2);
   base::FilePath new_file_path = new_dir.AppendASCII("model.tflite");
   ASSERT_EQ(static_cast<int32_t>(3), base::WriteFile(new_file_path, "boo", 3));
@@ -2380,7 +2429,7 @@
   std::unique_ptr<StoreUpdateData> update_data =
       guide_store()->CreateUpdateDataForPredictionModels(
           update_time +
-          optimization_guide::features::StoredModelsInactiveDuration());
+          optimization_guide::features::StoredModelsValidDuration());
   ASSERT_TRUE(update_data);
   base::FilePath old_file_path = old_dir.AppendASCII("model_v1.tflite");
   ASSERT_EQ(static_cast<int32_t>(3), base::WriteFile(old_file_path, "boo", 3));
@@ -2397,7 +2446,7 @@
   std::unique_ptr<StoreUpdateData> update_data2 =
       guide_store()->CreateUpdateDataForPredictionModels(
           update_time +
-          optimization_guide::features::StoredModelsInactiveDuration());
+          optimization_guide::features::StoredModelsValidDuration());
   ASSERT_TRUE(update_data2);
   base::FilePath new_file_path = new_dir.Append(GetBaseFileNameForModels());
   ASSERT_EQ(static_cast<int32_t>(3), base::WriteFile(new_file_path, "boo", 3));
@@ -2429,7 +2478,7 @@
   std::unique_ptr<StoreUpdateData> update_data =
       guide_store()->CreateUpdateDataForPredictionModels(
           update_time +
-          optimization_guide::features::StoredModelsInactiveDuration());
+          optimization_guide::features::StoredModelsValidDuration());
   ASSERT_TRUE(update_data);
   base::FilePath file_path = temp_dir().AppendASCII("file");
   ASSERT_EQ(static_cast<int32_t>(3), base::WriteFile(file_path, "boo", 3));
@@ -2669,17 +2718,22 @@
   std::unique_ptr<StoreUpdateData> update_data =
       guide_store()->CreateUpdateDataForPredictionModels(
           update_time -
-          optimization_guide::features::StoredModelsInactiveDuration());
+          optimization_guide::features::StoredModelsValidDuration());
   ASSERT_TRUE(update_data);
-  SeedPredictionModelUpdateData(update_data.get(),
-                                proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
+  base::FilePath old_file_path = temp_dir().AppendASCII("model_v1.tflite");
+  ASSERT_EQ(static_cast<int32_t>(3), base::WriteFile(old_file_path, "boo", 3));
+  SeedPredictionModelUpdateData(
+      update_data.get(), proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD,
+      old_file_path,
+      /*info=*/{},
+      update_time - optimization_guide::features::StoredModelsValidDuration());
   UpdatePredictionModels(std::move(update_data));
 
   // Add an update with models that are "active".
   std::unique_ptr<StoreUpdateData> update_data2 =
       guide_store()->CreateUpdateDataForPredictionModels(
           update_time +
-          optimization_guide::features::StoredModelsInactiveDuration());
+          optimization_guide::features::StoredModelsValidDuration());
   ASSERT_TRUE(update_data2);
   SeedPredictionModelUpdateData(update_data2.get(),
                                 proto::OPTIMIZATION_TARGET_LANGUAGE_DETECTION);
@@ -2695,9 +2749,12 @@
   ASSERT_TRUE(success);
 
   PurgeInactiveModels();
-
+  RunUntilIdle();
+  // The expired model should be removed.
   EXPECT_FALSE(guide_store()->FindPredictionModelEntryKey(
       proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD, &entry_key));
+  EXPECT_FALSE(base::PathExists(old_file_path));
+
   // Should not purge models that are still active.
   EXPECT_TRUE(guide_store()->FindPredictionModelEntryKey(
       proto::OPTIMIZATION_TARGET_LANGUAGE_DETECTION, &entry_key));
@@ -2708,4 +2765,121 @@
       "OptimizationGuide.PredictionModelExpired.LanguageDetection", 0);
 }
 
+struct ValidityTestCase {
+  std::string test_name;
+  bool keep_beyond_valid_duration;
+  bool initially_expired;
+  bool expect_kept;
+};
+
+class OptimizationGuideStoreValidityTest
+    : public OptimizationGuideStoreTest,
+      public ::testing::WithParamInterface<ValidityTestCase> {};
+
+TEST_P(OptimizationGuideStoreValidityTest, PurgeInactiveModels) {
+  const ValidityTestCase& test_case = GetParam();
+  base::HistogramTester histogram_tester;
+
+  MetadataSchemaState schema_state = MetadataSchemaState::kValid;
+  SeedInitialData(schema_state, 0);
+  CreateDatabase();
+  InitializeStore(schema_state);
+
+  // Add an update with one model according to ValidityTestCase settings.
+  base::Time update_time = base::Time().Now();
+  if (test_case.initially_expired) {
+    update_time -= optimization_guide::features::StoredModelsValidDuration();
+  } else {
+    update_time += optimization_guide::features::StoredModelsValidDuration();
+  }
+  std::unique_ptr<StoreUpdateData> update_data =
+      guide_store()->CreateUpdateDataForPredictionModels(update_time);
+  ASSERT_TRUE(update_data);
+  proto::ModelInfo info;
+  info.set_keep_beyond_valid_duration(test_case.keep_beyond_valid_duration);
+  base::FilePath old_file_path = temp_dir().AppendASCII("model_v1.tflite");
+  ASSERT_EQ(static_cast<int32_t>(3), base::WriteFile(old_file_path, "boo", 3));
+  SeedPredictionModelUpdateData(update_data.get(),
+                                proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD,
+                                old_file_path, info, update_time);
+  UpdatePredictionModels(std::move(update_data));
+
+  // Add an update with models that are "active" and should be unaffected.
+  std::unique_ptr<StoreUpdateData> update_data2 =
+      guide_store()->CreateUpdateDataForPredictionModels(
+          base::Time().Now() +
+          optimization_guide::features::StoredModelsValidDuration());
+  ASSERT_TRUE(update_data2);
+  SeedPredictionModelUpdateData(update_data2.get(),
+                                proto::OPTIMIZATION_TARGET_LANGUAGE_DETECTION);
+  UpdatePredictionModels(std::move(update_data2));
+
+  // Make sure both models are in the store.
+  OptimizationGuideStore::EntryKey entry_key;
+  bool success = guide_store()->FindPredictionModelEntryKey(
+      proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD, &entry_key);
+  ASSERT_TRUE(success);
+  EXPECT_TRUE(base::PathExists(old_file_path));
+
+  success = guide_store()->FindPredictionModelEntryKey(
+      proto::OPTIMIZATION_TARGET_LANGUAGE_DETECTION, &entry_key);
+  ASSERT_TRUE(success);
+
+  PurgeInactiveModels();
+  RunUntilIdle();
+  // Verify that the model file, entry key and histogram match expectations for
+  // PageLoad.
+  EXPECT_EQ(test_case.expect_kept,
+            guide_store()->FindPredictionModelEntryKey(
+                proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD, &entry_key));
+  EXPECT_EQ(test_case.expect_kept, base::PathExists(old_file_path));
+
+  if (test_case.expect_kept) {
+    histogram_tester.ExpectTotalCount(
+        "OptimizationGuide.PredictionModelExpired.PainfulPageLoad", 0);
+  } else {
+    histogram_tester.ExpectTotalCount(
+        "OptimizationGuide.PredictionModelExpired.PainfulPageLoad", 1);
+  }
+  // Verify that the other model is not deleted.
+  EXPECT_TRUE(guide_store()->FindPredictionModelEntryKey(
+      proto::OPTIMIZATION_TARGET_LANGUAGE_DETECTION, &entry_key));
+  histogram_tester.ExpectTotalCount(
+      "OptimizationGuide.PredictionModelExpired.LanguageDetection", 0);
+}
+INSTANTIATE_TEST_SUITE_P(
+    OptimizationGuideStoreValidityTests,
+    OptimizationGuideStoreValidityTest,
+    testing::ValuesIn<ValidityTestCase>({
+        {
+            "KeepDespiteInvalidModel",
+            /*keep_beyond_valid_duration=*/true,
+            /*initially_expired=*/true,
+            /*expect_kept=*/true,
+        },
+        {
+            "KeepAndInitiallyValid",
+            /*keep_beyond_valid_duration=*/true,
+            /*initially_expired=*/false,
+            /*expect_kept=*/true,
+        },
+        {
+            "DeleteAndInitiallyValid",
+            /*keep_beyond_valid_duration=*/false,
+            /*initially_expired=*/false,
+            /*expect_kept=*/true,
+        },
+        // Only in this case should the model be removed.
+        {
+            "DeleteAndInvalidModel",
+            /*keep_beyond_valid_duration=*/false,
+            /*initially_expired=*/true,
+            /*expect_kept=*/false,
+        },
+    }),
+    [](const testing::TestParamInfo<
+        OptimizationGuideStoreValidityTest::ParamType>& info) {
+      return info.param.test_name;
+    });
+
 }  // namespace optimization_guide
diff --git a/components/optimization_guide/core/store_update_data.cc b/components/optimization_guide/core/store_update_data.cc
index 0afd12d3e..74dcc732 100644
--- a/components/optimization_guide/core/store_update_data.cc
+++ b/components/optimization_guide/core/store_update_data.cc
@@ -200,8 +200,19 @@
   proto::StoreEntry entry_proto;
   entry_proto.set_entry_type(static_cast<proto::StoreEntryType>(
       OptimizationGuideStore::StoreEntryType::kPredictionModel));
+
+  base::TimeDelta expiry_duration;
+  if (prediction_model.model_info().has_valid_duration()) {
+    expiry_duration =
+        base::Seconds(prediction_model.model_info().valid_duration().seconds());
+  } else {
+    expiry_duration = features::StoredFetchedHintsFreshnessDuration();
+  }
+  expiry_time_ = base::Time::Now() + expiry_duration;
   entry_proto.set_expiry_time_secs(
-      expiry_time_->ToDeltaSinceWindowsEpoch().InSeconds());
+      expiry_time_.value().ToDeltaSinceWindowsEpoch().InSeconds());
+  entry_proto.set_keep_beyond_valid_duration(
+      prediction_model.model_info().keep_beyond_valid_duration());
   entry_proto.mutable_prediction_model()->CopyFrom(prediction_model);
   entries_to_save_->emplace_back(std::move(prediction_model_entry_key),
                                  std::move(entry_proto));
diff --git a/components/optimization_guide/core/store_update_data_unittest.cc b/components/optimization_guide/core/store_update_data_unittest.cc
index edac180..9a38940 100644
--- a/components/optimization_guide/core/store_update_data_unittest.cc
+++ b/components/optimization_guide/core/store_update_data_unittest.cc
@@ -122,9 +122,11 @@
       proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
   model_info->add_supported_model_engine_versions(
       proto::ModelEngineVersion::MODEL_ENGINE_VERSION_DECISION_TREE);
+  model_info->set_keep_beyond_valid_duration(false);
 
-  base::Time expected_expiry_time =
-      base::Time::Now() + features::StoredModelsInactiveDuration();
+  model_info->mutable_valid_duration()->set_seconds(3);
+
+  base::Time expected_expiry_time = base::Time::Now() + base::Seconds(3);
   std::unique_ptr<StoreUpdateData> prediction_model_update =
       StoreUpdateData::CreatePredictionModelStoreUpdateData(
           expected_expiry_time);
@@ -143,6 +145,8 @@
       found_prediction_model_entry = true;
       EXPECT_EQ(expected_expiry_time.ToDeltaSinceWindowsEpoch().InSeconds(),
                 store_entry.expiry_time_secs());
+      EXPECT_EQ(store_entry.keep_beyond_valid_duration(),
+                model_info->keep_beyond_valid_duration());
       break;
     }
   }
diff --git a/components/optimization_guide/proto/hint_cache.proto b/components/optimization_guide/proto/hint_cache.proto
index a27430d3..e89dec2 100644
--- a/components/optimization_guide/proto/hint_cache.proto
+++ b/components/optimization_guide/proto/hint_cache.proto
@@ -59,4 +59,6 @@
   optional PredictionModel prediction_model = 6;
   // The actual HostModelFeature data.
   optional HostModelFeatures host_model_features = 7;
+  // Whether to delete a model once expiry_time_secs is past.
+  optional bool keep_beyond_valid_duration = 8;
 }
diff --git a/components/optimization_guide/proto/models.proto b/components/optimization_guide/proto/models.proto
index ea953cc..e36ff0c 100644
--- a/components/optimization_guide/proto/models.proto
+++ b/components/optimization_guide/proto/models.proto
@@ -200,7 +200,7 @@
 
 // Metadata for a prediction model for a specific optimization target.
 //
-// Next ID: 8
+// Next ID: 10
 message ModelInfo {
   reserved 3;
 
@@ -223,6 +223,11 @@
   // This does not need to be sent to the server in the request for an update to
   // this model. The server will ignore this if sent.
   repeated AdditionalModelFile additional_files = 7;
+  // How long the model will remain valid in client storage. If
+  // |keep_beyond_valid_duration| is true, will be ignored.
+  optional Duration valid_duration = 8;
+  // Whether to delete the model once valid_duration has passed.
+  optional bool keep_beyond_valid_duration = 9;
   // Mechanism used for model owners to attach metadata to the request or
   // response.
   //
diff --git a/components/prefs/BUILD.gn b/components/prefs/BUILD.gn
index 0dd1a50a..e8d2069 100644
--- a/components/prefs/BUILD.gn
+++ b/components/prefs/BUILD.gn
@@ -2,6 +2,8 @@
 # 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")
+
 component("prefs") {
   sources = [
     "command_line_pref_store.cc",
@@ -63,6 +65,13 @@
     ]
     deps += [ "android:jni_headers" ]
   }
+
+  if (is_chromeos_ash) {
+    sources += [
+      "standalone_browser_pref_store.cc",
+      "standalone_browser_pref_store.h",
+    ]
+  }
 }
 
 static_library("test_support") {
diff --git a/components/prefs/pref_service.cc b/components/prefs/pref_service.cc
index ae6cdad..3644899d 100644
--- a/components/prefs/pref_service.cc
+++ b/components/prefs/pref_service.cc
@@ -435,8 +435,8 @@
   pref_value_store_ = pref_value_store_->CloneAndSpecialize(
       managed_prefs, supervised_user_prefs, extension_prefs,
       nullptr /* command_line_prefs */, nullptr /* user_prefs */,
-      recommended_prefs, nullptr /* default_prefs */, pref_notifier_.get(),
-      std::move(delegate));
+      nullptr /* standalone_browser_prefs */, recommended_prefs,
+      nullptr /* default_prefs */, pref_notifier_.get(), std::move(delegate));
 
   // Notify |pref_notifier_| on all changed values.
   for (const auto& kv : pref_changed_map) {
diff --git a/components/prefs/pref_service_factory.cc b/components/prefs/pref_service_factory.cc
index 2d4cad0..db6fcfe 100644
--- a/components/prefs/pref_service_factory.cc
+++ b/components/prefs/pref_service_factory.cc
@@ -34,9 +34,10 @@
   auto pref_notifier = std::make_unique<PrefNotifierImpl>();
   auto pref_value_store = std::make_unique<PrefValueStore>(
       managed_prefs_.get(), supervised_user_prefs_.get(),
-      extension_prefs_.get(), command_line_prefs_.get(), user_prefs_.get(),
-      recommended_prefs_.get(), pref_registry->defaults().get(),
-      pref_notifier.get(), std::move(delegate));
+      extension_prefs_.get(), standalone_browser_prefs_.get(),
+      command_line_prefs_.get(), user_prefs_.get(), recommended_prefs_.get(),
+      pref_registry->defaults().get(), pref_notifier.get(),
+      std::move(delegate));
   return std::make_unique<PrefService>(
       std::move(pref_notifier), std::move(pref_value_store), user_prefs_.get(),
       std::move(pref_registry), read_error_callback_, async_);
diff --git a/components/prefs/pref_service_factory.h b/components/prefs/pref_service_factory.h
index 2683d36..8c5ca7f 100644
--- a/components/prefs/pref_service_factory.h
+++ b/components/prefs/pref_service_factory.h
@@ -43,6 +43,10 @@
     extension_prefs_.swap(prefs);
   }
 
+  void set_standalone_browser_prefs(scoped_refptr<PrefStore> prefs) {
+    standalone_browser_prefs_.swap(prefs);
+  }
+
   void set_command_line_prefs(scoped_refptr<PrefStore> prefs) {
     command_line_prefs_.swap(prefs);
   }
@@ -87,6 +91,7 @@
   scoped_refptr<PrefStore> managed_prefs_;
   scoped_refptr<PrefStore> supervised_user_prefs_;
   scoped_refptr<PrefStore> extension_prefs_;
+  scoped_refptr<PrefStore> standalone_browser_prefs_;
   scoped_refptr<PrefStore> command_line_prefs_;
   scoped_refptr<PersistentPrefStore> user_prefs_;
   scoped_refptr<PrefStore> recommended_prefs_;
diff --git a/components/prefs/pref_service_unittest.cc b/components/prefs/pref_service_unittest.cc
index b31ed70..4af2d84 100644
--- a/components/prefs/pref_service_unittest.cc
+++ b/components/prefs/pref_service_unittest.cc
@@ -499,9 +499,10 @@
     auto pref_notifier = std::make_unique<PrefNotifierImpl>();
     auto pref_value_store = std::make_unique<PrefValueStore>(
         nullptr /* managed_prefs */, nullptr /* supervised_user_prefs */,
-        nullptr /* extension_prefs */, new TestingPrefStore(),
-        user_pref_store_.get(), nullptr /* recommended_prefs */,
-        pref_registry_->defaults().get(), pref_notifier.get());
+        nullptr /* extension_prefs */, nullptr /* standalone_browser_prefs */,
+        new TestingPrefStore(), user_pref_store_.get(),
+        nullptr /* recommended_prefs */, pref_registry_->defaults().get(),
+        pref_notifier.get());
     pref_service_ = std::make_unique<PrefService>(
         std::move(pref_notifier), std::move(pref_value_store), user_pref_store_,
         pref_registry_, base::DoNothing(), false);
diff --git a/components/prefs/pref_value_store.cc b/components/prefs/pref_value_store.cc
index 4a238d06..efa4027 100644
--- a/components/prefs/pref_value_store.cc
+++ b/components/prefs/pref_value_store.cc
@@ -49,6 +49,7 @@
 PrefValueStore::PrefValueStore(PrefStore* managed_prefs,
                                PrefStore* supervised_user_prefs,
                                PrefStore* extension_prefs,
+                               PrefStore* standalone_browser_prefs,
                                PrefStore* command_line_prefs,
                                PrefStore* user_prefs,
                                PrefStore* recommended_prefs,
@@ -61,6 +62,7 @@
   InitPrefStore(MANAGED_STORE, managed_prefs);
   InitPrefStore(SUPERVISED_USER_STORE, supervised_user_prefs);
   InitPrefStore(EXTENSION_STORE, extension_prefs);
+  InitPrefStore(STANDALONE_BROWSER_STORE, standalone_browser_prefs);
   InitPrefStore(COMMAND_LINE_STORE, command_line_prefs);
   InitPrefStore(USER_STORE, user_prefs);
   InitPrefStore(RECOMMENDED_STORE, recommended_prefs);
@@ -69,8 +71,8 @@
   CheckInitializationCompleted();
   if (delegate_) {
     delegate_->Init(managed_prefs, supervised_user_prefs, extension_prefs,
-                    command_line_prefs, user_prefs, recommended_prefs,
-                    default_prefs, pref_notifier);
+                    standalone_browser_prefs, command_line_prefs, user_prefs,
+                    recommended_prefs, default_prefs, pref_notifier);
   }
 }
 
@@ -80,6 +82,7 @@
     PrefStore* managed_prefs,
     PrefStore* supervised_user_prefs,
     PrefStore* extension_prefs,
+    PrefStore* standalone_browser_prefs,
     PrefStore* command_line_prefs,
     PrefStore* user_prefs,
     PrefStore* recommended_prefs,
@@ -93,6 +96,8 @@
     supervised_user_prefs = GetPrefStore(SUPERVISED_USER_STORE);
   if (!extension_prefs)
     extension_prefs = GetPrefStore(EXTENSION_STORE);
+  if (!standalone_browser_prefs)
+    standalone_browser_prefs = GetPrefStore(STANDALONE_BROWSER_STORE);
   if (!command_line_prefs)
     command_line_prefs = GetPrefStore(COMMAND_LINE_STORE);
   if (!user_prefs)
@@ -103,9 +108,9 @@
     default_prefs = GetPrefStore(DEFAULT_STORE);
 
   return std::make_unique<PrefValueStore>(
-      managed_prefs, supervised_user_prefs, extension_prefs, command_line_prefs,
-      user_prefs, recommended_prefs, default_prefs, pref_notifier,
-      std::move(delegate));
+      managed_prefs, supervised_user_prefs, extension_prefs,
+      standalone_browser_prefs, command_line_prefs, user_prefs,
+      recommended_prefs, default_prefs, pref_notifier, std::move(delegate));
 }
 
 bool PrefValueStore::GetValue(const std::string& name,
diff --git a/components/prefs/pref_value_store.h b/components/prefs/pref_value_store.h
index 9808056..ea483807 100644
--- a/components/prefs/pref_value_store.h
+++ b/components/prefs/pref_value_store.h
@@ -45,6 +45,7 @@
     virtual void Init(PrefStore* managed_prefs,
                       PrefStore* supervised_user_prefs,
                       PrefStore* extension_prefs,
+                      PrefStore* standalone_browser_prefs,
                       PrefStore* command_line_prefs,
                       PrefStore* user_prefs,
                       PrefStore* recommended_prefs,
@@ -64,15 +65,19 @@
   };
 
   // PrefStores must be listed here in order from highest to lowest priority.
-  //   MANAGED contains all managed preference values that are provided by
+  //   MANAGED contains all managed preferences that are provided by
   //      mandatory policies (e.g. Windows Group Policy or cloud policy).
   //   SUPERVISED_USER contains preferences that are valid for supervised users.
-  //   EXTENSION contains preference values set by extensions.
-  //   COMMAND_LINE contains preference values set by command-line switches.
-  //   USER contains all user-set preference values.
+  //   EXTENSION contains preferences set by extensions.
+  //   STANDALONE_BROWSER contains system preferences inherited from a separate
+  //      Chrome instance. One relevant source is extension prefs in lacros
+  //      passed to ash, so these prefs have similar precedence to extension
+  //      prefs.
+  //   COMMAND_LINE contains preferences set by command-line switches.
+  //   USER contains all user-set preferences.
   //   RECOMMENDED contains all preferences that are provided by recommended
   //      policies.
-  //   DEFAULT contains all application default preference values.
+  //   DEFAULT contains all application default preferences.
   enum PrefStoreType {
     // INVALID_STORE is not associated with an actual PrefStore but used as
     // an invalid marker, e.g. as a return value.
@@ -80,6 +85,7 @@
     MANAGED_STORE = 0,
     SUPERVISED_USER_STORE,
     EXTENSION_STORE,
+    STANDALONE_BROWSER_STORE,
     COMMAND_LINE_STORE,
     USER_STORE,
     RECOMMENDED_STORE,
@@ -105,6 +111,7 @@
   PrefValueStore(PrefStore* managed_prefs,
                  PrefStore* supervised_user_prefs,
                  PrefStore* extension_prefs,
+                 PrefStore* standalone_browser_prefs,
                  PrefStore* command_line_prefs,
                  PrefStore* user_prefs,
                  PrefStore* recommended_prefs,
@@ -125,6 +132,7 @@
       PrefStore* managed_prefs,
       PrefStore* supervised_user_prefs,
       PrefStore* extension_prefs,
+      PrefStore* standalone_browser_prefs,
       PrefStore* command_line_prefs,
       PrefStore* user_prefs,
       PrefStore* recommended_prefs,
diff --git a/components/prefs/pref_value_store_unittest.cc b/components/prefs/pref_value_store_unittest.cc
index 3964813..2871964 100644
--- a/components/prefs/pref_value_store_unittest.cc
+++ b/components/prefs/pref_value_store_unittest.cc
@@ -41,6 +41,7 @@
 const char kSupervisedUserPref[] = "this.pref.supervised_user";
 const char kCommandLinePref[] = "this.pref.command_line";
 const char kExtensionPref[] = "this.pref.extension";
+const char kStandaloneBrowserPref[] = "this.pref.standalone_browser";
 const char kUserPref[] = "this.pref.user";
 const char kRecommendedPref[] = "this.pref.recommended";
 const char kDefaultPref[] = "this.pref.default";
@@ -63,10 +64,18 @@
 const char kExtensionValue[] = "extension:extension";
 }
 
+namespace standalone_browser_pref {
+const char kManagedValue[] = "standalone_browser:managed";
+const char kSupervisedUserValue[] = "standalone_browser:supervised_user";
+const char kExtensionValue[] = "standalone_browser:extension";
+const char kStandaloneBrowserValue[] = "standalone_browser:standalone_browser";
+}  // namespace standalone_browser_pref
+
 namespace command_line_pref {
 const char kManagedValue[] = "command_line:managed";
 const char kSupervisedUserValue[] = "command_line:supervised_user";
 const char kExtensionValue[] = "command_line:extension";
+const char kStandaloneBrowserValue[] = "command_line:standalone_browser";
 const char kCommandLineValue[] = "command_line:command_line";
 }
 
@@ -74,6 +83,7 @@
 const char kManagedValue[] = "user:managed";
 const char kSupervisedUserValue[] = "supervised_user:supervised_user";
 const char kExtensionValue[] = "user:extension";
+const char kStandaloneBrowserValue[] = "user:standalone_browser";
 const char kCommandLineValue[] = "user:command_line";
 const char kUserValue[] = "user:user";
 }
@@ -82,6 +92,7 @@
 const char kManagedValue[] = "recommended:managed";
 const char kSupervisedUserValue[] = "recommended:supervised_user";
 const char kExtensionValue[] = "recommended:extension";
+const char kStandaloneBrowserValue[] = "recommended:standalone_browser";
 const char kCommandLineValue[] = "recommended:command_line";
 const char kUserValue[] = "recommended:user";
 const char kRecommendedValue[] = "recommended:recommended";
@@ -91,6 +102,7 @@
 const char kManagedValue[] = "default:managed";
 const char kSupervisedUserValue[] = "default:supervised_user";
 const char kExtensionValue[] = "default:extension";
+const char kStandaloneBrowserValue[] = "default:standalone_browser";
 const char kCommandLineValue[] = "default:command_line";
 const char kUserValue[] = "default:user";
 const char kRecommendedValue[] = "default:recommended";
@@ -104,6 +116,7 @@
     CreateManagedPrefs();
     CreateSupervisedUserPrefs();
     CreateExtensionPrefs();
+    CreateStandaloneBrowserPrefs();
     CreateCommandLinePrefs();
     CreateUserPrefs();
     CreateRecommendedPrefs();
@@ -113,9 +126,10 @@
     // Create a fresh PrefValueStore.
     pref_value_store_ = std::make_unique<PrefValueStore>(
         managed_pref_store_.get(), supervised_user_pref_store_.get(),
-        extension_pref_store_.get(), command_line_pref_store_.get(),
-        user_pref_store_.get(), recommended_pref_store_.get(),
-        default_pref_store_.get(), &pref_notifier_);
+        extension_pref_store_.get(), standalone_browser_pref_store_.get(),
+        command_line_pref_store_.get(), user_pref_store_.get(),
+        recommended_pref_store_.get(), default_pref_store_.get(),
+        &pref_notifier_);
 
     pref_value_store_->set_callback(
         base::BindRepeating(&MockPrefModelAssociator::ProcessPrefChange,
@@ -152,6 +166,20 @@
         extension_pref::kExtensionValue);
   }
 
+  void CreateStandaloneBrowserPrefs() {
+    standalone_browser_pref_store_ = new TestingPrefStore;
+    standalone_browser_pref_store_->SetString(
+        prefs::kManagedPref, standalone_browser_pref::kManagedValue);
+    standalone_browser_pref_store_->SetString(
+        prefs::kSupervisedUserPref,
+        standalone_browser_pref::kSupervisedUserValue);
+    standalone_browser_pref_store_->SetString(
+        prefs::kExtensionPref, standalone_browser_pref::kExtensionValue);
+    standalone_browser_pref_store_->SetString(
+        prefs::kStandaloneBrowserPref,
+        standalone_browser_pref::kStandaloneBrowserValue);
+  }
+
   void CreateCommandLinePrefs() {
     command_line_pref_store_ = new TestingPrefStore;
     command_line_pref_store_->SetString(
@@ -164,6 +192,9 @@
         prefs::kExtensionPref,
         command_line_pref::kExtensionValue);
     command_line_pref_store_->SetString(
+        prefs::kStandaloneBrowserPref,
+        command_line_pref::kStandaloneBrowserValue);
+    command_line_pref_store_->SetString(
         prefs::kCommandLinePref,
         command_line_pref::kCommandLineValue);
   }
@@ -182,6 +213,8 @@
     user_pref_store_->SetString(
         prefs::kExtensionPref,
         user_pref::kExtensionValue);
+    user_pref_store_->SetString(prefs::kStandaloneBrowserPref,
+                                user_pref::kStandaloneBrowserValue);
     user_pref_store_->SetString(
         prefs::kUserPref,
         user_pref::kUserValue);
@@ -202,6 +235,9 @@
         prefs::kExtensionPref,
         recommended_pref::kExtensionValue);
     recommended_pref_store_->SetString(
+        prefs::kStandaloneBrowserPref,
+        recommended_pref::kStandaloneBrowserValue);
+    recommended_pref_store_->SetString(
         prefs::kUserPref,
         recommended_pref::kUserValue);
     recommended_pref_store_->SetString(
@@ -223,6 +259,8 @@
     default_pref_store_->SetString(
         prefs::kExtensionPref,
         default_pref::kExtensionValue);
+    default_pref_store_->SetString(prefs::kStandaloneBrowserPref,
+                                   default_pref::kStandaloneBrowserValue);
     default_pref_store_->SetString(
         prefs::kUserPref,
         default_pref::kUserValue);
@@ -251,6 +289,7 @@
   scoped_refptr<TestingPrefStore> managed_pref_store_;
   scoped_refptr<TestingPrefStore> supervised_user_pref_store_;
   scoped_refptr<TestingPrefStore> extension_pref_store_;
+  scoped_refptr<TestingPrefStore> standalone_browser_pref_store_;
   scoped_refptr<TestingPrefStore> command_line_pref_store_;
   scoped_refptr<TestingPrefStore> user_pref_store_;
   scoped_refptr<TestingPrefStore> recommended_pref_store_;
@@ -480,6 +519,7 @@
   managed_pref_store_->SetInitializationCompleted();
   supervised_user_pref_store_->SetInitializationCompleted();
   extension_pref_store_->SetInitializationCompleted();
+  standalone_browser_pref_store_->SetInitializationCompleted();
   command_line_pref_store_->SetInitializationCompleted();
   recommended_pref_store_->SetInitializationCompleted();
   default_pref_store_->SetInitializationCompleted();
@@ -499,6 +539,8 @@
   EXPECT_FALSE(pref_value_store_->PrefValueInManagedStore(
       prefs::kExtensionPref));
   EXPECT_FALSE(pref_value_store_->PrefValueInManagedStore(
+      prefs::kStandaloneBrowserPref));
+  EXPECT_FALSE(pref_value_store_->PrefValueInManagedStore(
       prefs::kCommandLinePref));
   EXPECT_FALSE(pref_value_store_->PrefValueInManagedStore(
       prefs::kUserPref));
@@ -518,6 +560,8 @@
   EXPECT_TRUE(pref_value_store_->PrefValueInExtensionStore(
       prefs::kExtensionPref));
   EXPECT_FALSE(pref_value_store_->PrefValueInExtensionStore(
+      prefs::kStandaloneBrowserPref));
+  EXPECT_FALSE(pref_value_store_->PrefValueInExtensionStore(
       prefs::kCommandLinePref));
   EXPECT_FALSE(pref_value_store_->PrefValueInExtensionStore(
       prefs::kUserPref));
@@ -536,6 +580,8 @@
       prefs::kSupervisedUserPref));
   EXPECT_TRUE(pref_value_store_->PrefValueInUserStore(
       prefs::kExtensionPref));
+  EXPECT_TRUE(
+      pref_value_store_->PrefValueInUserStore(prefs::kStandaloneBrowserPref));
   EXPECT_TRUE(pref_value_store_->PrefValueInUserStore(
       prefs::kCommandLinePref));
   EXPECT_TRUE(pref_value_store_->PrefValueInUserStore(
@@ -556,6 +602,8 @@
   EXPECT_TRUE(pref_value_store_->PrefValueFromExtensionStore(
       prefs::kExtensionPref));
   EXPECT_FALSE(pref_value_store_->PrefValueFromExtensionStore(
+      prefs::kStandaloneBrowserPref));
+  EXPECT_FALSE(pref_value_store_->PrefValueFromExtensionStore(
       prefs::kCommandLinePref));
   EXPECT_FALSE(pref_value_store_->PrefValueFromExtensionStore(
       prefs::kUserPref));
@@ -574,6 +622,8 @@
       prefs::kSupervisedUserPref));
   EXPECT_FALSE(pref_value_store_->PrefValueFromUserStore(
       prefs::kExtensionPref));
+  EXPECT_FALSE(
+      pref_value_store_->PrefValueFromUserStore(prefs::kStandaloneBrowserPref));
   EXPECT_FALSE(pref_value_store_->PrefValueFromUserStore(
       prefs::kCommandLinePref));
   EXPECT_TRUE(pref_value_store_->PrefValueFromUserStore(
@@ -594,6 +644,8 @@
   EXPECT_FALSE(pref_value_store_->PrefValueFromRecommendedStore(
       prefs::kExtensionPref));
   EXPECT_FALSE(pref_value_store_->PrefValueFromRecommendedStore(
+      prefs::kStandaloneBrowserPref));
+  EXPECT_FALSE(pref_value_store_->PrefValueFromRecommendedStore(
       prefs::kCommandLinePref));
   EXPECT_FALSE(pref_value_store_->PrefValueFromRecommendedStore(
       prefs::kUserPref));
@@ -613,6 +665,8 @@
   EXPECT_FALSE(pref_value_store_->PrefValueFromDefaultStore(
       prefs::kExtensionPref));
   EXPECT_FALSE(pref_value_store_->PrefValueFromDefaultStore(
+      prefs::kStandaloneBrowserPref));
+  EXPECT_FALSE(pref_value_store_->PrefValueFromDefaultStore(
       prefs::kCommandLinePref));
   EXPECT_FALSE(pref_value_store_->PrefValueFromDefaultStore(
       prefs::kUserPref));
@@ -632,6 +686,8 @@
   EXPECT_FALSE(pref_value_store_->PrefValueUserModifiable(
       prefs::kExtensionPref));
   EXPECT_FALSE(pref_value_store_->PrefValueUserModifiable(
+      prefs::kStandaloneBrowserPref));
+  EXPECT_FALSE(pref_value_store_->PrefValueUserModifiable(
       prefs::kCommandLinePref));
   EXPECT_TRUE(pref_value_store_->PrefValueUserModifiable(
       prefs::kUserPref));
@@ -651,6 +707,8 @@
   EXPECT_TRUE(pref_value_store_->PrefValueExtensionModifiable(
       prefs::kExtensionPref));
   EXPECT_TRUE(pref_value_store_->PrefValueExtensionModifiable(
+      prefs::kStandaloneBrowserPref));
+  EXPECT_TRUE(pref_value_store_->PrefValueExtensionModifiable(
       prefs::kCommandLinePref));
   EXPECT_TRUE(pref_value_store_->PrefValueExtensionModifiable(
       prefs::kUserPref));
diff --git a/components/prefs/standalone_browser_pref_store.cc b/components/prefs/standalone_browser_pref_store.cc
new file mode 100644
index 0000000..08f31942
--- /dev/null
+++ b/components/prefs/standalone_browser_pref_store.cc
@@ -0,0 +1,7 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/prefs/standalone_browser_pref_store.h"
+
+StandaloneBrowserPrefStore::~StandaloneBrowserPrefStore() = default;
diff --git a/components/prefs/standalone_browser_pref_store.h b/components/prefs/standalone_browser_pref_store.h
new file mode 100644
index 0000000..dc1e8b7
--- /dev/null
+++ b/components/prefs/standalone_browser_pref_store.h
@@ -0,0 +1,27 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_PREFS_STANDALONE_BROWSER_PREF_STORE_H_
+#define COMPONENTS_PREFS_STANDALONE_BROWSER_PREF_STORE_H_
+
+#include "components/prefs/prefs_export.h"
+#include "components/prefs/value_map_pref_store.h"
+
+// A PrefStore implementation that holds preferences sent by another browser
+// instance. For example, in ash, this prefstore holds the value of
+// extension-controlled system prefs set in lacros (e.g. the screen magnifier)
+// TODO(crbug.com/1218145): Implement persistence.
+class COMPONENTS_PREFS_EXPORT StandaloneBrowserPrefStore
+    : public ValueMapPrefStore {
+ public:
+  StandaloneBrowserPrefStore() = default;
+  StandaloneBrowserPrefStore(const StandaloneBrowserPrefStore&) = delete;
+  StandaloneBrowserPrefStore& operator=(const StandaloneBrowserPrefStore&) =
+      delete;
+
+ protected:
+  ~StandaloneBrowserPrefStore() override;
+};
+
+#endif  // COMPONENTS_PREFS_STANDALONE_BROWSER_PREF_STORE_H_
diff --git a/components/prefs/testing_pref_service.cc b/components/prefs/testing_pref_service.cc
index fb06790..998a7370 100644
--- a/components/prefs/testing_pref_service.cc
+++ b/components/prefs/testing_pref_service.cc
@@ -19,6 +19,7 @@
     TestingPrefStore* managed_prefs,
     TestingPrefStore* supervised_user_prefs,
     TestingPrefStore* extension_prefs,
+    TestingPrefStore* standalone_browser_prefs,
     TestingPrefStore* user_prefs,
     TestingPrefStore* recommended_prefs,
     PrefRegistry* pref_registry,
@@ -28,6 +29,7 @@
           std::make_unique<PrefValueStore>(managed_prefs,
                                            supervised_user_prefs,
                                            extension_prefs,
+                                           standalone_browser_prefs,
                                            /*command_line_prefs=*/nullptr,
                                            user_prefs,
                                            recommended_prefs,
@@ -42,6 +44,7 @@
       managed_prefs_(managed_prefs),
       supervised_user_prefs_(supervised_user_prefs),
       extension_prefs_(extension_prefs),
+      standalone_browser_prefs_(standalone_browser_prefs),
       user_prefs_(user_prefs),
       recommended_prefs_(recommended_prefs) {}
 
@@ -50,6 +53,7 @@
           /*managed_prefs=*/new TestingPrefStore(),
           /*supervised_user_prefs=*/new TestingPrefStore(),
           /*extension_prefs=*/new TestingPrefStore(),
+          /*standalone_browser_prefs=*/new TestingPrefStore(),
           /*user_prefs=*/new TestingPrefStore(),
           /*recommended_prefs=*/new TestingPrefStore(),
           new PrefRegistrySimple(),
diff --git a/components/prefs/testing_pref_service.h b/components/prefs/testing_pref_service.h
index 7ec1954..cd05b48 100644
--- a/components/prefs/testing_pref_service.h
+++ b/components/prefs/testing_pref_service.h
@@ -79,6 +79,7 @@
   TestingPrefServiceBase(TestingPrefStore* managed_prefs,
                          TestingPrefStore* supervised_user_prefs,
                          TestingPrefStore* extension_prefs,
+                         TestingPrefStore* standalone_browser_prefs,
                          TestingPrefStore* user_prefs,
                          TestingPrefStore* recommended_prefs,
                          ConstructionPrefRegistry* pref_registry,
@@ -102,6 +103,7 @@
   scoped_refptr<TestingPrefStore> managed_prefs_;
   scoped_refptr<TestingPrefStore> supervised_user_prefs_;
   scoped_refptr<TestingPrefStore> extension_prefs_;
+  scoped_refptr<TestingPrefStore> standalone_browser_prefs_;
   scoped_refptr<TestingPrefStore> user_prefs_;
   scoped_refptr<TestingPrefStore> recommended_prefs_;
 };
@@ -130,6 +132,7 @@
     TestingPrefStore* managed_prefs,
     TestingPrefStore* supervised_user_prefs,
     TestingPrefStore* extension_prefs,
+    TestingPrefStore* standalone_browser_prefs,
     TestingPrefStore* user_prefs,
     TestingPrefStore* recommended_prefs,
     PrefRegistry* pref_registry,
@@ -269,6 +272,7 @@
   managed_prefs_->SetInitializationCompleted();
   supervised_user_prefs_->SetInitializationCompleted();
   extension_prefs_->SetInitializationCompleted();
+  standalone_browser_prefs_->SetInitializationCompleted();
   recommended_prefs_->SetInitializationCompleted();
   // |user_prefs_| is initialized in PrefService constructor so no need to
   // set initialization status again.
diff --git a/components/safe_browsing/core/browser/safe_browsing_metrics_collector.cc b/components/safe_browsing/core/browser/safe_browsing_metrics_collector.cc
index 73104b01..f4bd833a 100644
--- a/components/safe_browsing/core/browser/safe_browsing_metrics_collector.cc
+++ b/components/safe_browsing/core/browser/safe_browsing_metrics_collector.cc
@@ -534,8 +534,10 @@
                                   EventType::USER_STATE_ENABLED);
 
   if (!latest_enabled_event) {
-    // this code path could be possible if ESB was enabled via policy but
-    // later disabled by the user, since policy enables/disables are not tracked
+    // This code path could be possible if ESB was enabled via policy but
+    // later disabled by the user, since policy enables/disables are not
+    // tracked. It's also possible if it's been longer than kEventMaxDurationDay
+    // days since the latest enabled event.
     return "NeverEnabled";
   }
   const auto hours_since_enabled =
diff --git a/components/segmentation_platform/internal/android/java/src/org/chromium/components/segmentation_platform/SegmentationPlatformServiceImpl.java b/components/segmentation_platform/internal/android/java/src/org/chromium/components/segmentation_platform/SegmentationPlatformServiceImpl.java
index 76c27ba..38f34d5d 100644
--- a/components/segmentation_platform/internal/android/java/src/org/chromium/components/segmentation_platform/SegmentationPlatformServiceImpl.java
+++ b/components/segmentation_platform/internal/android/java/src/org/chromium/components/segmentation_platform/SegmentationPlatformServiceImpl.java
@@ -35,6 +35,12 @@
                 mNativePtr, this, segmentationKey, callback);
     }
 
+    @Override
+    public SegmentSelectionResult getCachedSegmentResult(String segmentationKey) {
+        return SegmentationPlatformServiceImplJni.get().getCachedSegmentResult(
+                mNativePtr, this, segmentationKey);
+    }
+
     @CalledByNative
     private void clearNativePtr() {
         mNativePtr = 0;
@@ -53,5 +59,7 @@
         void getSelectedSegment(long nativeSegmentationPlatformServiceAndroid,
                 SegmentationPlatformServiceImpl caller, String segmentationKey,
                 Callback<SegmentSelectionResult> callback);
+        SegmentSelectionResult getCachedSegmentResult(long nativeSegmentationPlatformServiceAndroid,
+                SegmentationPlatformServiceImpl caller, String segmentationKey);
     }
 }
diff --git a/components/segmentation_platform/internal/android/segmentation_platform_service_android.cc b/components/segmentation_platform/internal/android/segmentation_platform_service_android.cc
index 738d40fc..d580aa7 100644
--- a/components/segmentation_platform/internal/android/segmentation_platform_service_android.cc
+++ b/components/segmentation_platform/internal/android/segmentation_platform_service_android.cc
@@ -85,6 +85,16 @@
 }
 
 ScopedJavaLocalRef<jobject>
+SegmentationPlatformServiceAndroid::GetCachedSegmentResult(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& jcaller,
+    const JavaParamRef<jstring>& j_segmentation_key) {
+  return CreateJavaSegmentSelectionResult(
+      env, segmentation_platform_service_->GetCachedSegmentResult(
+               ConvertJavaStringToUTF8(env, j_segmentation_key)));
+}
+
+ScopedJavaLocalRef<jobject>
 SegmentationPlatformServiceAndroid::GetJavaObject() {
   return ScopedJavaLocalRef<jobject>(java_obj_);
 }
diff --git a/components/segmentation_platform/internal/android/segmentation_platform_service_android.h b/components/segmentation_platform/internal/android/segmentation_platform_service_android.h
index c9225b0..86ad117a 100644
--- a/components/segmentation_platform/internal/android/segmentation_platform_service_android.h
+++ b/components/segmentation_platform/internal/android/segmentation_platform_service_android.h
@@ -30,6 +30,11 @@
                           const JavaParamRef<jstring>& j_segmentation_key,
                           const JavaParamRef<jobject>& j_callback);
 
+  ScopedJavaLocalRef<jobject> GetCachedSegmentResult(
+      JNIEnv* env,
+      const JavaParamRef<jobject>& jcaller,
+      const JavaParamRef<jstring>& j_segmentation_key);
+
   ScopedJavaLocalRef<jobject> GetJavaObject();
 
  private:
diff --git a/components/segmentation_platform/internal/database/metadata_utils.cc b/components/segmentation_platform/internal/database/metadata_utils.cc
index 0ba1fe92..574f6807 100644
--- a/components/segmentation_platform/internal/database/metadata_utils.cc
+++ b/components/segmentation_platform/internal/database/metadata_utils.cc
@@ -4,8 +4,12 @@
 
 #include "components/segmentation_platform/internal/database/metadata_utils.h"
 
+#include <inttypes.h>
+
 #include "base/metrics/metrics_hashes.h"
 #include "base/notreached.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
 #include "base/time/time.h"
 #include "components/optimization_guide/proto/models.pb.h"
 #include "components/segmentation_platform/internal/database/signal_key.h"
@@ -40,6 +44,36 @@
       return 0;
   }
 }
+
+std::string FeatureToString(const proto::Feature& feature) {
+  std::string result;
+  if (feature.has_type()) {
+    result = "type:" + proto::SignalType_Name(feature.type()) + ", ";
+  }
+  if (feature.has_name()) {
+    result.append("name:" + feature.name() + ", ");
+  }
+  if (feature.has_name_hash()) {
+    result.append(
+        base::StringPrintf("name_hash:0x%" PRIx64 ", ", feature.name_hash()));
+  }
+  if (feature.has_bucket_count()) {
+    result.append(base::StringPrintf("bucket_count:%" PRIu64 ", ",
+                                     feature.bucket_count()));
+  }
+  if (feature.has_tensor_length()) {
+    result.append(base::StringPrintf("tensor_length:%" PRIu64 ", ",
+                                     feature.tensor_length()));
+  }
+  if (feature.has_aggregation()) {
+    result.append("aggregation:" +
+                  proto::Aggregation_Name(feature.aggregation()));
+  }
+  if (base::EndsWith(result, ", "))
+    result.resize(result.size() - 2);
+  return result;
+}
+
 }  // namespace
 
 ValidationResult ValidateSegmentInfo(const proto::SegmentInfo& segment_info) {
@@ -222,5 +256,38 @@
   return discrete_result;
 }
 
+std::string SegmetationModelMetadataToString(
+    const proto::SegmentationModelMetadata& model_metadata) {
+  std::string result;
+  for (const auto& feature : model_metadata.features()) {
+    result.append("feature:{" + FeatureToString(feature) + "}, ");
+  }
+  if (model_metadata.has_time_unit()) {
+    result.append(
+        "time_unit:" + proto::TimeUnit_Name(model_metadata.time_unit()) + ", ");
+  }
+  if (model_metadata.has_bucket_duration()) {
+    result.append(base::StringPrintf("bucket_duration:%" PRIu64 ", ",
+                                     model_metadata.bucket_duration()));
+  }
+  if (model_metadata.has_signal_storage_length()) {
+    result.append(base::StringPrintf("signal_storage_length:%" PRId64 ", ",
+                                     model_metadata.signal_storage_length()));
+  }
+  if (model_metadata.has_min_signal_collection_length()) {
+    result.append(
+        base::StringPrintf("min_signal_collection_length:%" PRId64 ", ",
+                           model_metadata.min_signal_collection_length()));
+  }
+  if (model_metadata.has_result_time_to_live()) {
+    result.append(base::StringPrintf("result_time_to_live:%" PRId64,
+                                     model_metadata.result_time_to_live()));
+  }
+
+  if (base::EndsWith(result, ", "))
+    result.resize(result.size() - 2);
+  return result;
+}
+
 }  // namespace metadata_utils
 }  // namespace segmentation_platform
diff --git a/components/segmentation_platform/internal/database/metadata_utils.h b/components/segmentation_platform/internal/database/metadata_utils.h
index fde9258..537c3c1 100644
--- a/components/segmentation_platform/internal/database/metadata_utils.h
+++ b/components/segmentation_platform/internal/database/metadata_utils.h
@@ -84,6 +84,9 @@
                            float input_score,
                            const proto::SegmentationModelMetadata& metadata);
 
+std::string SegmetationModelMetadataToString(
+    const proto::SegmentationModelMetadata& model_metadata);
+
 }  // namespace metadata_utils
 }  // namespace segmentation_platform
 
diff --git a/components/segmentation_platform/internal/database/metadata_utils_unittest.cc b/components/segmentation_platform/internal/database/metadata_utils_unittest.cc
index 2692ffd7..4780296 100644
--- a/components/segmentation_platform/internal/database/metadata_utils_unittest.cc
+++ b/components/segmentation_platform/internal/database/metadata_utils_unittest.cc
@@ -506,4 +506,28 @@
                                                       metadata));
 }
 
+TEST_F(MetadataUtilsTest, SegmetationModelMetadataToString) {
+  proto::SegmentationModelMetadata metadata;
+  ASSERT_TRUE(
+      metadata_utils::SegmetationModelMetadataToString(metadata).empty());
+
+  proto::Feature feature;
+  feature.set_type(proto::SignalType::UNKNOWN_SIGNAL_TYPE);
+  feature.set_name("test name");
+  feature.set_aggregation(proto::Aggregation::COUNT);
+  feature.set_bucket_count(456);
+  *metadata.add_features() = feature;
+
+  std::string expected =
+      "feature:{type:UNKNOWN_SIGNAL_TYPE, name:test name, bucket_count:456, "
+      "aggregation:COUNT}";
+  ASSERT_EQ(metadata_utils::SegmetationModelMetadataToString(metadata),
+            expected);
+
+  metadata.set_bucket_duration(10);
+  metadata.set_min_signal_collection_length(7);
+  ASSERT_EQ(metadata_utils::SegmetationModelMetadataToString(metadata),
+            expected + ", bucket_duration:10, min_signal_collection_length:7");
+}
+
 }  // namespace segmentation_platform
diff --git a/components/segmentation_platform/internal/dummy_segmentation_platform_service.cc b/components/segmentation_platform/internal/dummy_segmentation_platform_service.cc
index 3e7c2a5..7cbce72 100644
--- a/components/segmentation_platform/internal/dummy_segmentation_platform_service.cc
+++ b/components/segmentation_platform/internal/dummy_segmentation_platform_service.cc
@@ -22,6 +22,11 @@
       FROM_HERE, base::BindOnce(std::move(callback), SegmentSelectionResult()));
 }
 
+SegmentSelectionResult DummySegmentationPlatformService::GetCachedSegmentResult(
+    const std::string& segmentation_key) {
+  return SegmentSelectionResult();
+}
+
 void DummySegmentationPlatformService::EnableMetrics(
     bool signal_collection_allowed) {}
 }  // namespace segmentation_platform
diff --git a/components/segmentation_platform/internal/dummy_segmentation_platform_service.h b/components/segmentation_platform/internal/dummy_segmentation_platform_service.h
index 7102adc..67df2fd 100644
--- a/components/segmentation_platform/internal/dummy_segmentation_platform_service.h
+++ b/components/segmentation_platform/internal/dummy_segmentation_platform_service.h
@@ -27,6 +27,8 @@
   // SegmentationPlatformService overrides.
   void GetSelectedSegment(const std::string& segmentation_key,
                           SegmentSelectionCallback callback) override;
+  SegmentSelectionResult GetCachedSegmentResult(
+      const std::string& segmentation_key) override;
   void EnableMetrics(bool signal_collection_allowed) override;
 };
 
diff --git a/components/segmentation_platform/internal/dummy_segmentation_platform_service_unittest.cc b/components/segmentation_platform/internal/dummy_segmentation_platform_service_unittest.cc
index accf532..645967d 100644
--- a/components/segmentation_platform/internal/dummy_segmentation_platform_service_unittest.cc
+++ b/components/segmentation_platform/internal/dummy_segmentation_platform_service_unittest.cc
@@ -47,6 +47,8 @@
           &DummySegmentationPlatformServiceTest::OnGetSelectedSegment,
           base::Unretained(this), loop.QuitClosure(), expected));
   loop.Run();
+  ASSERT_EQ(expected,
+            segmentation_platform_service_->GetCachedSegmentResult("some_key"));
 }
 
 }  // namespace segmentation_platform
diff --git a/components/segmentation_platform/internal/segmentation_platform_service_impl.cc b/components/segmentation_platform/internal/segmentation_platform_service_impl.cc
index c76de96..1150f3e 100644
--- a/components/segmentation_platform/internal/segmentation_platform_service_impl.cc
+++ b/components/segmentation_platform/internal/segmentation_platform_service_impl.cc
@@ -103,8 +103,7 @@
       std::move(signal_storage_config_db), clock);
   segmentation_result_prefs_ =
       std::make_unique<SegmentationResultPrefs>(pref_service);
-  proxy_ =
-      std::make_unique<ServiceProxyImpl>(this, segment_info_database_.get());
+  proxy_ = std::make_unique<ServiceProxyImpl>(segment_info_database_.get());
 
   // Construct signal processors.
   user_action_signal_handler_ =
@@ -158,6 +157,13 @@
   selector->GetSelectedSegment(std::move(callback));
 }
 
+SegmentSelectionResult SegmentationPlatformServiceImpl::GetCachedSegmentResult(
+    const std::string& segmentation_key) {
+  CHECK(segment_selectors_.find(segmentation_key) != segment_selectors_.end());
+  auto& selector = segment_selectors_.at(segmentation_key);
+  return selector->GetCachedSegmentResult();
+}
+
 void SegmentationPlatformServiceImpl::EnableMetrics(
     bool signal_collection_allowed) {
   signal_filter_processor_->EnableMetrics(signal_collection_allowed);
diff --git a/components/segmentation_platform/internal/segmentation_platform_service_impl.h b/components/segmentation_platform/internal/segmentation_platform_service_impl.h
index fca4bd55..85bb2e0 100644
--- a/components/segmentation_platform/internal/segmentation_platform_service_impl.h
+++ b/components/segmentation_platform/internal/segmentation_platform_service_impl.h
@@ -112,6 +112,8 @@
   // SegmentationPlatformService overrides.
   void GetSelectedSegment(const std::string& segmentation_key,
                           SegmentSelectionCallback callback) override;
+  SegmentSelectionResult GetCachedSegmentResult(
+      const std::string& segmentation_key) override;
   void EnableMetrics(bool signal_collection_allowed) override;
   ServiceProxy* GetServiceProxy() override;
 
diff --git a/components/segmentation_platform/internal/segmentation_platform_service_impl_unittest.cc b/components/segmentation_platform/internal/segmentation_platform_service_impl_unittest.cc
index 6e0f7f98..24243366 100644
--- a/components/segmentation_platform/internal/segmentation_platform_service_impl_unittest.cc
+++ b/components/segmentation_platform/internal/segmentation_platform_service_impl_unittest.cc
@@ -175,6 +175,20 @@
     loop.Run();
   }
 
+  void AssertCachedSegment(
+      const std::string& segmentation_key,
+      bool is_ready,
+      OptimizationTarget expected =
+          OptimizationTarget::OPTIMIZATION_TARGET_UNKNOWN) {
+    SegmentSelectionResult result;
+    result.is_ready = is_ready;
+    if (is_ready)
+      result.segment = expected;
+    ASSERT_EQ(result,
+              segmentation_platform_service_impl_->GetCachedSegmentResult(
+                  segmentation_key));
+  }
+
  protected:
   base::test::TaskEnvironment task_environment_{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
@@ -253,6 +267,11 @@
       OptimizationTarget::OPTIMIZATION_TARGET_SEGMENTATION_SHARE);
   AssertSelectedSegment(kTestSegmentationKey2, false);
   AssertSelectedSegment(kTestSegmentationKey3, false);
+  AssertCachedSegment(
+      kTestSegmentationKey1, true,
+      OptimizationTarget::OPTIMIZATION_TARGET_SEGMENTATION_SHARE);
+  AssertCachedSegment(kTestSegmentationKey2, false);
+  AssertCachedSegment(kTestSegmentationKey3, false);
 
   mem_impl->OnSegmentationModelUpdated(
       OptimizationTarget::OPTIMIZATION_TARGET_SEGMENTATION_VOICE, metadata);
@@ -278,6 +297,11 @@
       OptimizationTarget::OPTIMIZATION_TARGET_SEGMENTATION_SHARE);
   AssertSelectedSegment(kTestSegmentationKey2, false);
   AssertSelectedSegment(kTestSegmentationKey3, false);
+  AssertCachedSegment(
+      kTestSegmentationKey1, true,
+      OptimizationTarget::OPTIMIZATION_TARGET_SEGMENTATION_SHARE);
+  AssertCachedSegment(kTestSegmentationKey2, false);
+  AssertCachedSegment(kTestSegmentationKey3, false);
 }
 
 TEST_F(SegmentationPlatformServiceImplTest,
@@ -352,6 +376,13 @@
       kTestSegmentationKey2, true,
       OptimizationTarget::OPTIMIZATION_TARGET_SEGMENTATION_VOICE);
   AssertSelectedSegment(kTestSegmentationKey3, false);
+  AssertCachedSegment(
+      kTestSegmentationKey1, true,
+      OptimizationTarget::OPTIMIZATION_TARGET_SEGMENTATION_SHARE);
+  AssertCachedSegment(
+      kTestSegmentationKey2, true,
+      OptimizationTarget::OPTIMIZATION_TARGET_SEGMENTATION_VOICE);
+  AssertCachedSegment(kTestSegmentationKey3, false);
 }
 
 }  // namespace segmentation_platform
diff --git a/components/segmentation_platform/internal/selection/segment_selector.h b/components/segmentation_platform/internal/selection/segment_selector.h
index e86615cb..af20d2a 100644
--- a/components/segmentation_platform/internal/selection/segment_selector.h
+++ b/components/segmentation_platform/internal/selection/segment_selector.h
@@ -28,9 +28,13 @@
   using SegmentSelectionCallback =
       base::OnceCallback<void(const SegmentSelectionResult&)>;
 
-  // Client API. Returns the selected segment from the last session. If none,
-  // returns empty result.
+  // Client API. Returns the selected segment from the last session
+  // asynchronously. If none, returns empty result.
   virtual void GetSelectedSegment(SegmentSelectionCallback callback) = 0;
+
+  // Client API. Returns the cached selected segment from the last session
+  // synchronously.
+  virtual SegmentSelectionResult GetCachedSegmentResult() = 0;
 };
 
 }  // namespace segmentation_platform
diff --git a/components/segmentation_platform/internal/selection/segment_selector_impl.cc b/components/segmentation_platform/internal/selection/segment_selector_impl.cc
index d444276..73a6f60 100644
--- a/components/segmentation_platform/internal/selection/segment_selector_impl.cc
+++ b/components/segmentation_platform/internal/selection/segment_selector_impl.cc
@@ -54,6 +54,10 @@
       base::BindOnce(std::move(callback), selected_segment_last_session_));
 }
 
+SegmentSelectionResult SegmentSelectorImpl::GetCachedSegmentResult() {
+  return selected_segment_last_session_;
+}
+
 void SegmentSelectorImpl::OnModelExecutionCompleted(
     OptimizationTarget segment_id) {
   // If the |segment_id| is not in config, then skip any updates early.
diff --git a/components/segmentation_platform/internal/selection/segment_selector_impl.h b/components/segmentation_platform/internal/selection/segment_selector_impl.h
index d48bd83..70f665df 100644
--- a/components/segmentation_platform/internal/selection/segment_selector_impl.h
+++ b/components/segmentation_platform/internal/selection/segment_selector_impl.h
@@ -41,6 +41,7 @@
 
   // SegmentSelector overrides.
   void GetSelectedSegment(SegmentSelectionCallback callback) override;
+  SegmentSelectionResult GetCachedSegmentResult() override;
 
   // ModelExecutionScheduler::Observer overrides.
 
diff --git a/components/segmentation_platform/internal/selection/segment_selector_unittest.cc b/components/segmentation_platform/internal/selection/segment_selector_unittest.cc
index 63bf8d353..11ba9bf5 100644
--- a/components/segmentation_platform/internal/selection/segment_selector_unittest.cc
+++ b/components/segmentation_platform/internal/selection/segment_selector_unittest.cc
@@ -301,6 +301,7 @@
   result.segment = segment_id0;
   result.is_ready = true;
   GetSelectedSegment(result);
+  ASSERT_EQ(result, segment_selector_->GetCachedSegmentResult());
 
   // Add results for a new segment.
   base::Time result_timestamp = base::Time::Now();
@@ -312,6 +313,7 @@
 
   // GetSelectedSegment should still return value from previous session.
   GetSelectedSegment(result);
+  ASSERT_EQ(result, segment_selector_->GetCachedSegmentResult());
 }
 
 }  // namespace segmentation_platform
diff --git a/components/segmentation_platform/internal/service_proxy_impl.cc b/components/segmentation_platform/internal/service_proxy_impl.cc
index c399021..5e67333 100644
--- a/components/segmentation_platform/internal/service_proxy_impl.cc
+++ b/components/segmentation_platform/internal/service_proxy_impl.cc
@@ -8,6 +8,7 @@
 
 #include "base/strings/stringprintf.h"
 #include "components/optimization_guide/core/optimization_guide_util.h"
+#include "components/segmentation_platform/internal/database/metadata_utils.h"
 #include "components/segmentation_platform/internal/segmentation_platform_service_impl.h"
 
 namespace segmentation_platform {
@@ -22,6 +23,12 @@
                  segment_info.segment_id()) +
              "\n";
   }
+  if (segment_info.has_model_metadata()) {
+    result.append("model_metadata: { " +
+                  metadata_utils::SegmetationModelMetadataToString(
+                      segment_info.model_metadata()) +
+                  " }\n");
+  }
   if (segment_info.has_prediction_result()) {
     const auto prediction_result = segment_info.prediction_result();
     std::string prediction_result_str = base::StringPrintf(
@@ -34,11 +41,9 @@
   return result;
 }
 
-ServiceProxyImpl::ServiceProxyImpl(SegmentationPlatformService* service,
-                                   SegmentInfoDatabase* segment_db)
+ServiceProxyImpl::ServiceProxyImpl(SegmentInfoDatabase* segment_db)
     : is_service_initialized_(false),
       service_status_flag_(0),
-      service_(service),
       segment_db_(segment_db) {}
 
 ServiceProxyImpl::~ServiceProxyImpl() = default;
@@ -71,19 +76,15 @@
   OnServiceStatusChanged(is_service_initialized_, service_status_flag_);
 }
 
-void ServiceProxyImpl::GetSelectedSegment(
-    const std::string& segmentation_key,
-    SegmentationPlatformService::SegmentSelectionCallback callback) {
-  service_->GetSelectedSegment(segmentation_key, std::move(callback));
-}
-
 //  Called after retrieving all the segmentation info from the DB.
 void ServiceProxyImpl::OnGetAllSegmentationInfo(
     std::vector<std::pair<OptimizationTarget, proto::SegmentInfo>>
-        segment_infos) {
-  std::vector<std::string> result;
-  for (const auto& segment_info : segment_infos) {
-    result.emplace_back(SegmentInfoToString(segment_info.second));
+        segment_info) {
+  std::vector<std::pair<std::string, std::string>> result;
+  for (const auto& info : segment_info) {
+    result.emplace_back(std::make_pair(
+        optimization_guide::GetStringNameForOptimizationTarget(info.first),
+        SegmentInfoToString(info.second)));
   }
 
   for (Observer& obs : observers_)
diff --git a/components/segmentation_platform/internal/service_proxy_impl.h b/components/segmentation_platform/internal/service_proxy_impl.h
index d9d83bf..067a99e 100644
--- a/components/segmentation_platform/internal/service_proxy_impl.h
+++ b/components/segmentation_platform/internal/service_proxy_impl.h
@@ -21,8 +21,7 @@
 // component and/or debug UI.
 class ServiceProxyImpl : public ServiceProxy {
  public:
-  ServiceProxyImpl(SegmentationPlatformService* service,
-                   SegmentInfoDatabase* segment_db);
+  explicit ServiceProxyImpl(SegmentInfoDatabase* segment_db);
   ~ServiceProxyImpl() override;
 
   // Helper method to convert |segment_info| to string.
@@ -38,9 +37,6 @@
 
   // Returns the current status of the segmentation service.
   void GetServiceStatus() override;
-  void GetSelectedSegment(
-      const std::string& segmentation_key,
-      SegmentationPlatformService::SegmentSelectionCallback callback) override;
 
   // Called when segmentation service status changed.
   void OnServiceStatusChanged(bool is_initialized, int status_flag);
@@ -49,11 +45,10 @@
   //  Called after retrieving all the segmentation info from the DB.
   void OnGetAllSegmentationInfo(
       std::vector<std::pair<OptimizationTarget, proto::SegmentInfo>>
-          segment_infos);
+          segment_info);
 
   bool is_service_initialized_;
   int service_status_flag_;
-  SegmentationPlatformService* service_;
   SegmentInfoDatabase* segment_db_;
   base::ObserverList<ServiceProxy::Observer> observers_;
 
diff --git a/components/segmentation_platform/internal/service_proxy_impl_unittest.cc b/components/segmentation_platform/internal/service_proxy_impl_unittest.cc
index 02ca55a..8c91940 100644
--- a/components/segmentation_platform/internal/service_proxy_impl_unittest.cc
+++ b/components/segmentation_platform/internal/service_proxy_impl_unittest.cc
@@ -7,6 +7,7 @@
 #include "base/strings/string_number_conversions.h"
 #include "components/leveldb_proto/public/proto_database.h"
 #include "components/leveldb_proto/testing/fake_db.h"
+#include "components/optimization_guide/core/optimization_guide_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace segmentation_platform {
@@ -39,8 +40,7 @@
     db_ = db.get();
     segment_db_ = std::make_unique<SegmentInfoDatabase>(std::move(db));
 
-    service_proxy_impl_ =
-        std::make_unique<ServiceProxyImpl>(nullptr, segment_db_.get());
+    service_proxy_impl_ = std::make_unique<ServiceProxyImpl>(segment_db_.get());
     service_proxy_impl_->AddObserver(this);
   }
 
@@ -56,7 +56,8 @@
   }
 
   void OnSegmentInfoAvailable(
-      const std::vector<std::string>& segment_info) override {
+      const std::vector<std::pair<std::string, std::string>>& segment_info)
+      override {
     segment_info_ = segment_info;
   }
 
@@ -68,7 +69,7 @@
   raw_ptr<leveldb_proto::test::FakeDB<proto::SegmentInfo>> db_{nullptr};
   std::unique_ptr<SegmentInfoDatabase> segment_db_;
   std::unique_ptr<ServiceProxyImpl> service_proxy_impl_;
-  std::vector<std::string> segment_info_;
+  std::vector<std::pair<std::string, std::string>> segment_info_;
 };
 
 TEST_F(ServiceProxyImplTest, GetServiceStatus) {
@@ -98,7 +99,11 @@
   service_proxy_impl_->OnServiceStatusChanged(true, 7);
   db_->LoadCallback(true);
   ASSERT_EQ(segment_info_.size(), 1u);
-  ASSERT_EQ(segment_info_.at(0), ServiceProxyImpl::SegmentInfoToString(info));
+  ASSERT_EQ(segment_info_.at(0).first,
+            optimization_guide::GetStringNameForOptimizationTarget(
+                OptimizationTarget::OPTIMIZATION_TARGET_SEGMENTATION_NEW_TAB));
+  ASSERT_EQ(segment_info_.at(0).second,
+            ServiceProxyImpl::SegmentInfoToString(info));
 }
 
 }  // namespace segmentation_platform
diff --git a/components/segmentation_platform/public/android/java/src/org/chromium/components/segmentation_platform/SegmentationPlatformService.java b/components/segmentation_platform/public/android/java/src/org/chromium/components/segmentation_platform/SegmentationPlatformService.java
index e3378e9..c300ec6 100644
--- a/components/segmentation_platform/public/android/java/src/org/chromium/components/segmentation_platform/SegmentationPlatformService.java
+++ b/components/segmentation_platform/public/android/java/src/org/chromium/components/segmentation_platform/SegmentationPlatformService.java
@@ -12,10 +12,18 @@
  */
 public interface SegmentationPlatformService {
     /**
-     * Called to get the segment selection result from the backend.
+     * Called to get the segment selection result asynchronously from the backend.
      * @param segmentationKey The key to be used to distinguish between different segmentation
-     *         usages. Currently unused.
+     *         usages.
      * @param callback The callback that contains the result of segmentation.
      */
     void getSelectedSegment(String segmentationKey, Callback<SegmentSelectionResult> callback);
+
+    /**
+     * Called to get the segment selection result synchronously from the backend.
+     * @param segmentationKey The key to be used to distinguish between different segmentation
+     *         usages.
+     * @return The result of segment selection
+     */
+    SegmentSelectionResult getCachedSegmentResult(String segmentationKey);
 }
\ No newline at end of file
diff --git a/components/segmentation_platform/public/segmentation_platform_service.h b/components/segmentation_platform/public/segmentation_platform_service.h
index cb74dbc4..7b93a8d 100644
--- a/components/segmentation_platform/public/segmentation_platform_service.h
+++ b/components/segmentation_platform/public/segmentation_platform_service.h
@@ -50,10 +50,16 @@
   using SegmentSelectionCallback =
       base::OnceCallback<void(const SegmentSelectionResult&)>;
 
-  // Called to get the selected segment. If none, returns empty result.
+  // Called to get the selected segment asynchronously. If none, returns empty
+  // result.
   virtual void GetSelectedSegment(const std::string& segmentation_key,
                                   SegmentSelectionCallback callback) = 0;
 
+  // Called to get the selected segment synchronously. If none, returns empty
+  // result.
+  virtual SegmentSelectionResult GetCachedSegmentResult(
+      const std::string& segmentation_key) = 0;
+
   // Called to enable or disable metrics collection. Must be explicitly called
   // on startup.
   virtual void EnableMetrics(bool signal_collection_allowed) = 0;
diff --git a/components/segmentation_platform/public/service_proxy.h b/components/segmentation_platform/public/service_proxy.h
index 8b1f914..6e9711d1 100644
--- a/components/segmentation_platform/public/service_proxy.h
+++ b/components/segmentation_platform/public/service_proxy.h
@@ -5,10 +5,10 @@
 #ifndef COMPONENTS_SEGMENTATION_PLATFORM_PUBLIC_SERVICE_PROXY_H_
 #define COMPONENTS_SEGMENTATION_PLATFORM_PUBLIC_SERVICE_PROXY_H_
 
+#include <utility>
 #include <vector>
 
 #include "base/observer_list_types.h"
-#include "components/segmentation_platform/public/segmentation_platform_service.h"
 
 namespace segmentation_platform {
 
@@ -21,7 +21,7 @@
     // Called whenever the servoice status changes.
     virtual void OnServiceStatusChanged(bool is_initialized, int status_flag) {}
     virtual void OnSegmentInfoAvailable(
-        const std::vector<std::string>& segment_info) {}
+        const std::vector<std::pair<std::string, std::string>>& segment_info) {}
   };
 
   virtual ~ServiceProxy() = default;
@@ -35,11 +35,6 @@
   // Returns the current status of the segmentation service.
   virtual void GetServiceStatus() = 0;
 
-  // Called to get the selected segment. If none, returns empty result.
-  virtual void GetSelectedSegment(
-      const std::string& segmentation_key,
-      SegmentationPlatformService::SegmentSelectionCallback callback) = 0;
-
  protected:
   ServiceProxy() = default;
 };
diff --git a/components/sync_preferences/pref_service_syncable.cc b/components/sync_preferences/pref_service_syncable.cc
index 3db63fd..84e4126 100644
--- a/components/sync_preferences/pref_service_syncable.cc
+++ b/components/sync_preferences/pref_service_syncable.cc
@@ -114,6 +114,7 @@
       nullptr,  // managed
       nullptr,  // supervised_user
       incognito_extension_pref_store,
+      nullptr,  // standalone_browser_prefs
       nullptr,  // command_line_prefs
       incognito_pref_store.get(),
       nullptr,  // recommended
diff --git a/components/sync_preferences/pref_service_syncable_factory.cc b/components/sync_preferences/pref_service_syncable_factory.cc
index 9f7b4f01..e6c6054 100644
--- a/components/sync_preferences/pref_service_syncable_factory.cc
+++ b/components/sync_preferences/pref_service_syncable_factory.cc
@@ -51,9 +51,10 @@
   auto pref_notifier = std::make_unique<PrefNotifierImpl>();
   auto pref_value_store = std::make_unique<PrefValueStore>(
       managed_prefs_.get(), supervised_user_prefs_.get(),
-      extension_prefs_.get(), command_line_prefs_.get(), user_prefs_.get(),
-      recommended_prefs_.get(), pref_registry->defaults().get(),
-      pref_notifier.get(), /*delegate=*/nullptr);
+      extension_prefs_.get(), standalone_browser_prefs_.get(),
+      command_line_prefs_.get(), user_prefs_.get(), recommended_prefs_.get(),
+      pref_registry->defaults().get(), pref_notifier.get(),
+      /*delegate=*/nullptr);
   return std::make_unique<PrefServiceSyncable>(
       std::move(pref_notifier), std::move(pref_value_store), user_prefs_.get(),
       std::move(pref_registry), pref_model_associator_client_,
diff --git a/components/sync_preferences/pref_service_syncable_unittest.cc b/components/sync_preferences/pref_service_syncable_unittest.cc
index a9658bf..64fac4c 100644
--- a/components/sync_preferences/pref_service_syncable_unittest.cc
+++ b/components/sync_preferences/pref_service_syncable_unittest.cc
@@ -403,6 +403,7 @@
                                              new TestingPrefStore,
                                              new TestingPrefStore,
                                              new TestingPrefStore,
+                                             new TestingPrefStore,
                                              user_prefs_.get(),
                                              new TestingPrefStore,
                                              pref_registry_->defaults().get(),
@@ -933,8 +934,9 @@
         std::unique_ptr<PrefNotifierImpl>(pref_notifier_),
         std::make_unique<PrefValueStore>(
             new TestingPrefStore, new TestingPrefStore, new TestingPrefStore,
-            new TestingPrefStore, user_prefs_.get(), new TestingPrefStore,
-            pref_registry_->defaults().get(), pref_notifier_),
+            new TestingPrefStore, new TestingPrefStore, user_prefs_.get(),
+            new TestingPrefStore, pref_registry_->defaults().get(),
+            pref_notifier_),
         user_prefs_, pref_registry_, &client_,
         /*read_error_callback=*/base::DoNothing(),
         /*async=*/false);
diff --git a/components/sync_preferences/testing_pref_service_syncable.cc b/components/sync_preferences/testing_pref_service_syncable.cc
index e2fd19c..454bbf8e 100644
--- a/components/sync_preferences/testing_pref_service_syncable.cc
+++ b/components/sync_preferences/testing_pref_service_syncable.cc
@@ -18,6 +18,7 @@
     TestingPrefServiceBase(TestingPrefStore* managed_prefs,
                            TestingPrefStore* supervised_user_prefs,
                            TestingPrefStore* extension_prefs,
+                           TestingPrefStore* standalone_browser_prefs,
                            TestingPrefStore* user_prefs,
                            TestingPrefStore* recommended_prefs,
                            user_prefs::PrefRegistrySyncable* pref_registry,
@@ -27,6 +28,7 @@
           std::make_unique<PrefValueStore>(managed_prefs,
                                            supervised_user_prefs,
                                            extension_prefs,
+                                           standalone_browser_prefs,
                                            /*command_line_prefs=*/nullptr,
                                            user_prefs,
                                            recommended_prefs,
@@ -42,6 +44,7 @@
           false),
       managed_prefs_(managed_prefs),
       extension_prefs_(extension_prefs),
+      standalone_browser_prefs_(standalone_browser_prefs),
       user_prefs_(user_prefs),
       recommended_prefs_(recommended_prefs) {}
 
@@ -53,6 +56,7 @@
           /*managed_prefs=*/new TestingPrefStore(),
           /*supervised_user_prefs=*/new TestingPrefStore(),
           /*extension_prefs=*/new TestingPrefStore(),
+          /*standalone_browser_prefs=*/new TestingPrefStore(),
           /*user_prefs=*/new TestingPrefStore(),
           /*recommended_prefs=*/new TestingPrefStore(),
           new user_prefs::PrefRegistrySyncable(),
@@ -62,6 +66,7 @@
     TestingPrefStore* managed_prefs,
     TestingPrefStore* supervised_user_prefs,
     TestingPrefStore* extension_prefs,
+    TestingPrefStore* standalone_browser_prefs,
     TestingPrefStore* user_prefs,
     TestingPrefStore* recommended_prefs,
     user_prefs::PrefRegistrySyncable* pref_registry,
@@ -71,6 +76,7 @@
           managed_prefs,
           supervised_user_prefs,
           extension_prefs,
+          standalone_browser_prefs,
           user_prefs,
           recommended_prefs,
           pref_registry,
diff --git a/components/sync_preferences/testing_pref_service_syncable.h b/components/sync_preferences/testing_pref_service_syncable.h
index e7564e61d5..cd95a28 100644
--- a/components/sync_preferences/testing_pref_service_syncable.h
+++ b/components/sync_preferences/testing_pref_service_syncable.h
@@ -35,6 +35,7 @@
   TestingPrefServiceSyncable(TestingPrefStore* managed_prefs,
                              TestingPrefStore* supervised_user_prefs,
                              TestingPrefStore* extension_prefs,
+                             TestingPrefStore* standalone_browser_prefs,
                              TestingPrefStore* user_prefs,
                              TestingPrefStore* recommended_prefs,
                              user_prefs::PrefRegistrySyncable* pref_registry,
@@ -60,6 +61,7 @@
     TestingPrefServiceBase(TestingPrefStore* managed_prefs,
                            TestingPrefStore* supervised_user_prefs,
                            TestingPrefStore* extension_prefs,
+                           TestingPrefStore* standalone_browser_prefs,
                            TestingPrefStore* user_prefs,
                            TestingPrefStore* recommended_prefs,
                            user_prefs::PrefRegistrySyncable* pref_registry,
diff --git a/components/test/data/payments/no_shipping.js b/components/test/data/payments/no_shipping.js
index 19c77135..c804f83 100644
--- a/components/test/data/payments/no_shipping.js
+++ b/components/test/data/payments/no_shipping.js
@@ -10,12 +10,22 @@
  * Launches the PaymentRequest UI that does not require a shipping address.
  */
 function buy() { // eslint-disable-line no-unused-vars
+  buyWithMethods([
+    {
+      supportedMethods: 'basic-card',
+      data: {supportedNetworks: ['visa', 'mastercard']},
+    },
+  ]);
+}
+
+/**
+ * Launches the PaymentRequest UI that does not require a shipping address.
+ * @param {String} methodData - An array of payment method objects.
+ */
+function buyWithMethods(methodData) {
   try {
     new PaymentRequest(
-        [{
-          supportedMethods: 'basic-card',
-          data: {supportedNetworks: ['visa', 'mastercard']},
-        }],
+      methodData,
         {
           total: {label: 'Total', amount: {currency: 'USD', value: '5.00'}},
           displayItems: [
diff --git a/components/viz/common/gpu/vulkan_in_process_context_provider.cc b/components/viz/common/gpu/vulkan_in_process_context_provider.cc
index c2b5ef56..38838e9 100644
--- a/components/viz/common/gpu/vulkan_in_process_context_provider.cc
+++ b/components/viz/common/gpu/vulkan_in_process_context_provider.cc
@@ -132,9 +132,24 @@
   GrVkGetProc get_proc = [](const char* proc_name, VkInstance instance,
                             VkDevice device) {
     if (device) {
-      if (std::strcmp("vkCreateGraphicsPipelines", proc_name) == 0)
+      // Using vkQueue*Hook for all vkQueue* methods here to make both chrome
+      // side access and skia side access to the same queue thread safe.
+      // vkQueue*Hook routes all skia side access to the same
+      // VulkanFunctionPointers vkQueue* api which chrome uses and is under the
+      // lock.
+      if (std::strcmp("vkCreateGraphicsPipelines", proc_name) == 0) {
         return reinterpret_cast<PFN_vkVoidFunction>(
             &gpu::CreateGraphicsPipelinesHook);
+      } else if (std::strcmp("vkQueueSubmit", proc_name) == 0) {
+        return reinterpret_cast<PFN_vkVoidFunction>(
+            &gpu::VulkanQueueSubmitHook);
+      } else if (std::strcmp("vkQueueWaitIdle", proc_name) == 0) {
+        return reinterpret_cast<PFN_vkVoidFunction>(
+            &gpu::VulkanQueueWaitIdleHook);
+      } else if (std::strcmp("vkQueuePresentKHR", proc_name) == 0) {
+        return reinterpret_cast<PFN_vkVoidFunction>(
+            &gpu::VulkanQueuePresentKHRHook);
+      }
       return vkGetDeviceProcAddr(device, proc_name);
     }
     return vkGetInstanceProcAddr(instance, proc_name);
diff --git a/components/webxr/OWNERS b/components/webxr/OWNERS
index 2310121..d37fd2d1 100644
--- a/components/webxr/OWNERS
+++ b/components/webxr/OWNERS
@@ -1,2 +1,7 @@
 alcooper@chromium.org
+bajones@chromium.org
 bialpio@chromium.org
+klausw@chromium.org
+
+# WebXR Test related
+bsheedy@chromium.org
diff --git a/components/webxr/android/arcore_device_provider.cc b/components/webxr/android/arcore_device_provider.cc
index 6bb16b5..714e8c0 100644
--- a/components/webxr/android/arcore_device_provider.cc
+++ b/components/webxr/android/arcore_device_provider.cc
@@ -19,16 +19,7 @@
 
 ArCoreDeviceProvider::~ArCoreDeviceProvider() = default;
 
-void ArCoreDeviceProvider::Initialize(
-    base::RepeatingCallback<void(device::mojom::XRDeviceId,
-                                 device::mojom::VRDisplayInfoPtr,
-                                 device::mojom::XRDeviceDataPtr,
-                                 mojo::PendingRemote<device::mojom::XRRuntime>)>
-        add_device_callback,
-    base::RepeatingCallback<void(device::mojom::XRDeviceId)>
-        remove_device_callback,
-    base::OnceClosure initialization_complete,
-    device::XrFrameSinkClientFactory xr_frame_sink_client_factory) {
+void ArCoreDeviceProvider::Initialize(device::VRDeviceProviderClient* client) {
   if (device::IsArCoreSupported()) {
     DVLOG(2) << __func__ << ": ARCore is supported, creating device";
 
@@ -37,14 +28,14 @@
         std::make_unique<device::ArImageTransportFactory>(),
         std::make_unique<webxr::MailboxToSurfaceBridgeFactoryImpl>(),
         std::make_unique<webxr::ArCoreJavaUtils>(compositor_delegate_provider_),
-        std::move(xr_frame_sink_client_factory));
+        client->GetXrFrameSinkClientFactory());
 
-    add_device_callback.Run(
+    client->AddRuntime(
         arcore_device_->GetId(), arcore_device_->GetVRDisplayInfo(),
         arcore_device_->GetDeviceData(), arcore_device_->BindXRRuntime());
   }
   initialized_ = true;
-  std::move(initialization_complete).Run();
+  client->OnProviderInitialized();
 }
 
 bool ArCoreDeviceProvider::Initialized() {
diff --git a/components/webxr/android/arcore_device_provider.h b/components/webxr/android/arcore_device_provider.h
index 15b43d1..74af836b 100644
--- a/components/webxr/android/arcore_device_provider.h
+++ b/components/webxr/android/arcore_device_provider.h
@@ -28,16 +28,7 @@
   ArCoreDeviceProvider& operator=(const ArCoreDeviceProvider&) = delete;
 
   ~ArCoreDeviceProvider() override;
-  void Initialize(
-      base::RepeatingCallback<void(
-          device::mojom::XRDeviceId,
-          device::mojom::VRDisplayInfoPtr,
-          device::mojom::XRDeviceDataPtr,
-          mojo::PendingRemote<device::mojom::XRRuntime>)> add_device_callback,
-      base::RepeatingCallback<void(device::mojom::XRDeviceId)>
-          remove_device_callback,
-      base::OnceClosure initialization_complete,
-      device::XrFrameSinkClientFactory xr_frame_sink_client_factory) override;
+  void Initialize(device::VRDeviceProviderClient* client) override;
   bool Initialized() override;
 
  private:
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 2dc83679..626b8f5 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -414,8 +414,7 @@
     "attribution_reporting/attribution_utils.h",
     "attribution_reporting/rate_limit_table.cc",
     "attribution_reporting/rate_limit_table.h",
-    "attribution_reporting/sent_report.cc",
-    "attribution_reporting/sent_report.h",
+    "attribution_reporting/send_result.h",
     "attribution_reporting/sql_utils.cc",
     "attribution_reporting/sql_utils.h",
     "attribution_reporting/storable_source.cc",
diff --git a/content/browser/attribution_reporting/attribution_internals_browsertest.cc b/content/browser/attribution_reporting/attribution_internals_browsertest.cc
index 26147bf..1cc378c 100644
--- a/content/browser/attribution_reporting/attribution_internals_browsertest.cc
+++ b/content/browser/attribution_reporting/attribution_internals_browsertest.cc
@@ -17,6 +17,7 @@
 #include "content/browser/attribution_reporting/attribution_report.h"
 #include "content/browser/attribution_reporting/attribution_storage.h"
 #include "content/browser/attribution_reporting/attribution_test_utils.h"
+#include "content/browser/attribution_reporting/send_result.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_ui.h"
 #include "content/public/browser/web_ui_controller.h"
@@ -337,22 +338,22 @@
 
   OverrideWebUIAttributionManager();
 
-  manager_.NotifyReportSent(SentReport(ReportBuilder(SourceBuilder(now).Build())
-                                           .SetReportTime(now + base::Hours(3))
-                                           .Build(),
-                                       SentReport::Status::kSent,
+  manager_.NotifyReportSent(ReportBuilder(SourceBuilder(now).Build())
+                                .SetReportTime(now + base::Hours(3))
+                                .Build(),
+                            SendResult(SendResult::Status::kSent,
                                        /*http_response_code=*/200));
-  manager_.NotifyReportSent(SentReport(ReportBuilder(SourceBuilder(now).Build())
-                                           .SetReportTime(now + base::Hours(4))
-                                           .SetPriority(-1)
-                                           .Build(),
-                                       SentReport::Status::kDropped,
+  manager_.NotifyReportSent(ReportBuilder(SourceBuilder(now).Build())
+                                .SetReportTime(now + base::Hours(4))
+                                .SetPriority(-1)
+                                .Build(),
+                            SendResult(SendResult::Status::kDropped,
                                        /*http_response_code=*/0));
-  manager_.NotifyReportSent(SentReport(ReportBuilder(SourceBuilder(now).Build())
-                                           .SetReportTime(now + base::Hours(5))
-                                           .SetPriority(-2)
-                                           .Build(),
-                                       SentReport::Status::kFailure,
+  manager_.NotifyReportSent(ReportBuilder(SourceBuilder(now).Build())
+                                .SetReportTime(now + base::Hours(5))
+                                .SetPriority(-2)
+                                .Build(),
+                            SendResult(SendResult::Status::kFailure,
                                        /*http_response_code=*/0));
   ON_CALL(manager_, GetPendingReportsForWebUI)
       .WillByDefault(InvokeCallback<std::vector<AttributionReport>>(
@@ -492,8 +493,8 @@
       .WillOnce(InvokeCallback<std::vector<AttributionReport>>({report}));
 
   report.set_report_time(report.report_time() + base::Hours(1));
-  manager_.NotifyReportSent(SentReport(report, SentReport::Status::kSent,
-                                       /*http_response_code=*/200));
+  manager_.NotifyReportSent(report, SendResult(SendResult::Status::kSent,
+                                               /*http_response_code=*/200));
 
   EXPECT_CALL(manager_, ClearData)
       .WillOnce([](base::Time delete_begin, base::Time delete_end,
diff --git a/content/browser/attribution_reporting/attribution_internals_handler_impl.cc b/content/browser/attribution_reporting/attribution_internals_handler_impl.cc
index f8edee2..34c1bea 100644
--- a/content/browser/attribution_reporting/attribution_internals_handler_impl.cc
+++ b/content/browser/attribution_reporting/attribution_internals_handler_impl.cc
@@ -15,7 +15,7 @@
 #include "content/browser/attribution_reporting/attribution_manager_impl.h"
 #include "content/browser/attribution_reporting/attribution_report.h"
 #include "content/browser/attribution_reporting/attribution_storage.h"
-#include "content/browser/attribution_reporting/sent_report.h"
+#include "content/browser/attribution_reporting/send_result.h"
 #include "content/browser/attribution_reporting/storable_source.h"
 #include "content/browser/storage_partition_impl.h"
 #include "content/public/browser/browser_context.h"
@@ -212,31 +212,33 @@
   }
 }
 
-void AttributionInternalsHandlerImpl::OnReportSent(const SentReport& info) {
+void AttributionInternalsHandlerImpl::OnReportSent(
+    const AttributionReport& report,
+    const SendResult& info) {
   mojom::WebUIAttributionReport::Status status;
   switch (info.status) {
-    case SentReport::Status::kSent:
+    case SendResult::Status::kSent:
       status = mojom::WebUIAttributionReport::Status::kSent;
       break;
-    case SentReport::Status::kDropped:
+    case SendResult::Status::kDropped:
       status =
           mojom::WebUIAttributionReport::Status::kProhibitedByBrowserPolicy;
       break;
-    case SentReport::Status::kFailure:
+    case SendResult::Status::kFailure:
       status = mojom::WebUIAttributionReport::Status::kNetworkError;
       break;
-    case SentReport::Status::kTransientFailure:
-    case SentReport::Status::kOffline:
-    case SentReport::Status::kRemovedFromQueue:
+    case SendResult::Status::kTransientFailure:
+    case SendResult::Status::kOffline:
+    case SendResult::Status::kRemovedFromQueue:
       NOTREACHED();
       return;
   }
 
-  auto report =
-      WebUIAttributionReport(info.report, info.http_response_code, status);
+  auto web_report =
+      WebUIAttributionReport(report, info.http_response_code, status);
 
   for (auto& observer : observers_) {
-    observer->OnReportSent(report.Clone());
+    observer->OnReportSent(web_report.Clone());
   }
 }
 
diff --git a/content/browser/attribution_reporting/attribution_internals_handler_impl.h b/content/browser/attribution_reporting/attribution_internals_handler_impl.h
index 7198ed978..7d0fc9d 100644
--- a/content/browser/attribution_reporting/attribution_internals_handler_impl.h
+++ b/content/browser/attribution_reporting/attribution_internals_handler_impl.h
@@ -68,7 +68,8 @@
   void OnReportsChanged() override;
   void OnSourceDeactivated(
       const AttributionStorage::DeactivatedSource& deactivated_source) override;
-  void OnReportSent(const SentReport& info) override;
+  void OnReportSent(const AttributionReport& report,
+                    const SendResult& info) override;
   void OnReportDropped(
       const AttributionStorage::CreateReportResult& result) override;
 
diff --git a/content/browser/attribution_reporting/attribution_manager.h b/content/browser/attribution_reporting/attribution_manager.h
index 196b764..570e54d 100644
--- a/content/browser/attribution_reporting/attribution_manager.h
+++ b/content/browser/attribution_reporting/attribution_manager.h
@@ -12,7 +12,6 @@
 #include "base/observer_list_types.h"
 #include "content/browser/attribution_reporting/attribution_report.h"
 #include "content/browser/attribution_reporting/attribution_storage.h"
-#include "content/browser/attribution_reporting/sent_report.h"
 
 namespace base {
 class Time;
@@ -29,6 +28,8 @@
 class StorableSource;
 class WebContents;
 
+struct SendResult;
+
 // Interface that mediates data flow between the network, storage layer, and
 // blink.
 class AttributionManager {
@@ -58,7 +59,8 @@
     virtual void OnSourceDeactivated(
         const AttributionStorage::DeactivatedSource& source) {}
 
-    virtual void OnReportSent(const SentReport& info) {}
+    virtual void OnReportSent(const AttributionReport& report,
+                              const SendResult& info) {}
 
     virtual void OnReportDropped(
         const AttributionStorage::CreateReportResult& result) {}
diff --git a/content/browser/attribution_reporting/attribution_manager_impl.cc b/content/browser/attribution_reporting/attribution_manager_impl.cc
index 745cb28..f3fd8d3 100644
--- a/content/browser/attribution_reporting/attribution_manager_impl.cc
+++ b/content/browser/attribution_reporting/attribution_manager_impl.cc
@@ -21,6 +21,7 @@
 #include "content/browser/attribution_reporting/attribution_reporter_impl.h"
 #include "content/browser/attribution_reporting/attribution_storage_delegate_impl.h"
 #include "content/browser/attribution_reporting/attribution_storage_sql.h"
+#include "content/browser/attribution_reporting/send_result.h"
 #include "content/browser/attribution_reporting/storable_source.h"
 #include "content/browser/attribution_reporting/storable_trigger.h"
 #include "content/browser/storage_partition_impl.h"
@@ -84,20 +85,20 @@
 }
 
 ConversionReportSendOutcome ConvertToConversionReportSendOutcome(
-    SentReport::Status status) {
+    SendResult::Status status) {
   switch (status) {
-    case SentReport::Status::kSent:
+    case SendResult::Status::kSent:
       return ConversionReportSendOutcome::kSent;
-    case SentReport::Status::kTransientFailure:
-    case SentReport::Status::kFailure:
+    case SendResult::Status::kTransientFailure:
+    case SendResult::Status::kFailure:
       return ConversionReportSendOutcome::kFailed;
-    case SentReport::Status::kOffline:
-    case SentReport::Status::kRemovedFromQueue:
+    case SendResult::Status::kOffline:
+    case SendResult::Status::kRemovedFromQueue:
       // Offline reports and reports removed from the queue before being sent
       // should never record an outcome.
       NOTREACHED();
       return ConversionReportSendOutcome::kFailed;
-    case SentReport::Status::kDropped:
+    case SendResult::Status::kDropped:
       return ConversionReportSendOutcome::kDropped;
   }
 }
@@ -425,23 +426,23 @@
   reporter_->AddReportsToQueue(std::move(reports));
 }
 
-void AttributionManagerImpl::OnReportSent(SentReport info) {
-  DCHECK(info.report.report_id().has_value());
+void AttributionManagerImpl::OnReportSent(AttributionReport report,
+                                          SendResult info) {
+  DCHECK(report.report_id().has_value());
 
   // If there was a transient failure, and another attempt is allowed,
   // update the report's DB state to reflect that. Otherwise, delete the report
   // from storage if it wasn't skipped due to the browser being offline.
 
   bool should_retry = false;
-  if (info.status == SentReport::Status::kTransientFailure) {
-    info.report.set_failed_send_attempts(info.report.failed_send_attempts() +
-                                         1);
+  if (info.status == SendResult::Status::kTransientFailure) {
+    report.set_failed_send_attempts(report.failed_send_attempts() + 1);
     const absl::optional<base::TimeDelta> delay =
         attribution_policy_->GetFailedReportDelay(
-            info.report.failed_send_attempts());
+            report.failed_send_attempts());
     if (delay.has_value()) {
       should_retry = true;
-      info.report.set_report_time(info.report.report_time() + *delay);
+      report.set_report_time(report.report_time() + *delay);
     }
   }
 
@@ -452,7 +453,7 @@
     // occur.
     attribution_storage_
         .AsyncCall(&AttributionStorage::UpdateReportForSendFailure)
-        .WithArgs(*info.report.report_id(), info.report.report_time())
+        .WithArgs(*report.report_id(), report.report_time())
         .Then(base::BindOnce(
             [](base::WeakPtr<AttributionManagerImpl> manager,
                AttributionReport report, bool success) {
@@ -465,17 +466,17 @@
 
               manager->NotifyReportsChanged();
             },
-            weak_factory_.GetWeakPtr(), info.report));
-  } else if (info.status == SentReport::Status::kOffline ||
-             info.status == SentReport::Status::kRemovedFromQueue) {
+            weak_factory_.GetWeakPtr(), report));
+  } else if (info.status == SendResult::Status::kOffline ||
+             info.status == SendResult::Status::kRemovedFromQueue) {
     // Remove the ID from the set so that subsequent attempts will not be
     // deduplicated.
-    size_t num_removed = queued_reports_.erase(*info.report.report_id());
+    size_t num_removed = queued_reports_.erase(*report.report_id());
     DCHECK_EQ(num_removed, 1u);
   } else {
     RecordDeleteEvent(DeleteEvent::kStarted);
     attribution_storage_.AsyncCall(&AttributionStorage::DeleteReport)
-        .WithArgs(*info.report.report_id())
+        .WithArgs(*report.report_id())
         .Then(base::BindOnce(
             [](base::WeakPtr<AttributionManagerImpl> manager,
                AttributionReport::Id report_id, bool succeeded) {
@@ -491,7 +492,7 @@
                 manager->NotifyReportsChanged();
               }
             },
-            weak_factory_.GetWeakPtr(), *info.report.report_id()));
+            weak_factory_.GetWeakPtr(), *report.report_id()));
 
     base::UmaHistogramEnumeration(
         "Conversion.ReportSendOutcome",
@@ -505,21 +506,20 @@
   // ID, remove the ID from the wait-set; if it was the last such ID,
   // run the callback.
   if (!send_reports_for_web_ui_callback_.is_null() &&
-      pending_report_ids_for_internals_ui_.erase(*info.report.report_id()) >
-          0 &&
+      pending_report_ids_for_internals_ui_.erase(*report.report_id()) > 0 &&
       pending_report_ids_for_internals_ui_.empty()) {
     std::move(send_reports_for_web_ui_callback_).Run();
   }
 
   // TODO(apaseltiner): Consider surfacing retry attempts in internals UI.
-  if (info.status != SentReport::Status::kSent &&
-      info.status != SentReport::Status::kFailure &&
-      info.status != SentReport::Status::kDropped) {
+  if (info.status != SendResult::Status::kSent &&
+      info.status != SendResult::Status::kFailure &&
+      info.status != SendResult::Status::kDropped) {
     return;
   }
 
   for (Observer& observer : observers_)
-    observer.OnReportSent(info);
+    observer.OnReportSent(report, info);
 }
 
 void AttributionManagerImpl::NotifySourcesChanged() {
diff --git a/content/browser/attribution_reporting/attribution_manager_impl.h b/content/browser/attribution_reporting/attribution_manager_impl.h
index 0cdcf98..8661c494 100644
--- a/content/browser/attribution_reporting/attribution_manager_impl.h
+++ b/content/browser/attribution_reporting/attribution_manager_impl.h
@@ -20,7 +20,6 @@
 #include "content/browser/attribution_reporting/attribution_manager.h"
 #include "content/browser/attribution_reporting/attribution_report.h"
 #include "content/browser/attribution_reporting/attribution_storage.h"
-#include "content/browser/attribution_reporting/sent_report.h"
 #include "content/common/content_export.h"
 #include "storage/browser/quota/special_storage_policy.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -38,6 +37,8 @@
 
 class StoragePartitionImpl;
 
+struct SendResult;
+
 // Provides access to the manager owned by the default StoragePartition.
 class AttributionManagerProviderImpl : public AttributionManager::Provider {
  public:
@@ -153,7 +154,7 @@
   void OnGetReportsToSendFromWebUI(base::OnceClosure done,
                                    std::vector<AttributionReport> reports);
 
-  void OnReportSent(SentReport info);
+  void OnReportSent(AttributionReport report, SendResult info);
 
   void OnReportStored(AttributionStorage::CreateReportResult result);
 
diff --git a/content/browser/attribution_reporting/attribution_manager_impl_unittest.cc b/content/browser/attribution_reporting/attribution_manager_impl_unittest.cc
index aa68480..be71091 100644
--- a/content/browser/attribution_reporting/attribution_manager_impl_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_manager_impl_unittest.cc
@@ -28,7 +28,7 @@
 #include "content/browser/attribution_reporting/attribution_report.h"
 #include "content/browser/attribution_reporting/attribution_storage.h"
 #include "content/browser/attribution_reporting/attribution_test_utils.h"
-#include "content/browser/attribution_reporting/sent_report.h"
+#include "content/browser/attribution_reporting/send_result.h"
 #include "content/browser/attribution_reporting/storable_source.h"
 #include "content/browser/attribution_reporting/storable_trigger.h"
 #include "content/public/test/browser_task_environment.h"
@@ -79,7 +79,10 @@
               (const DeactivatedSource& source),
               (override));
 
-  MOCK_METHOD(void, OnReportSent, (const SentReport& info), (override));
+  MOCK_METHOD(void,
+              OnReportSent,
+              (const AttributionReport& report, const SendResult& info),
+              (override));
 
   MOCK_METHOD(void,
               OnReportDropped,
@@ -91,6 +94,11 @@
 class TestAttributionReporter
     : public AttributionManagerImpl::AttributionReporter {
  public:
+  struct CallbackData {
+    AttributionReport report;
+    SendResult info;
+  };
+
   TestAttributionReporter() = default;
   ~TestAttributionReporter() override = default;
 
@@ -101,13 +109,14 @@
 
     for (auto& report : reports) {
       added_reports_.push_back(report);
-      SentReport info(std::move(report), sent_report_status_,
+      SendResult info(send_result_status_,
                       /*http_response_code=*/0);
 
       if (should_run_report_sent_callbacks_) {
-        report_sent_callback_.Run(std::move(info));
+        report_sent_callback_.Run(std::move(report), std::move(info));
       } else {
-        deferred_callbacks_.push_back(std::move(info));
+        deferred_callbacks_.push_back(
+            {.report = std::move(report), .info = std::move(info)});
       }
     }
 
@@ -117,14 +126,15 @@
 
   void RunDeferredCallbacks() {
     for (auto& deferred_callback : deferred_callbacks_) {
-      report_sent_callback_.Run(std::move(deferred_callback));
+      report_sent_callback_.Run(std::move(deferred_callback.report),
+                                std::move(deferred_callback.info));
     }
     deferred_callbacks_.clear();
   }
 
   void RemoveAllReportsFromQueue() override {
     for (auto& deferred_callback : deferred_callbacks_) {
-      deferred_callback.status = SentReport::Status::kRemovedFromQueue;
+      deferred_callback.info.status = SendResult::Status::kRemovedFromQueue;
     }
     RunDeferredCallbacks();
   }
@@ -133,8 +143,8 @@
     should_run_report_sent_callbacks_ = should_run_report_sent_callbacks;
   }
 
-  void SetSentReportStatus(SentReport::Status status) {
-    sent_report_status_ = status;
+  void SetSendResultStatus(SendResult::Status status) {
+    send_result_status_ = status;
   }
 
   const std::vector<AttributionReport>& added_reports() const {
@@ -152,18 +162,20 @@
   }
 
   void SetReportSentCallback(
-      base::RepeatingCallback<void(SentReport)> report_sent_callback) {
+      base::RepeatingCallback<void(AttributionReport, SendResult)>
+          report_sent_callback) {
     report_sent_callback_ = std::move(report_sent_callback);
   }
 
  private:
-  base::RepeatingCallback<void(SentReport)> report_sent_callback_;
+  base::RepeatingCallback<void(AttributionReport, SendResult)>
+      report_sent_callback_;
   bool should_run_report_sent_callbacks_ = false;
-  SentReport::Status sent_report_status_ = SentReport::Status::kSent;
+  SendResult::Status send_result_status_ = SendResult::Status::kSent;
   size_t expected_num_reports_ = 0u;
   std::vector<AttributionReport> added_reports_;
   base::OnceClosure quit_closure_;
-  std::vector<SentReport> deferred_callbacks_;
+  std::vector<CallbackData> deferred_callbacks_;
 };
 
 // Time after impression that a conversion can first be sent. See
@@ -310,7 +322,7 @@
        QueuedReportFailedWithShouldRetry_QueuedAgain) {
   base::HistogramTester histograms;
   test_reporter_->ShouldRunReportSentCallbacks(true);
-  test_reporter_->SetSentReportStatus(SentReport::Status::kTransientFailure);
+  test_reporter_->SetSendResultStatus(SendResult::Status::kTransientFailure);
 
   attribution_manager_->HandleSource(
       SourceBuilder(clock().Now()).SetExpiry(kImpressionExpiry).Build());
@@ -330,7 +342,7 @@
        QueuedReportFailedWithoutShouldRetry_NotQueuedAgain) {
   base::HistogramTester histograms;
   test_reporter_->ShouldRunReportSentCallbacks(true);
-  test_reporter_->SetSentReportStatus(SentReport::Status::kFailure);
+  test_reporter_->SetSendResultStatus(SendResult::Status::kFailure);
 
   attribution_manager_->HandleSource(
       SourceBuilder(clock().Now()).SetExpiry(kImpressionExpiry).Build());
@@ -361,7 +373,7 @@
 TEST_F(AttributionManagerImplTest, QueuedReportAlwaysFails_StopsSending) {
   base::HistogramTester histograms;
   test_reporter_->ShouldRunReportSentCallbacks(false);
-  test_reporter_->SetSentReportStatus(SentReport::Status::kTransientFailure);
+  test_reporter_->SetSendResultStatus(SendResult::Status::kTransientFailure);
 
   attribution_manager_->HandleSource(
       SourceBuilder(clock().Now()).SetExpiry(kImpressionExpiry).Build());
@@ -426,7 +438,7 @@
 TEST_F(AttributionManagerImplTest, QueuedReportOffline_NoFailureIncrement) {
   base::HistogramTester histograms;
   test_reporter_->ShouldRunReportSentCallbacks(true);
-  test_reporter_->SetSentReportStatus(SentReport::Status::kTransientFailure);
+  test_reporter_->SetSendResultStatus(SendResult::Status::kTransientFailure);
 
   attribution_manager_->HandleSource(
       SourceBuilder(clock().Now()).SetExpiry(kImpressionExpiry).Build());
@@ -438,7 +450,7 @@
   // into the queue 2 times.
   EXPECT_THAT(test_reporter_->added_reports(), SizeIs(3));
 
-  test_reporter_->SetSentReportStatus(SentReport::Status::kOffline);
+  test_reporter_->SetSendResultStatus(SendResult::Status::kOffline);
   task_environment_.FastForwardBy(base::Minutes(30));
   EXPECT_THAT(test_reporter_->added_reports(), SizeIs(3));
 
@@ -495,23 +507,23 @@
       observation(&observer);
   observation.Observe(attribution_manager_.get());
 
-  EXPECT_CALL(observer,
-              OnReportSent(Field(
-                  &SentReport::report,
-                  Property(&AttributionReport::impression,
-                           Property(&StorableSource::source_event_id, 1u)))));
-  EXPECT_CALL(observer,
-              OnReportSent(Field(
-                  &SentReport::report,
-                  Property(&AttributionReport::impression,
-                           Property(&StorableSource::source_event_id, 2u)))));
-  EXPECT_CALL(observer,
-              OnReportSent(Field(
-                  &SentReport::report,
-                  Property(&AttributionReport::impression,
-                           Property(&StorableSource::source_event_id, 3u)))));
+  EXPECT_CALL(
+      observer,
+      OnReportSent(Property(&AttributionReport::impression,
+                            Property(&StorableSource::source_event_id, 1u)),
+                   _));
+  EXPECT_CALL(
+      observer,
+      OnReportSent(Property(&AttributionReport::impression,
+                            Property(&StorableSource::source_event_id, 2u)),
+                   _));
+  EXPECT_CALL(
+      observer,
+      OnReportSent(Property(&AttributionReport::impression,
+                            Property(&StorableSource::source_event_id, 3u)),
+                   _));
 
-  test_reporter_->SetSentReportStatus(SentReport::Status::kSent);
+  test_reporter_->SetSendResultStatus(SendResult::Status::kSent);
   attribution_manager_->HandleSource(SourceBuilder(clock().Now())
                                          .SetSourceEventId(1)
                                          .SetExpiry(kImpressionExpiry)
@@ -521,7 +533,7 @@
                                   kAttributionManagerQueueReportsInterval);
 
   // This one should be stored, as its status is `kDropped`.
-  test_reporter_->SetSentReportStatus(SentReport::Status::kDropped);
+  test_reporter_->SetSendResultStatus(SendResult::Status::kDropped);
   attribution_manager_->HandleSource(SourceBuilder(clock().Now())
                                          .SetSourceEventId(2)
                                          .SetExpiry(kImpressionExpiry)
@@ -530,7 +542,7 @@
   task_environment_.FastForwardBy(kFirstReportingWindow -
                                   kAttributionManagerQueueReportsInterval);
 
-  test_reporter_->SetSentReportStatus(SentReport::Status::kSent);
+  test_reporter_->SetSendResultStatus(SendResult::Status::kSent);
   attribution_manager_->HandleSource(SourceBuilder(clock().Now())
                                          .SetSourceEventId(3)
                                          .SetExpiry(kImpressionExpiry)
@@ -540,7 +552,7 @@
                                   kAttributionManagerQueueReportsInterval);
 
   // This one shouldn't be stored, as it will be retried.
-  test_reporter_->SetSentReportStatus(SentReport::Status::kTransientFailure);
+  test_reporter_->SetSendResultStatus(SendResult::Status::kTransientFailure);
   attribution_manager_->HandleSource(SourceBuilder(clock().Now())
                                          .SetSourceEventId(4)
                                          .SetExpiry(kImpressionExpiry)
diff --git a/content/browser/attribution_reporting/attribution_network_sender_impl.cc b/content/browser/attribution_reporting/attribution_network_sender_impl.cc
index d0caf9d..d8ed1983 100644
--- a/content/browser/attribution_reporting/attribution_network_sender_impl.cc
+++ b/content/browser/attribution_reporting/attribution_network_sender_impl.cc
@@ -10,11 +10,7 @@
 #include "base/bind.h"
 #include "base/check.h"
 #include "base/metrics/histogram_functions.h"
-#include "base/metrics/histogram_macros.h"
-#include "base/time/time.h"
-#include "content/browser/attribution_reporting/attribution_report.h"
-#include "content/browser/attribution_reporting/attribution_utils.h"
-#include "content/browser/attribution_reporting/sent_report.h"
+#include "content/browser/attribution_reporting/send_result.h"
 #include "content/public/browser/storage_partition.h"
 #include "net/base/isolation_info.h"
 #include "net/base/load_flags.h"
@@ -43,29 +39,6 @@
   kMaxValue = kExternalError
 };
 
-// Called when a network request is started for |report|, for logging metrics.
-void LogMetricsOnReportSend(const AttributionReport& report) {
-  // Reports sent from the WebUI should not log metrics.
-  if (report.report_time() == base::Time::Min())
-    return;
-
-  // Use a large time range to capture users that might not open the browser for
-  // a long time while a conversion report is pending. Revisit this range if it
-  // is non-ideal for real world data.
-  base::Time now = base::Time::Now();
-  base::Time original_report_time =
-      ComputeReportTime(report.impression(), report.conversion_time());
-  base::TimeDelta time_since_original_report_time = now - original_report_time;
-  base::UmaHistogramCustomTimes(
-      "Conversions.ExtraReportDelay2", time_since_original_report_time,
-      base::Seconds(1), base::Days(24), /*buckets=*/100);
-
-  base::TimeDelta time_from_conversion_to_report_send =
-      report.report_time() - report.conversion_time();
-  UMA_HISTOGRAM_COUNTS_1000("Conversions.TimeFromConversionToReportSend",
-                            time_from_conversion_to_report_send.InHours());
-}
-
 }  // namespace
 
 AttributionNetworkSenderImpl::AttributionNetworkSenderImpl(
@@ -75,7 +48,8 @@
 AttributionNetworkSenderImpl::~AttributionNetworkSenderImpl() = default;
 
 void AttributionNetworkSenderImpl::SendReport(
-    AttributionReport report,
+    GURL report_url,
+    std::string report_body,
     ReportSentCallback sent_callback) {
   // The browser process URLLoaderFactory is not created by default, so don't
   // create it until it is directly needed.
@@ -85,7 +59,7 @@
   }
 
   auto resource_request = std::make_unique<network::ResourceRequest>();
-  resource_request->url = report.ReportURL();
+  resource_request->url = std::move(report_url);
   resource_request->method = net::HttpRequestHeaders::kPostMethod;
   resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
   resource_request->load_flags =
@@ -129,7 +103,6 @@
                                         std::move(simple_url_loader));
   simple_url_loader_ptr->SetTimeoutDuration(base::Seconds(30));
 
-  std::string report_body = report.ReportBody();
   simple_url_loader_ptr->AttachStringForUpload(report_body, "application/json");
 
   // Retry once on network change. A network change during DNS resolution
@@ -141,14 +114,12 @@
                    network::SimpleURLLoader::RETRY_ON_NAME_NOT_RESOLVED;
   simple_url_loader_ptr->SetRetryOptions(/*max_retries=*/1, retry_mode);
 
-  LogMetricsOnReportSend(report);
-
   // Unretained is safe because the URLLoader is owned by |this| and will be
   // deleted before |this|.
   simple_url_loader_ptr->DownloadHeadersOnly(
       url_loader_factory_.get(),
       base::BindOnce(&AttributionNetworkSenderImpl::OnReportSent,
-                     base::Unretained(this), std::move(it), std::move(report),
+                     base::Unretained(this), std::move(it),
                      std::move(sent_callback)));
 }
 
@@ -159,7 +130,6 @@
 
 void AttributionNetworkSenderImpl::OnReportSent(
     UrlLoaderList::iterator it,
-    AttributionReport report,
     ReportSentCallback sent_callback,
     scoped_refptr<net::HttpResponseHeaders> headers) {
   network::SimpleURLLoader* loader = it->get();
@@ -201,15 +171,14 @@
                    net_error == net::ERR_CONNECTION_ABORTED ||
                    net_error == net::ERR_CONNECTION_RESET);
 
-  SentReport::Status report_status =
+  SendResult::Status report_status =
       (status == Status::kOk)
-          ? SentReport::Status::kSent
-          : (should_retry ? SentReport::Status::kTransientFailure
-                          : SentReport::Status::kFailure);
+          ? SendResult::Status::kSent
+          : (should_retry ? SendResult::Status::kTransientFailure
+                          : SendResult::Status::kFailure);
 
   std::move(sent_callback)
-      .Run(SentReport(std::move(report), report_status,
-                      headers ? headers->response_code() : 0));
+      .Run(SendResult(report_status, headers ? headers->response_code() : 0));
 }
 
 }  // namespace content
diff --git a/content/browser/attribution_reporting/attribution_network_sender_impl.h b/content/browser/attribution_reporting/attribution_network_sender_impl.h
index 5dfa485..958398f 100644
--- a/content/browser/attribution_reporting/attribution_network_sender_impl.h
+++ b/content/browser/attribution_reporting/attribution_network_sender_impl.h
@@ -26,8 +26,6 @@
 
 class StoragePartition;
 
-class AttributionReport;
-
 // Implemented a NetworkSender capable of issuing POST requests for complete
 // conversions. Maintains a set of all ongoing UrlLoaders used for posting
 // conversion reports. Created and owned by AttributionReporterImpl.
@@ -48,7 +46,8 @@
   // seconds.
   // |sent_callback| is run after the request finishes, whether or not it
   // succeeded,
-  void SendReport(AttributionReport report,
+  void SendReport(GURL report_url,
+                  std::string report_body,
                   ReportSentCallback sent_callback) override;
 
   // Tests inject a TestURLLoaderFactory so they can mock the network response.
@@ -61,7 +60,6 @@
 
   // Called when headers are available for a sent report.
   void OnReportSent(UrlLoaderList::iterator it,
-                    AttributionReport report,
                     ReportSentCallback sent_callback,
                     scoped_refptr<net::HttpResponseHeaders> headers);
 
diff --git a/content/browser/attribution_reporting/attribution_network_sender_impl_unittest.cc b/content/browser/attribution_reporting/attribution_network_sender_impl_unittest.cc
index 565f333a..de6a54d 100644
--- a/content/browser/attribution_reporting/attribution_network_sender_impl_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_network_sender_impl_unittest.cc
@@ -13,7 +13,7 @@
 #include "base/test/mock_callback.h"
 #include "base/time/time.h"
 #include "content/browser/attribution_reporting/attribution_test_utils.h"
-#include "content/browser/attribution_reporting/sent_report.h"
+#include "content/browser/attribution_reporting/send_result.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/test/browser_task_environment.h"
 #include "content/public/test/test_browser_context.h"
@@ -67,7 +67,7 @@
   // |task_environment_| must be initialized first.
   content::BrowserTaskEnvironment task_environment_;
 
-  base::MockCallback<base::OnceCallback<void(SentReport)>> callback_;
+  base::MockCallback<base::OnceCallback<void(SendResult)>> callback_;
 
   // Unique ptr so it can be reset during testing.
   std::unique_ptr<AttributionNetworkSenderImpl> network_sender_;
@@ -79,14 +79,18 @@
 
 TEST_F(AttributionNetworkSenderTest,
        ConversionReportReceived_NetworkRequestMade) {
-  network_sender_->SendReport(DefaultReport(), base::DoNothing());
+  auto report = DefaultReport();
+  network_sender_->SendReport(report.ReportURL(), report.ReportBody(),
+                              base::DoNothing());
   EXPECT_EQ(1, test_url_loader_factory_.NumPending());
   EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest(
       kReportUrl, ""));
 }
 
 TEST_F(AttributionNetworkSenderTest, LoadFlags) {
-  network_sender_->SendReport(DefaultReport(), base::DoNothing());
+  auto report = DefaultReport();
+  network_sender_->SendReport(report.ReportURL(), report.ReportBody(),
+                              base::DoNothing());
   int load_flags =
       test_url_loader_factory_.GetPendingRequest(0)->request.load_flags;
   EXPECT_TRUE(load_flags & net::LOAD_BYPASS_CACHE);
@@ -94,8 +98,11 @@
 }
 
 TEST_F(AttributionNetworkSenderTest, Isolation) {
-  network_sender_->SendReport(DefaultReport(), base::DoNothing());
-  network_sender_->SendReport(DefaultReport(), base::DoNothing());
+  auto report = DefaultReport();
+  network_sender_->SendReport(report.ReportURL(), report.ReportBody(),
+                              base::DoNothing());
+  network_sender_->SendReport(report.ReportURL(), report.ReportBody(),
+                              base::DoNothing());
 
   const network::ResourceRequest& request1 =
       test_url_loader_factory_.GetPendingRequest(0)->request;
@@ -142,7 +149,8 @@
                           .Build();
     AttributionReport report =
         ReportBuilder(impression).SetTriggerData(5).Build();
-    network_sender_->SendReport(report, base::DoNothing());
+    network_sender_->SendReport(report.ReportURL(), report.ReportBody(),
+                                base::DoNothing());
 
     const network::ResourceRequest* pending_request;
     EXPECT_TRUE(
@@ -161,7 +169,8 @@
           .SetConversionOrigin(url::Origin::Create(GURL("https://sub.b.com")))
           .Build();
   AttributionReport report = ReportBuilder(impression).Build();
-  network_sender_->SendReport(report, base::DoNothing());
+  network_sender_->SendReport(report.ReportURL(), report.ReportBody(),
+                              base::DoNothing());
 
   const network::ResourceRequest* pending_request;
   EXPECT_TRUE(test_url_loader_factory_.IsPending(
@@ -178,10 +187,11 @@
 
 TEST_F(AttributionNetworkSenderTest, ReportSent_CallbackFired) {
   auto report = DefaultReport();
-  EXPECT_CALL(callback_, Run(SentReport(report, SentReport::Status::kSent,
+  EXPECT_CALL(callback_, Run(SendResult(SendResult::Status::kSent,
                                         net::HttpStatusCode::HTTP_OK)));
 
-  network_sender_->SendReport(std::move(report), callback_.Get());
+  network_sender_->SendReport(report.ReportURL(), report.ReportBody(),
+                              callback_.Get());
   EXPECT_EQ(1, test_url_loader_factory_.NumPending());
   EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest(
       kReportUrl, ""));
@@ -190,7 +200,9 @@
 TEST_F(AttributionNetworkSenderTest, SenderDeletedDuringRequest_NoCrash) {
   EXPECT_CALL(callback_, Run).Times(0);
 
-  network_sender_->SendReport(DefaultReport(), callback_.Get());
+  auto report = DefaultReport();
+  network_sender_->SendReport(report.ReportURL(), report.ReportBody(),
+                              callback_.Get());
   EXPECT_EQ(1, test_url_loader_factory_.NumPending());
   network_sender_.reset();
   EXPECT_FALSE(test_url_loader_factory_.SimulateResponseForPendingRequest(
@@ -201,12 +213,12 @@
   auto report = DefaultReport();
 
   // Verify that the sent callback runs if the request times out.
-  // TODO(apaseltiner): Should we propagate the timeout via the SentReport
+  // TODO(apaseltiner): Should we propagate the timeout via the SendResult
   // instead of just setting |http_response_code = 0|?
-  EXPECT_CALL(callback_,
-              Run(SentReport(report, SentReport::Status::kTransientFailure,
-                             /*http_response_code=*/0)));
-  network_sender_->SendReport(std::move(report), callback_.Get());
+  EXPECT_CALL(callback_, Run(SendResult(SendResult::Status::kTransientFailure,
+                                        /*http_response_code=*/0)));
+  network_sender_->SendReport(report.ReportURL(), report.ReportBody(),
+                              callback_.Get());
   EXPECT_EQ(1, test_url_loader_factory_.NumPending());
 
   // The request should time out after 30 seconds.
@@ -219,22 +231,24 @@
        ReportRequestFailsWithTargetedError_ShouldRetrySet) {
   struct {
     int net_error;
-    SentReport::Status expected_status;
+    SendResult::Status expected_status;
   } kTestCases[] = {
-      {net::ERR_INTERNET_DISCONNECTED, SentReport::Status::kTransientFailure},
-      {net::ERR_TIMED_OUT, SentReport::Status::kTransientFailure},
-      {net::ERR_CONNECTION_ABORTED, SentReport::Status::kTransientFailure},
-      {net::ERR_CONNECTION_TIMED_OUT, SentReport::Status::kTransientFailure},
-      {net::ERR_CONNECTION_REFUSED, SentReport::Status::kFailure},
-      {net::ERR_CERT_DATE_INVALID, SentReport::Status::kFailure},
-      {net::OK, SentReport::Status::kFailure},
+      {net::ERR_INTERNET_DISCONNECTED, SendResult::Status::kTransientFailure},
+      {net::ERR_TIMED_OUT, SendResult::Status::kTransientFailure},
+      {net::ERR_CONNECTION_ABORTED, SendResult::Status::kTransientFailure},
+      {net::ERR_CONNECTION_TIMED_OUT, SendResult::Status::kTransientFailure},
+      {net::ERR_CONNECTION_REFUSED, SendResult::Status::kFailure},
+      {net::ERR_CERT_DATE_INVALID, SendResult::Status::kFailure},
+      {net::OK, SendResult::Status::kFailure},
   };
 
   for (const auto& test_case : kTestCases) {
     EXPECT_CALL(callback_,
-                Run(Field(&SentReport::status, test_case.expected_status)));
+                Run(Field(&SendResult::status, test_case.expected_status)));
 
-    network_sender_->SendReport(DefaultReport(), callback_.Get());
+    auto report = DefaultReport();
+    network_sender_->SendReport(report.ReportURL(), report.ReportBody(),
+                                callback_.Get());
     EXPECT_EQ(1, test_url_loader_factory_.NumPending());
 
     // By default, headers are not sent for network errors.
@@ -261,9 +275,10 @@
           kSendHeadersOnNetworkError);
 
   auto report = DefaultReport();
-  EXPECT_CALL(callback_, Run(SentReport(report, SentReport::Status::kFailure,
+  EXPECT_CALL(callback_, Run(SendResult(SendResult::Status::kFailure,
                                         net::HttpStatusCode::HTTP_OK)));
-  network_sender_->SendReport(std::move(report), callback_.Get());
+  network_sender_->SendReport(report.ReportURL(), report.ReportBody(),
+                              callback_.Get());
 
   // Ensure the request was replied to.
   EXPECT_EQ(0, test_url_loader_factory_.NumPending());
@@ -273,10 +288,11 @@
        ReportRequestFailsWithHttpError_ShouldRetryNotSet) {
   auto report = DefaultReport();
   EXPECT_CALL(callback_,
-              Run(SentReport(report, SentReport::Status::kFailure,
+              Run(SendResult(SendResult::Status::kFailure,
                              net::HttpStatusCode::HTTP_BAD_REQUEST)));
 
-  network_sender_->SendReport(std::move(report), callback_.Get());
+  network_sender_->SendReport(report.ReportURL(), report.ReportBody(),
+                              callback_.Get());
   EXPECT_EQ(1, test_url_loader_factory_.NumPending());
 
   EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest(
@@ -291,7 +307,9 @@
 
     EXPECT_CALL(callback_, Run);
 
-    network_sender_->SendReport(DefaultReport(), callback_.Get());
+    auto report = DefaultReport();
+    network_sender_->SendReport(report.ReportURL(), report.ReportBody(),
+                                callback_.Get());
     EXPECT_EQ(1, test_url_loader_factory_.NumPending());
 
     // Simulate the request failing due to network change.
@@ -321,7 +339,9 @@
   {
     base::HistogramTester histograms;
 
-    network_sender_->SendReport(DefaultReport(), base::DoNothing());
+    auto report = DefaultReport();
+    network_sender_->SendReport(report.ReportURL(), report.ReportBody(),
+                                base::DoNothing());
     EXPECT_EQ(1, test_url_loader_factory_.NumPending());
 
     // Simulate the request failing due to network change.
@@ -351,11 +371,12 @@
     EXPECT_CALL(callback_, Run).Times(0);
     EXPECT_CALL(checkpoint, Call(1));
     EXPECT_CALL(callback_,
-                Run(SentReport(report, SentReport::Status::kFailure,
+                Run(SendResult(SendResult::Status::kFailure,
                                net::HttpStatusCode::HTTP_BAD_REQUEST)));
   }
 
-  network_sender_->SendReport(std::move(report), callback_.Get());
+  network_sender_->SendReport(report.ReportURL(), report.ReportBody(),
+                              callback_.Get());
   checkpoint.Call(1);
 
   // We should run the sent callback even if there is an http error.
@@ -367,7 +388,9 @@
   EXPECT_CALL(callback_, Run).Times(10);
 
   for (int i = 0; i < 10; i++) {
-    network_sender_->SendReport(DefaultReport(), callback_.Get());
+    auto report = DefaultReport();
+    network_sender_->SendReport(report.ReportURL(), report.ReportBody(),
+                                callback_.Get());
   }
   EXPECT_EQ(10, test_url_loader_factory_.NumPending());
 
@@ -384,7 +407,9 @@
   // All OK.
   {
     base::HistogramTester histograms;
-    network_sender_->SendReport(DefaultReport(), base::DoNothing());
+    auto report = DefaultReport();
+    network_sender_->SendReport(report.ReportURL(), report.ReportBody(),
+                                base::DoNothing());
     EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest(
         kReportUrl, ""));
     // kOk = 0.
@@ -395,7 +420,9 @@
   // Internal error.
   {
     base::HistogramTester histograms;
-    network_sender_->SendReport(DefaultReport(), base::DoNothing());
+    auto report = DefaultReport();
+    network_sender_->SendReport(report.ReportURL(), report.ReportBody(),
+                                base::DoNothing());
     network::URLLoaderCompletionStatus completion_status(net::ERR_FAILED);
     EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest(
         GURL(kReportUrl), completion_status,
@@ -407,7 +434,9 @@
   }
   {
     base::HistogramTester histograms;
-    network_sender_->SendReport(DefaultReport(), base::DoNothing());
+    auto report = DefaultReport();
+    network_sender_->SendReport(report.ReportURL(), report.ReportBody(),
+                                base::DoNothing());
     EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest(
         kReportUrl, "", net::HTTP_UNAUTHORIZED));
     // kExternalError = 2.
@@ -418,15 +447,4 @@
   }
 }
 
-TEST_F(AttributionNetworkSenderTest, TimeFromConversionToReportSendHistogram) {
-  base::HistogramTester histograms;
-  auto report = DefaultReport();
-  report.set_report_time(base::Time() + base::Hours(5));
-  network_sender_->SendReport(std::move(report), base::DoNothing());
-  EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest(
-      kReportUrl, ""));
-  histograms.ExpectUniqueSample("Conversions.TimeFromConversionToReportSend", 5,
-                                1);
-}
-
 }  // namespace content
diff --git a/content/browser/attribution_reporting/attribution_reporter_impl.cc b/content/browser/attribution_reporting/attribution_reporter_impl.cc
index 88e79ee0..c8600306 100644
--- a/content/browser/attribution_reporting/attribution_reporter_impl.cc
+++ b/content/browser/attribution_reporting/attribution_reporter_impl.cc
@@ -4,22 +4,56 @@
 
 #include "content/browser/attribution_reporting/attribution_reporter_impl.h"
 
+#include <string>
+
 #include "base/bind.h"
 #include "base/callback.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
 #include "base/rand_util.h"
 #include "base/time/clock.h"
+#include "base/time/time.h"
 #include "content/browser/attribution_reporting/attribution_manager.h"
 #include "content/browser/attribution_reporting/attribution_network_sender_impl.h"
 #include "content/browser/attribution_reporting/attribution_report.h"
-#include "content/browser/attribution_reporting/sent_report.h"
+#include "content/browser/attribution_reporting/attribution_utils.h"
+#include "content/browser/attribution_reporting/send_result.h"
 #include "content/browser/storage_partition_impl.h"
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/browser/network_service_instance.h"
 #include "content/public/common/content_client.h"
 #include "services/network/public/cpp/network_connection_tracker.h"
+#include "url/gurl.h"
 
 namespace content {
 
+namespace {
+
+// Called when |report| is to be sent over network, for logging metrics.
+void LogMetricsOnReportSend(const AttributionReport& report) {
+  // Reports sent from the WebUI should not log metrics.
+  if (report.report_time() == base::Time::Min())
+    return;
+
+  // Use a large time range to capture users that might not open the browser for
+  // a long time while a conversion report is pending. Revisit this range if it
+  // is non-ideal for real world data.
+  base::Time now = base::Time::Now();
+  base::Time original_report_time =
+      ComputeReportTime(report.impression(), report.conversion_time());
+  base::TimeDelta time_since_original_report_time = now - original_report_time;
+  base::UmaHistogramCustomTimes(
+      "Conversions.ExtraReportDelay2", time_since_original_report_time,
+      base::Seconds(1), base::Days(24), /*buckets=*/100);
+
+  base::TimeDelta time_from_conversion_to_report_send =
+      report.report_time() - report.conversion_time();
+  UMA_HISTOGRAM_COUNTS_1000("Conversions.TimeFromConversionToReportSend",
+                            time_from_conversion_to_report_send.InHours());
+}
+
+}  // namespace
+
 AttributionReporterImpl::AttributionReporterImpl(
     StoragePartitionImpl* storage_partition,
     const base::Clock* clock,
@@ -59,8 +93,8 @@
   while (!report_queue_.empty()) {
     AttributionReport report = report_queue_.top();
     report_queue_.pop();
-    callback_.Run(SentReport(std::move(report),
-                             SentReport::Status::kRemovedFromQueue,
+    callback_.Run(std::move(report),
+                  SendResult(SendResult::Status::kRemovedFromQueue,
                              /*http_response_code=*/0));
   }
 }
@@ -103,18 +137,23 @@
     // If there's no network connection, drop the report and tell the manager to
     // retry it later.
     if (offline_) {
-      callback_.Run(SentReport(std::move(report), SentReport::Status::kOffline,
-                               /*http_response_code=*/0));
+      callback_.Run(std::move(report), SendResult(SendResult::Status::kOffline,
+                                                  /*http_response_code=*/0));
     } else {
-      network_sender_->SendReport(std::move(report), callback_);
+      LogMetricsOnReportSend(report);
+
+      GURL report_url = report.ReportURL();
+      std::string report_body = report.ReportBody();
+      network_sender_->SendReport(std::move(report_url), std::move(report_body),
+                                  base::BindOnce(callback_, std::move(report)));
     }
   } else {
     // If measurement is disallowed, just drop the report on the floor. We need
     // to make sure we forward that the report was "sent" to ensure it is
     // deleted from storage, etc. This simulates sending the report through a
     // null channel.
-    callback_.Run(SentReport(std::move(report), SentReport::Status::kDropped,
-                             /*http_response_code=*/0));
+    callback_.Run(std::move(report), SendResult(SendResult::Status::kDropped,
+                                                /*http_response_code=*/0));
   }
   MaybeScheduleNextReport();
 }
diff --git a/content/browser/attribution_reporting/attribution_reporter_impl.h b/content/browser/attribution_reporting/attribution_reporter_impl.h
index 1877b23..ca0719cb 100644
--- a/content/browser/attribution_reporting/attribution_reporter_impl.h
+++ b/content/browser/attribution_reporting/attribution_reporter_impl.h
@@ -8,6 +8,7 @@
 #include <stdint.h>
 #include <memory>
 #include <queue>
+#include <string>
 #include <vector>
 
 #include "base/callback_forward.h"
@@ -19,6 +20,8 @@
 #include "services/network/public/cpp/network_connection_tracker.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 
+class GURL;
+
 namespace base {
 class Clock;
 }  // namespace base
@@ -27,7 +30,7 @@
 
 class StoragePartitionImpl;
 
-struct SentReport;
+struct SendResult;
 
 // This class is responsible for managing the dispatch of conversion reports to
 // an AttributionReporterImpl::NetworkSender. It maintains a queue of reports
@@ -45,15 +48,16 @@
     virtual ~NetworkSender() = default;
 
     // Callback used to notify caller that the requested report has been sent.
-    using ReportSentCallback = base::OnceCallback<void(SentReport)>;
+    using ReportSentCallback = base::OnceCallback<void(SendResult)>;
 
     // Generates and sends a conversion report matching |report|. This should
     // generate a secure POST request with no-credentials.
-    virtual void SendReport(AttributionReport report,
+    virtual void SendReport(GURL report_url,
+                            std::string report_body,
                             ReportSentCallback sent_callback) = 0;
   };
 
-  using Callback = base::RepeatingCallback<void(SentReport)>;
+  using Callback = base::RepeatingCallback<void(AttributionReport, SendResult)>;
 
   AttributionReporterImpl(StoragePartitionImpl* storage_partition,
                           const base::Clock* clock,
diff --git a/content/browser/attribution_reporting/attribution_reporter_impl_unittest.cc b/content/browser/attribution_reporting/attribution_reporter_impl_unittest.cc
index 50cba88..95a1df3 100644
--- a/content/browser/attribution_reporting/attribution_reporter_impl_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_reporter_impl_unittest.cc
@@ -5,9 +5,12 @@
 #include "content/browser/attribution_reporting/attribution_reporter_impl.h"
 
 #include "base/memory/raw_ptr.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/mock_callback.h"
+#include "base/time/time.h"
 #include "content/browser/attribution_reporting/attribution_test_utils.h"
-#include "content/browser/attribution_reporting/sent_report.h"
+#include "content/browser/attribution_reporting/send_result.h"
 #include "content/browser/storage_partition_impl.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/network_service_instance.h"
@@ -30,15 +33,23 @@
 
 using Checkpoint = ::testing::MockFunction<void(int step)>;
 
+const char kDefaultReportOrigin[] = "https://report.test/";
+
 // Create a report which should be sent at |report_time|. Impression
 // data/conversion data/conversion id are all the same for simplicity.
 AttributionReport GetReport(base::Time report_time,
-                            AttributionReport::Id conversion_id) {
+                            AttributionReport::Id conversion_id,
+                            base::Time conversion_time = base::Time(),
+                            url::Origin reporting_origin = url::Origin::Create(
+                                GURL(kDefaultReportOrigin))) {
   // Construct impressions with a null impression time as it is not used for
   // reporting.
-  return ReportBuilder(SourceBuilder(base::Time()).Build())
+  return ReportBuilder(SourceBuilder(base::Time())
+                           .SetReportingOrigin(std::move(reporting_origin))
+                           .Build())
       .SetReportTime(report_time)
       .SetReportId(conversion_id)
+      .SetConversionTime(conversion_time)
       .Build();
 }
 
@@ -46,17 +57,16 @@
  public:
   MOCK_METHOD(void,
               SendReport,
-              (AttributionReport report, ReportSentCallback callback),
+              (GURL url, std::string report_body, ReportSentCallback callback),
               (override));
 };
 
-auto InvokeCallbackWith(SentReport::Status status,
+auto InvokeCallbackWith(SendResult::Status status,
                         int http_response_code = 200) {
   return
-      [=](AttributionReport report,
+      [=](GURL url, std::string report_body,
           AttributionReporterImpl::NetworkSender::ReportSentCallback callback) {
-        std::move(callback).Run(
-            SentReport(std::move(report), status, http_response_code));
+        std::move(callback).Run(SendResult(status, http_response_code));
       };
 }
 
@@ -103,11 +113,11 @@
        ReportAddedWithImmediateReportTime_ReportSent) {
   const auto report = GetReport(clock().Now(), AttributionReport::Id(1));
 
-  EXPECT_CALL(*sender_, SendReport(report, _))
-      .WillOnce(InvokeCallbackWith(SentReport::Status::kSent));
+  EXPECT_CALL(*sender_, SendReport(report.ReportURL(), report.ReportBody(), _))
+      .WillOnce(InvokeCallbackWith(SendResult::Status::kSent));
 
-  EXPECT_CALL(callback_, Run(SentReport(report, SentReport::Status::kSent,
-                                        /*http_response_code=*/200)));
+  EXPECT_CALL(callback_, Run(report, SendResult(SendResult::Status::kSent,
+                                                /*http_response_code=*/200)));
 
   reporter_->AddReportsToQueue({report});
 
@@ -121,11 +131,11 @@
   const auto report =
       GetReport(clock().Now() - base::Hours(10), AttributionReport::Id(1));
 
-  EXPECT_CALL(*sender_, SendReport(report, _))
-      .WillOnce(InvokeCallbackWith(SentReport::Status::kSent));
+  EXPECT_CALL(*sender_, SendReport(report.ReportURL(), report.ReportBody(), _))
+      .WillOnce(InvokeCallbackWith(SendResult::Status::kSent));
 
-  EXPECT_CALL(callback_, Run(SentReport(report, SentReport::Status::kSent,
-                                        /*http_response_code=*/200)));
+  EXPECT_CALL(callback_, Run(report, SendResult(SendResult::Status::kSent,
+                                                /*http_response_code=*/200)));
 
   reporter_->AddReportsToQueue({report});
 
@@ -141,8 +151,8 @@
 
   EXPECT_CALL(*sender_, SendReport).Times(0);
   EXPECT_CALL(callback_,
-              Run(SentReport(report, SentReport::Status::kRemovedFromQueue,
-                             /*http_response_code=*/0)));
+              Run(report, SendResult(SendResult::Status::kRemovedFromQueue,
+                                     /*http_response_code=*/0)));
 
   reporter_->AddReportsToQueue({report});
 
@@ -167,7 +177,8 @@
     EXPECT_CALL(checkpoint, Call(1));
     EXPECT_CALL(*sender_, SendReport).Times(0);
     EXPECT_CALL(checkpoint, Call(2));
-    EXPECT_CALL(*sender_, SendReport(report, _));
+    EXPECT_CALL(*sender_,
+                SendReport(report.ReportURL(), report.ReportBody(), _));
   }
 
   reporter_->AddReportsToQueue({report});
@@ -186,7 +197,8 @@
 
   // A duplicate report should be scheduled, as it is up to the manager to
   // perform deduplication.
-  EXPECT_CALL(*sender_, SendReport(report, _)).Times(2);
+  EXPECT_CALL(*sender_, SendReport(report.ReportURL(), report.ReportBody(), _))
+      .Times(2);
 
   reporter_->AddReportsToQueue({report});
   reporter_->AddReportsToQueue({report});
@@ -202,9 +214,11 @@
   {
     InSequence seq;
 
-    EXPECT_CALL(*sender_, SendReport(report, _));
+    EXPECT_CALL(*sender_,
+                SendReport(report.ReportURL(), report.ReportBody(), _));
     EXPECT_CALL(checkpoint, Call(1));
-    EXPECT_CALL(*sender_, SendReport(report, _));
+    EXPECT_CALL(*sender_,
+                SendReport(report.ReportURL(), report.ReportBody(), _));
   }
 
   reporter_->AddReportsToQueue({report});
@@ -227,14 +241,20 @@
     for (int i = 1; i < 10; i++) {
       EXPECT_CALL(checkpoint, Call(i));
 
+      auto origin =
+          url::Origin::Create(GURL(base::StringPrintf("https://%d.com/", i)));
+
       auto report =
-          GetReport(clock().Now() + base::Minutes(i), AttributionReport::Id(i));
+          GetReport(clock().Now() + base::Minutes(i), AttributionReport::Id(i),
+                    base::Time(), std::move(origin));
 
-      EXPECT_CALL(*sender_, SendReport(report, _))
-          .WillOnce(InvokeCallbackWith(SentReport::Status::kSent));
+      EXPECT_CALL(*sender_,
+                  SendReport(report.ReportURL(), report.ReportBody(), _))
+          .WillOnce(InvokeCallbackWith(SendResult::Status::kSent));
 
-      EXPECT_CALL(callback_, Run(SentReport(report, SentReport::Status::kSent,
-                                            /*http_response_code=*/200)));
+      EXPECT_CALL(callback_,
+                  Run(report, SendResult(SendResult::Status::kSent,
+                                         /*http_response_code=*/200)));
 
       reports.push_back(std::move(report));
     }
@@ -259,14 +279,20 @@
     for (int i = 1; i < 10; i++) {
       EXPECT_CALL(checkpoint, Call(i));
 
+      auto origin =
+          url::Origin::Create(GURL(base::StringPrintf("https://%d.com/", i)));
+
       auto report =
-          GetReport(clock().Now() + base::Minutes(i), AttributionReport::Id(i));
+          GetReport(clock().Now() + base::Minutes(i), AttributionReport::Id(i),
+                    base::Time(), std::move(origin));
 
-      EXPECT_CALL(*sender_, SendReport(report, _))
-          .WillOnce(InvokeCallbackWith(SentReport::Status::kSent));
+      EXPECT_CALL(*sender_,
+                  SendReport(report.ReportURL(), report.ReportBody(), _))
+          .WillOnce(InvokeCallbackWith(SendResult::Status::kSent));
 
-      EXPECT_CALL(callback_, Run(SentReport(report, SentReport::Status::kSent,
-                                            /*http_response_code=*/200)));
+      EXPECT_CALL(callback_,
+                  Run(report, SendResult(SendResult::Status::kSent,
+                                         /*http_response_code=*/200)));
 
       reports.push_back(std::move(report));
     }
@@ -300,8 +326,8 @@
 
   EXPECT_CALL(*sender_, SendReport).Times(0);
 
-  EXPECT_CALL(callback_, Run(SentReport(report, SentReport::Status::kDropped,
-                                        /*http_response_code=*/0)));
+  EXPECT_CALL(callback_, Run(report, SendResult(SendResult::Status::kDropped,
+                                                /*http_response_code=*/0)));
 
   reporter_->AddReportsToQueue({report});
 
@@ -327,23 +353,24 @@
 
     EXPECT_CALL(*sender_, SendReport).Times(0);
     EXPECT_CALL(callback_,
-                Run(SentReport(report1_1, SentReport::Status::kOffline,
-                               /*http_response_code=*/0)));
+                Run(report1_1, SendResult(SendResult::Status::kOffline,
+                                          /*http_response_code=*/0)));
     EXPECT_CALL(checkpoint, Call(1));
     EXPECT_CALL(*sender_, SendReport).Times(0);
     EXPECT_CALL(callback_,
-                Run(SentReport(report2_1, SentReport::Status::kOffline,
-                               /*http_response_code=*/0)));
+                Run(report2_1, SendResult(SendResult::Status::kOffline,
+                                          /*http_response_code=*/0)));
     EXPECT_CALL(checkpoint, Call(2));
     EXPECT_CALL(*sender_, SendReport)
-        .WillOnce(InvokeCallbackWith(SentReport::Status::kSent));
-    EXPECT_CALL(callback_, Run(SentReport(report1_2, SentReport::Status::kSent,
+        .WillOnce(InvokeCallbackWith(SendResult::Status::kSent));
+    EXPECT_CALL(callback_,
+                Run(report1_2, SendResult(SendResult::Status::kSent,
                                           /*http_response_code=*/200)));
     EXPECT_CALL(checkpoint, Call(3));
     EXPECT_CALL(*sender_, SendReport).Times(0);
     EXPECT_CALL(callback_,
-                Run(SentReport(report2_2, SentReport::Status::kOffline,
-                               /*http_response_code=*/0)));
+                Run(report2_2, SendResult(SendResult::Status::kOffline,
+                                          /*http_response_code=*/0)));
   }
 
   SetOffline(true);
@@ -368,4 +395,21 @@
   task_environment_.FastForwardBy(base::Minutes(1));
 }
 
+TEST_F(AttributionReporterImplTest, TimeFromConversionToReportSendHistogram) {
+  base::HistogramTester histograms;
+
+  const base::TimeDelta delay = base::Hours(5);
+  const auto report =
+      GetReport(/*report_time=*/clock().Now() + delay, AttributionReport::Id(1),
+                /*conversion_time=*/clock().Now());
+
+  EXPECT_CALL(*sender_, SendReport(report.ReportURL(), report.ReportBody(), _));
+
+  reporter_->AddReportsToQueue({report});
+  task_environment_.FastForwardBy(delay);
+
+  histograms.ExpectUniqueSample("Conversions.TimeFromConversionToReportSend", 5,
+                                1);
+}
+
 }  // namespace content
diff --git a/content/browser/attribution_reporting/attribution_test_utils.cc b/content/browser/attribution_reporting/attribution_test_utils.cc
index 3208cf0..5b372db 100644
--- a/content/browser/attribution_reporting/attribution_test_utils.cc
+++ b/content/browser/attribution_reporting/attribution_test_utils.cc
@@ -145,9 +145,10 @@
     observer.OnSourceDeactivated(source);
 }
 
-void MockAttributionManager::NotifyReportSent(const SentReport& info) {
+void MockAttributionManager::NotifyReportSent(const AttributionReport& report,
+                                              const SendResult& info) {
   for (Observer& observer : observers_)
-    observer.OnReportSent(info);
+    observer.OnReportSent(report, info);
 }
 
 void MockAttributionManager::NotifyReportDropped(
@@ -352,9 +353,9 @@
   return tie(a) == tie(b);
 }
 
-bool operator==(const SentReport& a, const SentReport& b) {
-  const auto tie = [](const SentReport& info) {
-    return std::make_tuple(info.report, info.status, info.http_response_code);
+bool operator==(const SendResult& a, const SendResult& b) {
+  const auto tie = [](const SendResult& info) {
+    return std::make_tuple(info.status, info.http_response_code);
   };
   return tie(a) == tie(b);
 }
@@ -509,32 +510,32 @@
              << "}";
 }
 
-std::ostream& operator<<(std::ostream& out, SentReport::Status status) {
+std::ostream& operator<<(std::ostream& out, SendResult::Status status) {
   switch (status) {
-    case SentReport::Status::kSent:
+    case SendResult::Status::kSent:
       out << "kSent";
       break;
-    case SentReport::Status::kTransientFailure:
+    case SendResult::Status::kTransientFailure:
       out << "kTransientFailure";
       break;
-    case SentReport::Status::kFailure:
+    case SendResult::Status::kFailure:
       out << "kFailure";
       break;
-    case SentReport::Status::kDropped:
+    case SendResult::Status::kDropped:
       out << "kDropped";
       break;
-    case SentReport::Status::kOffline:
+    case SendResult::Status::kOffline:
       out << "kOffline";
       break;
-    case SentReport::Status::kRemovedFromQueue:
+    case SendResult::Status::kRemovedFromQueue:
       out << "kRemovedFromQueue";
       break;
   }
   return out;
 }
 
-std::ostream& operator<<(std::ostream& out, const SentReport& info) {
-  return out << "{report=" << info.report << ",status=" << info.status
+std::ostream& operator<<(std::ostream& out, const SendResult& info) {
+  return out << "{status=" << info.status
              << ",http_response_code=" << info.http_response_code << "}";
 }
 
diff --git a/content/browser/attribution_reporting/attribution_test_utils.h b/content/browser/attribution_reporting/attribution_test_utils.h
index c5bc8b59..b927d70 100644
--- a/content/browser/attribution_reporting/attribution_test_utils.h
+++ b/content/browser/attribution_reporting/attribution_test_utils.h
@@ -24,7 +24,7 @@
 #include "content/browser/attribution_reporting/attribution_report.h"
 #include "content/browser/attribution_reporting/attribution_storage.h"
 #include "content/browser/attribution_reporting/rate_limit_table.h"
-#include "content/browser/attribution_reporting/sent_report.h"
+#include "content/browser/attribution_reporting/send_result.h"
 #include "content/browser/attribution_reporting/storable_source.h"
 #include "content/browser/attribution_reporting/storable_trigger.h"
 #include "content/test/test_content_browser_client.h"
@@ -196,7 +196,8 @@
   void NotifyReportsChanged();
   void NotifySourceDeactivated(
       const AttributionStorage::DeactivatedSource& source);
-  void NotifyReportSent(const SentReport& info);
+  void NotifyReportSent(const AttributionReport& report,
+                        const SendResult& info);
   void NotifyReportDropped(
       const AttributionStorage::CreateReportResult& result);
 
@@ -331,7 +332,7 @@
 
 bool operator==(const AttributionReport& a, const AttributionReport& b);
 
-bool operator==(const SentReport& a, const SentReport& b);
+bool operator==(const SendResult& a, const SendResult& b);
 
 bool operator==(const AttributionStorage::DeactivatedSource& a,
                 const AttributionStorage::DeactivatedSource& b);
@@ -354,9 +355,9 @@
 
 std::ostream& operator<<(std::ostream& out, const AttributionReport& report);
 
-std::ostream& operator<<(std::ostream& out, SentReport::Status status);
+std::ostream& operator<<(std::ostream& out, SendResult::Status status);
 
-std::ostream& operator<<(std::ostream& out, const SentReport& info);
+std::ostream& operator<<(std::ostream& out, const SendResult& info);
 
 std::ostream& operator<<(std::ostream& out,
                          StorableSource::AttributionLogic attribution_logic);
diff --git a/content/browser/attribution_reporting/sent_report.h b/content/browser/attribution_reporting/send_result.h
similarity index 65%
rename from content/browser/attribution_reporting/sent_report.h
rename to content/browser/attribution_reporting/send_result.h
index 9cd78d5..d4d7732 100644
--- a/content/browser/attribution_reporting/sent_report.h
+++ b/content/browser/attribution_reporting/send_result.h
@@ -2,18 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CONTENT_BROWSER_ATTRIBUTION_REPORTING_SENT_REPORT_H_
-#define CONTENT_BROWSER_ATTRIBUTION_REPORTING_SENT_REPORT_H_
+#ifndef CONTENT_BROWSER_ATTRIBUTION_REPORTING_SEND_RESULT_H_
+#define CONTENT_BROWSER_ATTRIBUTION_REPORTING_SEND_RESULT_H_
 
-#include "base/time/time.h"
-#include "content/browser/attribution_reporting/attribution_report.h"
 #include "content/common/content_export.h"
 
 namespace content {
 
 // Struct that contains data about sent reports. Some info is displayed in the
 // Conversion Internals WebUI.
-struct CONTENT_EXPORT SentReport {
+struct CONTENT_EXPORT SendResult {
   enum class Status {
     kSent,
     // The report failed without receiving response headers.
@@ -30,14 +28,13 @@
     kRemovedFromQueue,
   };
 
-  SentReport(AttributionReport report, Status status, int http_response_code);
-  SentReport(const SentReport& other);
-  SentReport& operator=(const SentReport& other);
-  SentReport(SentReport&& other);
-  SentReport& operator=(SentReport&& other);
-  ~SentReport();
-
-  AttributionReport report;
+  SendResult(Status status, int http_response_code)
+      : status(status), http_response_code(http_response_code) {}
+  SendResult(const SendResult& other) = default;
+  SendResult& operator=(const SendResult& other) = default;
+  SendResult(SendResult&& other) = default;
+  SendResult& operator=(SendResult&& other) = default;
+  ~SendResult() = default;
 
   Status status;
 
@@ -50,4 +47,4 @@
 
 }  // namespace content
 
-#endif  // CONTENT_BROWSER_ATTRIBUTION_REPORTING_SENT_REPORT_H_
+#endif  // CONTENT_BROWSER_ATTRIBUTION_REPORTING_SEND_RESULT_H_
diff --git a/content/browser/attribution_reporting/sent_report.cc b/content/browser/attribution_reporting/sent_report.cc
deleted file mode 100644
index 0f77409..0000000
--- a/content/browser/attribution_reporting/sent_report.cc
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "content/browser/attribution_reporting/sent_report.h"
-
-namespace content {
-
-SentReport::SentReport(AttributionReport report,
-                       Status status,
-                       int http_response_code)
-    : report(std::move(report)),
-      status(status),
-      http_response_code(http_response_code) {}
-
-SentReport::SentReport(const SentReport& other) = default;
-SentReport& SentReport::operator=(const SentReport& other) = default;
-SentReport::SentReport(SentReport&& other) = default;
-SentReport& SentReport::operator=(SentReport&& other) = default;
-
-SentReport::~SentReport() = default;
-
-}  // namespace content
diff --git a/content/browser/back_forward_cache_features_browsertest.cc b/content/browser/back_forward_cache_features_browsertest.cc
index 39ce9992..62aea25 100644
--- a/content/browser/back_forward_cache_features_browsertest.cc
+++ b/content/browser/back_forward_cache_features_browsertest.cc
@@ -2321,8 +2321,16 @@
 
 // Tests that the short vibration sequence on the page stops after it enters
 // bfcache.
+// http://crbug.com/1280741
+#if defined(OS_MAC)
+#define MAYBE_ShortVibrationSequenceStopsAfterEnteringCache \
+    DISABLED_ShortVibrationSequenceStopsAfterEnteringCache
+#else
+#define MAYBE_ShortVibrationSequenceStopsAfterEnteringCache \
+    ShortVibrationSequenceStopsAfterEnteringCache
+#endif
 IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
-                       ShortVibrationSequenceStopsAfterEnteringCache) {
+                       MAYBE_ShortVibrationSequenceStopsAfterEnteringCache) {
   ASSERT_TRUE(embedded_test_server()->Start());
   TestVibrationManager vibration_manager;
 
diff --git a/content/browser/devtools/protocol/devtools_protocol_browsertest.cc b/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
index cddf902..963313e0 100644
--- a/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
+++ b/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
@@ -1022,7 +1022,10 @@
   EXPECT_THAT(console_messages_, ElementsAre("first page", "second page"));
 }
 
-IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, NavigationPreservesMessages) {
+// TODO(crbug.com/1280531): Disabled due to flakiness. Flaky on mac and linux
+// la-cros
+IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest,
+                       DISABLED_NavigationPreservesMessages) {
   ASSERT_TRUE(embedded_test_server()->Start());
   GURL test_url = embedded_test_server()->GetURL("/devtools/navigation.html");
   NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 1);
diff --git a/content/browser/devtools/protocol/page_handler.cc b/content/browser/devtools/protocol/page_handler.cc
index d48b487..b92684a 100644
--- a/content/browser/devtools/protocol/page_handler.cc
+++ b/content/browser/devtools/protocol/page_handler.cc
@@ -194,11 +194,14 @@
 
 }  // namespace
 
-PageHandler::PageHandler(EmulationHandler* emulation_handler,
-                         BrowserHandler* browser_handler,
-                         bool allow_unsafe_operations)
+PageHandler::PageHandler(
+    EmulationHandler* emulation_handler,
+    BrowserHandler* browser_handler,
+    bool allow_unsafe_operations,
+    absl::optional<url::Origin> navigation_initiator_origin)
     : DevToolsDomainHandler(Page::Metainfo::domainName),
       allow_unsafe_operations_(allow_unsafe_operations),
+      navigation_initiator_origin_(navigation_initiator_origin),
       enabled_(false),
       screencast_enabled_(false),
       screencast_quality_(kDefaultScreenshotQuality),
@@ -520,6 +523,15 @@
   params.referrer = Referrer(GURL(referrer.fromMaybe("")), policy);
   params.transition_type = type;
   params.frame_tree_node_id = frame_tree_node->frame_tree_node_id();
+  if (navigation_initiator_origin_.has_value()) {
+    // When this agent has an initiator origin defined, ensure that its
+    // navigations are considered renderer-initiated by that origin, such that
+    // URL spoof defenses are in effect. (crbug.com/1192417)
+    params.is_renderer_initiated = true;
+    params.initiator_origin = *navigation_initiator_origin_;
+    params.source_site_instance = SiteInstance::CreateForURL(
+        host_->GetBrowserContext(), navigation_initiator_origin_->GetURL());
+  }
   // Handler may be destroyed while navigating if the session
   // gets disconnected as a result of access checks.
   base::WeakPtr<PageHandler> weak_self = weak_factory_.GetWeakPtr();
diff --git a/content/browser/devtools/protocol/page_handler.h b/content/browser/devtools/protocol/page_handler.h
index e8a0df8..1b7edbd 100644
--- a/content/browser/devtools/protocol/page_handler.h
+++ b/content/browser/devtools/protocol/page_handler.h
@@ -63,7 +63,8 @@
  public:
   PageHandler(EmulationHandler* emulation_handler,
               BrowserHandler* browser_handler,
-              bool allow_unsafe_operations);
+              bool allow_unsafe_operations,
+              absl::optional<url::Origin> navigation_initiator_origin);
 
   PageHandler(const PageHandler&) = delete;
   PageHandler& operator=(const PageHandler&) = delete;
@@ -209,6 +210,7 @@
   void OnDownloadDestroyed(download::DownloadItem* item) override;
 
   const bool allow_unsafe_operations_;
+  const absl::optional<url::Origin> navigation_initiator_origin_;
 
   bool enabled_;
   bool bypass_csp_ = false;
diff --git a/content/browser/devtools/render_frame_devtools_agent_host.cc b/content/browser/devtools/render_frame_devtools_agent_host.cc
index 9aaf942..fdb3757 100644
--- a/content/browser/devtools/render_frame_devtools_agent_host.cc
+++ b/content/browser/devtools/render_frame_devtools_agent_host.cc
@@ -294,6 +294,14 @@
   if (!ShouldAllowSession(session))
     return false;
 
+  if (frame_tree_node_ && !frame_tree_node_->parent() &&
+      frame_tree_node_->is_on_initial_empty_document()) {
+    // Since the DevTools protocol can be used to modify the initial empty
+    // document of a tab, notify the browser that the pending URL shouldn't be
+    // displayed anymore to eliminate a URL spoof risk.
+    frame_host_->DidAccessInitialMainDocument();
+  }
+
   auto emulation_handler = std::make_unique<protocol::EmulationHandler>();
   protocol::EmulationHandler* emulation_handler_ptr = emulation_handler.get();
 
@@ -348,7 +356,8 @@
       GetId(), auto_attacher_.get(), session->GetRootSession()));
   session->AddHandler(std::make_unique<protocol::PageHandler>(
       emulation_handler_ptr, browser_handler_ptr,
-      session->GetClient()->AllowUnsafeOperations()));
+      session->GetClient()->AllowUnsafeOperations(),
+      session->GetClient()->GetNavigationInitiatorOrigin()));
   session->AddHandler(std::make_unique<protocol::SecurityHandler>());
   if (!frame_tree_node_ || !frame_tree_node_->parent()) {
     session->AddHandler(
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index cf91da5..90a3111a 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -4427,6 +4427,7 @@
   // Sets a default value for before_unload_end_time so that the browser
   // survives a hacked renderer.
   base::TimeTicks before_unload_end_time = renderer_before_unload_end_time;
+  base::TimeDelta browser_to_renderer_ipc_time_delta;
   if (!renderer_before_unload_start_time.is_null() &&
       !renderer_before_unload_end_time.is_null()) {
     base::TimeTicks before_unload_completed_time = base::TimeTicks::Now();
@@ -4448,6 +4449,19 @@
           converter.ToLocalTimeTicks(blink::RemoteTimeTicks::FromTimeTicks(
               renderer_before_unload_end_time));
       before_unload_end_time = browser_before_unload_end_time.ToTimeTicks();
+      if (base::FeatureList::IsEnabled(
+              features::kIncludeIpcOverheadInNavigationStart)) {
+        const blink::LocalTimeTicks browser_before_unload_start_time =
+            converter.ToLocalTimeTicks(blink::RemoteTimeTicks::FromTimeTicks(
+                renderer_before_unload_start_time));
+        browser_to_renderer_ipc_time_delta =
+            browser_before_unload_start_time.ToTimeTicks() -
+            send_before_unload_start_time_;
+      }
+    } else if (base::FeatureList::IsEnabled(
+                   features::kIncludeIpcOverheadInNavigationStart)) {
+      browser_to_renderer_ipc_time_delta =
+          (renderer_before_unload_start_time - send_before_unload_start_time_);
     }
 
     base::TimeDelta on_before_unload_overhead_time =
@@ -4490,7 +4504,12 @@
               proceed, before_unload_end_time);
         }
       },
-      weak_ptr_factory_.GetWeakPtr(), before_unload_end_time, proceed,
+      // The overhead of the browser->renderer IPC may be non trivial. Account
+      // for it here. Ideally this would also include the time to execute the
+      // JS, but we would need to exclude the time spent waiting for a dialog,
+      // which is tricky.
+      weak_ptr_factory_.GetWeakPtr(),
+      before_unload_end_time - browser_to_renderer_ipc_time_delta, proceed,
       unload_ack_is_for_navigation_);
 
   if (is_frame_being_destroyed) {
diff --git a/content/browser/renderer_host/render_view_host_impl.cc b/content/browser/renderer_host/render_view_host_impl.cc
index 2d918fcf..de1ab4c9 100644
--- a/content/browser/renderer_host/render_view_host_impl.cc
+++ b/content/browser/renderer_host/render_view_host_impl.cc
@@ -678,10 +678,6 @@
 }
 
 void RenderViewHostImpl::ClosePage() {
-  // TODO(crbug.com/1161996): Remove this VLOG once the investigation is done.
-  VLOG(1) << "RenderViewHostImpl::ClosePage() IsRenderViewLive() = "
-          << IsRenderViewLive()
-          << ", SuddenTerminationAllowed() = " << SuddenTerminationAllowed();
   is_waiting_for_page_close_completion_ = true;
 
   if (IsRenderViewLive() && !SuddenTerminationAllowed()) {
diff --git a/content/browser/renderer_host/render_widget_host_view_mac.mm b/content/browser/renderer_host/render_widget_host_view_mac.mm
index 72099de3..9b276a3 100644
--- a/content/browser/renderer_host/render_widget_host_view_mac.mm
+++ b/content/browser/renderer_host/render_widget_host_view_mac.mm
@@ -1108,6 +1108,16 @@
     DCHECK(GetFocusedWidget());
     if (actual_range)
       *actual_range = requested_range;
+
+    // Check selection bounds first (currently populated only for EditContext)
+    const absl::optional<gfx::Rect> text_selection_bound =
+        GetTextInputManager()->GetTextSelectionBounds();
+    if (text_selection_bound) {
+      *rect = text_selection_bound.value();
+      return true;
+    }
+
+    // If no selection bounds, fall back to use selection region.
     *rect = GetTextInputManager()
                 ->GetSelectionRegion(GetFocusedWidget()->GetView())
                 ->caret_rect;
diff --git a/content/browser/webui/web_ui_data_source_impl.cc b/content/browser/webui/web_ui_data_source_impl.cc
index 9565175e..eda0259 100644
--- a/content/browser/webui/web_ui_data_source_impl.cc
+++ b/content/browser/webui/web_ui_data_source_impl.cc
@@ -12,12 +12,15 @@
 #include <vector>
 
 #include "base/bind.h"
+#include "base/callback.h"
+#include "base/check.h"
 #include "base/check_op.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/ref_counted_memory.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/task/thread_pool.h"
 #include "base/trace_event/trace_event.h"
 #include "content/grit/content_resources.h"
 #include "content/public/browser/content_browser_client.h"
@@ -52,6 +55,24 @@
 
 namespace {
 
+void GetDataResourceBytesOnWorkerThread(
+    int resource_id,
+    URLDataSource::GotDataCallback callback) {
+  base::ThreadPool::CreateSequencedTaskRunner(
+      {base::TaskPriority::USER_BLOCKING,
+       base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN, base::MayBlock()})
+      ->PostTask(
+          FROM_HERE,
+          base::BindOnce(
+              [](int resource_id, URLDataSource::GotDataCallback callback) {
+                ContentClient* content_client = GetContentClient();
+                DCHECK(content_client);
+                std::move(callback).Run(
+                    content_client->GetDataResourceBytes(resource_id));
+              },
+              resource_id, std::move(callback)));
+}
+
 std::string CleanUpPath(const std::string& path) {
   // Remove the query string for named resource lookups.
   std::string clean_path = path.substr(0, path.find_first_of('?'));
@@ -351,9 +372,7 @@
   if (resource_id == kNonExistentResource) {
     std::move(callback).Run(nullptr);
   } else {
-    scoped_refptr<base::RefCountedMemory> response(
-        GetContentClient()->GetDataResourceBytes(resource_id));
-    std::move(callback).Run(response.get());
+    GetDataResourceBytesOnWorkerThread(resource_id, std::move(callback));
   }
 }
 
diff --git a/content/browser/xr/OWNERS b/content/browser/xr/OWNERS
index 5682b9d7..d95d28c 100644
--- a/content/browser/xr/OWNERS
+++ b/content/browser/xr/OWNERS
@@ -1,5 +1 @@
-alcooper@chromium.org
-klausw@chromium.org
-
-# Browser Test-related.
-bsheedy@chromium.org
+file://components/webxr/OWNERS
diff --git a/content/browser/xr/service/isolated_device_provider.cc b/content/browser/xr/service/isolated_device_provider.cc
index ee25ce2..f7b0c40 100644
--- a/content/browser/xr/service/isolated_device_provider.cc
+++ b/content/browser/xr/service/isolated_device_provider.cc
@@ -16,19 +16,8 @@
 namespace content {
 
 void IsolatedVRDeviceProvider::Initialize(
-    base::RepeatingCallback<void(device::mojom::XRDeviceId,
-                                 device::mojom::VRDisplayInfoPtr,
-                                 device::mojom::XRDeviceDataPtr,
-                                 mojo::PendingRemote<device::mojom::XRRuntime>)>
-        add_device_callback,
-    base::RepeatingCallback<void(device::mojom::XRDeviceId)>
-        remove_device_callback,
-    base::OnceClosure initialization_complete,
-    device::XrFrameSinkClientFactory xr_frame_sink_client_factory) {
-  add_device_callback_ = std::move(add_device_callback);
-  remove_device_callback_ = std::move(remove_device_callback);
-  initialization_complete_ = std::move(initialization_complete);
-
+    device::VRDeviceProviderClient* client) {
+  client_ = client;
   SetupDeviceProvider();
 }
 
@@ -41,8 +30,8 @@
     mojo::PendingRemote<device::mojom::XRCompositorHost> compositor_host,
     device::mojom::XRDeviceDataPtr device_data,
     device::mojom::XRDeviceId device_id) {
-  add_device_callback_.Run(device_id, nullptr, std::move(device_data),
-                           std::move(device));
+  client_->AddRuntime(device_id, nullptr, std::move(device_data),
+                      std::move(device));
 
   auto* integration_client = GetXrIntegrationClient();
   if (!integration_client)
@@ -56,7 +45,7 @@
 }
 
 void IsolatedVRDeviceProvider::OnDeviceRemoved(device::mojom::XRDeviceId id) {
-  remove_device_callback_.Run(id);
+  client_->RemoveRuntime(id);
   ui_host_map_.erase(id);
 }
 
@@ -65,7 +54,7 @@
   // should be removed.
   for (auto& entry : ui_host_map_) {
     auto id = entry.first;
-    remove_device_callback_.Run(id);
+    client_->RemoveRuntime(id);
   }
   ui_host_map_.clear();
 
@@ -88,7 +77,7 @@
 void IsolatedVRDeviceProvider::OnDevicesEnumerated() {
   if (!initialized_) {
     initialized_ = true;
-    std::move(initialization_complete_).Run();
+    client_->OnProviderInitialized();
   }
 
   // Either we've hit the max retries and given up (in which case we don't have
diff --git a/content/browser/xr/service/isolated_device_provider.h b/content/browser/xr/service/isolated_device_provider.h
index f65036e1..3bf72be 100644
--- a/content/browser/xr/service/isolated_device_provider.h
+++ b/content/browser/xr/service/isolated_device_provider.h
@@ -26,16 +26,7 @@
   ~IsolatedVRDeviceProvider() override;
 
   // If the VR API requires initialization that should happen here.
-  void Initialize(
-      base::RepeatingCallback<void(
-          device::mojom::XRDeviceId,
-          device::mojom::VRDisplayInfoPtr,
-          device::mojom::XRDeviceDataPtr,
-          mojo::PendingRemote<device::mojom::XRRuntime>)> add_device_callback,
-      base::RepeatingCallback<void(device::mojom::XRDeviceId)>
-          remove_device_callback,
-      base::OnceClosure initialization_complete,
-      device::XrFrameSinkClientFactory xr_frame_sink_client_factory) override;
+  void Initialize(device::VRDeviceProviderClient* client) override;
 
   // Returns true if initialization is complete.
   bool Initialized() override;
@@ -56,15 +47,7 @@
   int retry_count_ = 0;
   mojo::Remote<device::mojom::IsolatedXRRuntimeProvider> device_provider_;
 
-  // TODO(crbug.com/1090029): Wrap XRDeviceId + VRDisplayInfo into XRDeviceData
-  base::RepeatingCallback<void(device::mojom::XRDeviceId,
-                               device::mojom::VRDisplayInfoPtr,
-                               device::mojom::XRDeviceDataPtr,
-                               mojo::PendingRemote<device::mojom::XRRuntime>)>
-      add_device_callback_;
-  base::RepeatingCallback<void(device::mojom::XRDeviceId)>
-      remove_device_callback_;
-  base::OnceClosure initialization_complete_;
+  device::VRDeviceProviderClient* client_ = nullptr;
   mojo::Receiver<device::mojom::IsolatedXRRuntimeProviderClient> receiver_{
       this};
 
diff --git a/content/browser/xr/service/xr_runtime_manager_impl.cc b/content/browser/xr/service/xr_runtime_manager_impl.cc
index 0bcc089c..2a212db0 100644
--- a/content/browser/xr/service/xr_runtime_manager_impl.cc
+++ b/content/browser/xr/service/xr_runtime_manager_impl.cc
@@ -503,14 +503,10 @@
       continue;
     }
 
-    provider->Initialize(
-        base::BindRepeating(&XRRuntimeManagerImpl::AddRuntime,
-                            base::Unretained(this)),
-        base::BindRepeating(&XRRuntimeManagerImpl::RemoveRuntime,
-                            base::Unretained(this)),
-        base::BindOnce(&XRRuntimeManagerImpl::OnProviderInitialized,
-                       base::Unretained(this)),
-        base::BindRepeating(&FrameSinkClientFactory));
+    // It is acceptable for the providers to potentially take/keep a reference
+    // to ourselves here, since we own the providers and can guarantee that they
+    // will not outlive us.
+    provider->Initialize(this);
   }
 
   providers_initialized_ = true;
@@ -573,6 +569,11 @@
     service->RuntimesChanged();
 }
 
+device::XrFrameSinkClientFactory
+XRRuntimeManagerImpl::GetXrFrameSinkClientFactory() {
+  return base::BindRepeating(&FrameSinkClientFactory);
+}
+
 void XRRuntimeManagerImpl::ForEachRuntime(
     base::RepeatingCallback<void(BrowserXRRuntime*)> fn) {
   for (auto& runtime : runtimes_) {
diff --git a/content/browser/xr/service/xr_runtime_manager_impl.h b/content/browser/xr/service/xr_runtime_manager_impl.h
index 0f8ced4..c6aa700 100644
--- a/content/browser/xr/service/xr_runtime_manager_impl.h
+++ b/content/browser/xr/service/xr_runtime_manager_impl.h
@@ -23,6 +23,7 @@
 #include "content/public/browser/gpu_data_manager_observer.h"
 #include "content/public/browser/xr_integration_client.h"
 #include "content/public/browser/xr_runtime_manager.h"
+#include "device/vr/public/cpp/vr_device_provider.h"
 #include "device/vr/public/mojom/vr_service.mojom-forward.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 
@@ -38,7 +39,8 @@
 class CONTENT_EXPORT XRRuntimeManagerImpl
     : public XRRuntimeManager,
       public base::RefCounted<XRRuntimeManagerImpl>,
-      public content::GpuDataManagerObserver {
+      public content::GpuDataManagerObserver,
+      public device::VRDeviceProviderClient {
  public:
   friend base::RefCounted<XRRuntimeManagerImpl>;
   static constexpr auto kRefCountPreference =
@@ -92,6 +94,16 @@
   void ForEachRuntime(
       base::RepeatingCallback<void(BrowserXRRuntime*)> fn) override;
 
+  // VRDeviceProviderClient implementation
+  void AddRuntime(
+      device::mojom::XRDeviceId id,
+      device::mojom::VRDisplayInfoPtr info,
+      device::mojom::XRDeviceDataPtr device_data,
+      mojo::PendingRemote<device::mojom::XRRuntime> runtime) override;
+  void RemoveRuntime(device::mojom::XRDeviceId id) override;
+  void OnProviderInitialized() override;
+  device::XrFrameSinkClientFactory GetXrFrameSinkClientFactory() override;
+
  private:
   // Constructor also used by tests to supply an arbitrary list of providers
   static scoped_refptr<XRRuntimeManagerImpl> CreateInstance(
@@ -108,15 +120,8 @@
   ~XRRuntimeManagerImpl() override;
 
   void InitializeProviders();
-  void OnProviderInitialized();
   bool AreAllProvidersInitialized();
 
-  void AddRuntime(device::mojom::XRDeviceId id,
-                  device::mojom::VRDisplayInfoPtr info,
-                  device::mojom::XRDeviceDataPtr device_data,
-                  mojo::PendingRemote<device::mojom::XRRuntime> runtime);
-  void RemoveRuntime(device::mojom::XRDeviceId id);
-
   bool IsInitializedOnCompatibleAdapter(BrowserXRRuntimeImpl* runtime);
 
   // Gets the system default immersive-vr runtime if available.
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityTreeTest.java b/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityTreeTest.java
index 3d36dae..3d7479e 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityTreeTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityTreeTest.java
@@ -21,6 +21,7 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.test.util.Batch;
+import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.content_public.browser.test.ContentJUnit4ClassRunner;
 
@@ -285,7 +286,10 @@
 
     @Test
     @SmallTest
-    public void test_buttonWithListboxPopup() {
+    @DisableIf.Build(sdk_is_greater_than = Build.VERSION_CODES.Q,
+            message = "Fails on Android 11: https://crbug.com/1280713")
+    public void
+    test_buttonWithListboxPopup() {
         performHtmlTest("button-with-listbox-popup.html");
     }
 
@@ -1107,7 +1111,10 @@
 
     @Test
     @SmallTest
-    public void test_selectmenu() {
+    @DisableIf.Build(sdk_is_greater_than = Build.VERSION_CODES.Q,
+            message = "Fails on Android 11: https://crbug.com/1280713")
+    public void
+    test_selectmenu() {
         performHtmlTest("selectmenu.html");
     }
 
@@ -1344,4 +1351,4 @@
     public void test_wbr() {
         performHtmlTest("wbr.html");
     }
-}
\ No newline at end of file
+}
diff --git a/content/public/browser/devtools_agent_host_client.cc b/content/public/browser/devtools_agent_host_client.cc
index e6f7cd5..8d5455e 100644
--- a/content/public/browser/devtools_agent_host_client.cc
+++ b/content/public/browser/devtools_agent_host_client.cc
@@ -39,4 +39,9 @@
   return false;
 }
 
+absl::optional<url::Origin>
+DevToolsAgentHostClient::GetNavigationInitiatorOrigin() {
+  return absl::nullopt;
+}
+
 }  // namespace content
diff --git a/content/public/browser/devtools_agent_host_client.h b/content/public/browser/devtools_agent_host_client.h
index 221e9da..cb13d3c6 100644
--- a/content/public/browser/devtools_agent_host_client.h
+++ b/content/public/browser/devtools_agent_host_client.h
@@ -7,6 +7,8 @@
 
 #include "base/containers/span.h"
 #include "content/common/content_export.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "url/origin.h"
 
 class GURL;
 namespace content {
@@ -52,6 +54,13 @@
   // that are already privileged, such as local automation clients.
   virtual bool AllowUnsafeOperations();
 
+  // A value to use as NavigationController::LoadURLParams::initiator_origin.
+  // If set, navigations would also be treated as renderer-initiated.
+  // This is useful e.g. for Chrome Extensions so that their calls to
+  // Page.navigate would be treated as renderer-initiated naviation subject to
+  // URL spoofing protection.
+  virtual absl::optional<url::Origin> GetNavigationInitiatorOrigin();
+
   // Determines protocol message format.
   virtual bool UsesBinaryProtocol();
 };
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index 1743ca8..5046fce 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -384,6 +384,20 @@
 const base::Feature kIdleDetection{"IdleDetection",
                                    base::FEATURE_ENABLED_BY_DEFAULT};
 
+// Historically most navigations required IPC from browser to renderer and
+// from renderer back to browser. This was done to check for before-unload
+// handlers on the current page and occurred regardless of whether a
+// before-unload handler was present. The navigation start time (as used in
+// various metrics) is the time the renderer initiates the IPC back to the
+// browser. If this feature is enabled, the navigation start time takes into
+// account the cost of the IPC from the browser to renderer. More specifically:
+// navigation_start = time_renderer_sends_ipc_to_browser -
+//    (time_renderer_receives_ipc - time_browser_sends_ipc)
+// Note that navigation_start does not take into account the amount of time the
+// renderer spends processing the IPC (that is, executing script).
+const base::Feature kIncludeIpcOverheadInNavigationStart{
+    "IncludeIpcOverheadInNavigationStart", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Kill switch for the GetInstalledRelatedApps API.
 const base::Feature kInstalledApp{"InstalledApp",
                                   base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index e730daa..db74c78 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -93,6 +93,7 @@
 CONTENT_EXPORT extern const base::Feature kGreaseUACH;
 CONTENT_EXPORT extern const base::Feature kHistoryPreventSandboxedNavigation;
 CONTENT_EXPORT extern const base::Feature kIdleDetection;
+CONTENT_EXPORT extern const base::Feature kIncludeIpcOverheadInNavigationStart;
 CONTENT_EXPORT extern const base::Feature kInstalledApp;
 CONTENT_EXPORT extern const base::Feature kInstalledAppProvider;
 CONTENT_EXPORT extern const base::Feature kInstalledAppsInCbd;
diff --git a/content/services/isolated_xr_device/OWNERS b/content/services/isolated_xr_device/OWNERS
index 4c4713b..d95d28c 100644
--- a/content/services/isolated_xr_device/OWNERS
+++ b/content/services/isolated_xr_device/OWNERS
@@ -1,2 +1 @@
-alcooper@chromium.org
-klausw@chromium.org
+file://components/webxr/OWNERS
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
index 4f9cb153..bd4f90f 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
@@ -128,6 +128,7 @@
 crbug.com/1099955 [ swiftshader-gl angle-disabled win   ] WebglExtension_EXT_shader_texture_lod [ Skip ]
 crbug.com/1099955 [ swiftshader-gl angle-disabled linux ] WebglExtension_EXT_texture_compression_bptc [ Skip ]
 crbug.com/1099955 [ swiftshader-gl angle-disabled win   ] WebglExtension_EXT_texture_compression_bptc [ Skip ]
+crbug.com/1280666 [ swiftshader-gl angle-disabled mac ] WebglExtension_WEBGL_compressed_texture_s3tc_srgb [ Skip ]
 crbug.com/1099955 [ angle-swiftshader no-swiftshader-gl linux ] WebglExtension_EXT_disjoint_timer_query [ Skip ]
 crbug.com/1099955 [ angle-swiftshader no-swiftshader-gl mac   ] WebglExtension_EXT_disjoint_timer_query [ Skip ]
 crbug.com/1099955 [ angle-swiftshader no-swiftshader-gl win   ] WebglExtension_EXT_disjoint_timer_query [ Skip ]
diff --git a/content/web_test/browser/web_test_control_host.cc b/content/web_test/browser/web_test_control_host.cc
index 6f8dc9e..4fb06885 100644
--- a/content/web_test/browser/web_test_control_host.cc
+++ b/content/web_test/browser/web_test_control_host.cc
@@ -794,7 +794,7 @@
   if (!renderer_dump_result_->layout) {
     DCHECK_EQ(0, waiting_for_layout_dumps_);
 
-    main_window_->web_contents()->ForEachRenderFrameHost(
+    main_window_->web_contents()->GetMainFrame()->ForEachRenderFrameHost(
         base::BindLambdaForTesting([&](RenderFrameHost* render_frame_host) {
           if (!render_frame_host->IsRenderFrameLive())
             return;
diff --git a/device/bluetooth/chromeos/bluetooth_utils.cc b/device/bluetooth/chromeos/bluetooth_utils.cc
index b0ab4d9a..02c5939c 100644
--- a/device/bluetooth/chromeos/bluetooth_utils.cc
+++ b/device/bluetooth/chromeos/bluetooth_utils.cc
@@ -39,18 +39,6 @@
 
 constexpr base::TimeDelta kMaxDeviceSelectionDuration = base::Seconds(30);
 
-// This enum is tied directly to a UMA enum defined in
-// //tools/metrics/histograms/enums.xml, and should always reflect it (do not
-// change one without changing the other).
-enum class BluetoothTransportType {
-  kUnknown = 0,
-  kClassic = 1,
-  kLE = 2,
-  kDual = 3,
-  kInvalid = 4,
-  kMaxValue = kInvalid
-};
-
 // Get limited number of devices from |devices| and
 // prioritize paired/connecting devices over other devices.
 BluetoothAdapter::DeviceList GetLimitedNumDevices(
diff --git a/device/bluetooth/chromeos/bluetooth_utils.h b/device/bluetooth/chromeos/bluetooth_utils.h
index b78a3e4..d1ec8ae 100644
--- a/device/bluetooth/chromeos/bluetooth_utils.h
+++ b/device/bluetooth/chromeos/bluetooth_utils.h
@@ -98,6 +98,18 @@
   kMaxValue = kSuccess,
 };
 
+// This enum is tied directly to a UMA enum defined in
+// //tools/metrics/histograms/enums.xml, and should always reflect it (do not
+// change one without changing the other).
+enum class BluetoothTransportType {
+  kUnknown = 0,
+  kClassic = 1,
+  kLE = 2,
+  kDual = 3,
+  kInvalid = 4,
+  kMaxValue = kInvalid
+};
+
 // Return filtered devices based on the filter type and max number of devices.
 DEVICE_BLUETOOTH_EXPORT device::BluetoothAdapter::DeviceList
 FilterBluetoothDeviceList(const BluetoothAdapter::DeviceList& devices,
diff --git a/device/vr/OWNERS b/device/vr/OWNERS
index 49db46fe..205aeab 100644
--- a/device/vr/OWNERS
+++ b/device/vr/OWNERS
@@ -1,6 +1,4 @@
-alcooper@chromium.org
-bajones@chromium.org
-bialpio@chromium.org
+file://components/webxr/OWNERS
 
 per-file *.mojom=set noparent
 per-file *.mojom=file://ipc/SECURITY_OWNERS
diff --git a/device/vr/android/arcore/OWNERS b/device/vr/android/arcore/OWNERS
deleted file mode 100644
index 8f984dc5..0000000
--- a/device/vr/android/arcore/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-bialpio@chromium.org
-klausw@chromium.org
diff --git a/device/vr/android/gvr/gvr_device_provider.cc b/device/vr/android/gvr/gvr_device_provider.cc
index a5c20859..6055202 100644
--- a/device/vr/android/gvr/gvr_device_provider.cc
+++ b/device/vr/android/gvr/gvr_device_provider.cc
@@ -14,15 +14,7 @@
 GvrDeviceProvider::GvrDeviceProvider() = default;
 GvrDeviceProvider::~GvrDeviceProvider() = default;
 
-void GvrDeviceProvider::Initialize(
-    base::RepeatingCallback<void(mojom::XRDeviceId,
-                                 mojom::VRDisplayInfoPtr,
-                                 mojom::XRDeviceDataPtr,
-                                 mojo::PendingRemote<mojom::XRRuntime>)>
-        add_device_callback,
-    base::RepeatingCallback<void(mojom::XRDeviceId)> remove_device_callback,
-    base::OnceClosure initialization_complete,
-    XrFrameSinkClientFactory xr_frame_sink_client_factory) {
+void GvrDeviceProvider::Initialize(VRDeviceProviderClient* client) {
   // We only expose GvrDevice if
   //  - we could potentially install VRServices to support presentation, and
   //  - this build is a bundle and, thus, supports installing the VR module.
@@ -30,12 +22,12 @@
     vr_device_ = base::WrapUnique(new GvrDevice());
   }
   if (vr_device_) {
-    add_device_callback.Run(vr_device_->GetId(), vr_device_->GetVRDisplayInfo(),
-                            vr_device_->GetDeviceData(),
-                            vr_device_->BindXRRuntime());
+    client->AddRuntime(vr_device_->GetId(), vr_device_->GetVRDisplayInfo(),
+                       vr_device_->GetDeviceData(),
+                       vr_device_->BindXRRuntime());
   }
   initialized_ = true;
-  std::move(initialization_complete).Run();
+  client->OnProviderInitialized();
 }
 
 bool GvrDeviceProvider::Initialized() {
diff --git a/device/vr/android/gvr/gvr_device_provider.h b/device/vr/android/gvr/gvr_device_provider.h
index 0de4b21..46afc96 100644
--- a/device/vr/android/gvr/gvr_device_provider.h
+++ b/device/vr/android/gvr/gvr_device_provider.h
@@ -24,16 +24,7 @@
 
   ~GvrDeviceProvider() override;
 
-  void Initialize(
-      base::RepeatingCallback<void(mojom::XRDeviceId,
-                                   mojom::VRDisplayInfoPtr,
-                                   mojom::XRDeviceDataPtr,
-                                   mojo::PendingRemote<mojom::XRRuntime>)>
-          add_device_callback,
-      base::RepeatingCallback<void(mojom::XRDeviceId)> remove_device_callback,
-      base::OnceClosure initialization_complete,
-      XrFrameSinkClientFactory xr_frame_sink_client_factory) override;
-
+  void Initialize(VRDeviceProviderClient* client) override;
   bool Initialized() override;
 
  private:
diff --git a/device/vr/openxr/OWNERS b/device/vr/openxr/OWNERS
index 49a9910..0b752de 100644
--- a/device/vr/openxr/OWNERS
+++ b/device/vr/openxr/OWNERS
@@ -1,3 +1,2 @@
-alcooper@chromium.org
-bajones@chromium.org
+file://components/webxr/OWNERS
 rafael.cintron@microsoft.com
diff --git a/device/vr/orientation/orientation_device_provider.cc b/device/vr/orientation/orientation_device_provider.cc
index 2ac2eb2..c2bca59 100644
--- a/device/vr/orientation/orientation_device_provider.cc
+++ b/device/vr/orientation/orientation_device_provider.cc
@@ -19,28 +19,19 @@
 
 VROrientationDeviceProvider::~VROrientationDeviceProvider() = default;
 
-void VROrientationDeviceProvider::Initialize(
-    base::RepeatingCallback<void(mojom::XRDeviceId,
-                                 mojom::VRDisplayInfoPtr,
-                                 mojom::XRDeviceDataPtr,
-                                 mojo::PendingRemote<mojom::XRRuntime>)>
-        add_device_callback,
-    base::RepeatingCallback<void(mojom::XRDeviceId)> remove_device_callback,
-    base::OnceClosure initialization_complete,
-    XrFrameSinkClientFactory xr_frame_sink_client_factory) {
+void VROrientationDeviceProvider::Initialize(VRDeviceProviderClient* client) {
   if (device_ && device_->IsAvailable()) {
-    add_device_callback.Run(device_->GetId(), device_->GetVRDisplayInfo(),
-                            device_->GetDeviceData(), device_->BindXRRuntime());
+    client->AddRuntime(device_->GetId(), device_->GetVRDisplayInfo(),
+                       device_->GetDeviceData(), device_->BindXRRuntime());
     return;
   }
 
   if (!device_) {
+    client_ = client;
     device_ = std::make_unique<VROrientationDevice>(
         sensor_provider_.get(),
         base::BindOnce(&VROrientationDeviceProvider::DeviceInitialized,
                        base::Unretained(this)));
-    add_device_callback_ = add_device_callback;
-    initialized_callback_ = std::move(initialization_complete);
   }
 }
 
@@ -56,13 +47,12 @@
 
   // If the device successfully connected to the orientation APIs, provide it.
   if (device_->IsAvailable()) {
-    add_device_callback_.Run(device_->GetId(), device_->GetVRDisplayInfo(),
-                             device_->GetDeviceData(),
-                             device_->BindXRRuntime());
+    client_->AddRuntime(device_->GetId(), device_->GetVRDisplayInfo(),
+                        device_->GetDeviceData(), device_->BindXRRuntime());
   }
 
   initialized_ = true;
-  std::move(initialized_callback_).Run();
+  client_->OnProviderInitialized();
 }
 
 }  // namespace device
diff --git a/device/vr/orientation/orientation_device_provider.h b/device/vr/orientation/orientation_device_provider.h
index 3a4ae644..1f46dab6 100644
--- a/device/vr/orientation/orientation_device_provider.h
+++ b/device/vr/orientation/orientation_device_provider.h
@@ -29,15 +29,7 @@
 
   ~VROrientationDeviceProvider() override;
 
-  void Initialize(
-      base::RepeatingCallback<void(mojom::XRDeviceId,
-                                   mojom::VRDisplayInfoPtr,
-                                   mojom::XRDeviceDataPtr,
-                                   mojo::PendingRemote<mojom::XRRuntime>)>
-          add_device_callback,
-      base::RepeatingCallback<void(mojom::XRDeviceId)> remove_device_callback,
-      base::OnceClosure initialization_complete,
-      XrFrameSinkClientFactory xr_frame_sink_client_factory) override;
+  void Initialize(VRDeviceProviderClient* client) override;
 
   bool Initialized() override;
 
@@ -49,13 +41,7 @@
   mojo::Remote<device::mojom::SensorProvider> sensor_provider_;
 
   std::unique_ptr<VROrientationDevice> device_;
-
-  base::RepeatingCallback<void(mojom::XRDeviceId,
-                               mojom::VRDisplayInfoPtr,
-                               mojom::XRDeviceDataPtr,
-                               mojo::PendingRemote<mojom::XRRuntime>)>
-      add_device_callback_;
-  base::OnceClosure initialized_callback_;
+  VRDeviceProviderClient* client_ = nullptr;
 };
 
 }  // namespace device
diff --git a/device/vr/orientation/orientation_device_provider_unittest.cc b/device/vr/orientation/orientation_device_provider_unittest.cc
index cb14e07..cb1f37d 100644
--- a/device/vr/orientation/orientation_device_provider_unittest.cc
+++ b/device/vr/orientation/orientation_device_provider_unittest.cc
@@ -94,58 +94,6 @@
     return init_params;
   }
 
-  base::RepeatingCallback<void(device::mojom::XRDeviceId,
-                               mojom::VRDisplayInfoPtr,
-                               mojom::XRDeviceDataPtr,
-                               mojo::PendingRemote<mojom::XRRuntime> device)>
-  DeviceAndIdCallbackFailIfCalled() {
-    return base::BindRepeating(
-        [](device::mojom::XRDeviceId id, mojom::VRDisplayInfoPtr,
-           mojom::XRDeviceDataPtr,
-           mojo::PendingRemote<mojom::XRRuntime> device) { FAIL(); });
-  }
-
-  base::RepeatingCallback<void(device::mojom::XRDeviceId)>
-  DeviceIdCallbackFailIfCalled() {
-    return base::BindRepeating([](device::mojom::XRDeviceId id) { FAIL(); });
-  }
-
-  base::RepeatingCallback<void(device::mojom::XRDeviceId,
-                               mojom::VRDisplayInfoPtr,
-                               mojom::XRDeviceDataPtr,
-                               mojo::PendingRemote<mojom::XRRuntime> device)>
-  DeviceAndIdCallbackMustBeCalled(base::RunLoop* loop) {
-    return base::BindRepeating(
-        [](base::OnceClosure quit_closure, device::mojom::XRDeviceId id,
-           mojom::VRDisplayInfoPtr info, mojom::XRDeviceDataPtr data,
-           mojo::PendingRemote<mojom::XRRuntime> device) {
-          ASSERT_TRUE(device);
-          ASSERT_TRUE(info);
-          ASSERT_TRUE(data);
-          std::move(quit_closure).Run();
-        },
-        loop->QuitClosure());
-  }
-
-  base::RepeatingCallback<void(device::mojom::XRDeviceId)>
-  DeviceIdCallbackMustBeCalled(base::RunLoop* loop) {
-    return base::BindRepeating(
-        [](base::OnceClosure quit_closure, device::mojom::XRDeviceId id) {
-          std::move(quit_closure).Run();
-        },
-        loop->QuitClosure());
-  }
-
-  base::OnceClosure ClosureFailIfCalled() {
-    return base::BindOnce([]() { FAIL(); });
-  }
-
-  base::OnceClosure ClosureMustBeCalled(base::RunLoop* loop) {
-    return base::BindOnce(
-        [](base::OnceClosure quit_closure) { std::move(quit_closure).Run(); },
-        loop->QuitClosure());
-  }
-
   // Needed for MakeRequest to work.
   base::test::TaskEnvironment task_environment_;
 
@@ -161,6 +109,56 @@
   mojo::Remote<mojom::SensorClient> sensor_client_;
 };
 
+class MockOrientationDeviceProviderClient : public VRDeviceProviderClient {
+ public:
+  MockOrientationDeviceProviderClient(base::RunLoop* wait_for_device,
+                                      base::RunLoop* wait_for_init)
+      : wait_for_device_(wait_for_device), wait_for_init_(wait_for_init) {}
+  ~MockOrientationDeviceProviderClient() override = default;
+  void AddRuntime(
+      device::mojom::XRDeviceId id,
+      device::mojom::VRDisplayInfoPtr info,
+      device::mojom::XRDeviceDataPtr device_data,
+      mojo::PendingRemote<device::mojom::XRRuntime> runtime) override {
+    if (wait_for_device_) {
+      ASSERT_TRUE(info);
+      ASSERT_TRUE(device_data);
+      ASSERT_TRUE(runtime);
+      wait_for_device_->Quit();
+      return;
+    }
+
+    // If we were created without a wait_for_device runloop, then the test
+    // is not expecting us to be called.
+    FAIL();
+  }
+
+  void RemoveRuntime(device::mojom::XRDeviceId id) override {
+    // The only devices that actually create an Orientation device cannot
+    // physically remove the orientation sensors, so this should never be
+    // called.
+    FAIL();
+  }
+
+  void OnProviderInitialized() override {
+    if (!wait_for_init_) {
+      FAIL();
+    }
+
+    wait_for_init_->Quit();
+  }
+
+  device::XrFrameSinkClientFactory GetXrFrameSinkClientFactory() override {
+    ADD_FAILURE();
+
+    return base::BindRepeating(&FrameSinkClientFactory);
+  }
+
+ private:
+  base::RunLoop* wait_for_device_ = nullptr;
+  base::RunLoop* wait_for_init_ = nullptr;
+};
+
 TEST_F(VROrientationDeviceProviderTest, InitializationTest) {
   // Check that without running anything, the provider will not be initialized.
   EXPECT_FALSE(provider_->Initialized());
@@ -170,10 +168,9 @@
   base::RunLoop wait_for_device;
   base::RunLoop wait_for_init;
 
-  provider_->Initialize(DeviceAndIdCallbackMustBeCalled(&wait_for_device),
-                        DeviceIdCallbackFailIfCalled(),
-                        ClosureMustBeCalled(&wait_for_init),
-                        base::BindRepeating(&FrameSinkClientFactory));
+  MockOrientationDeviceProviderClient client(&wait_for_device, &wait_for_init);
+
+  provider_->Initialize(&client);
 
   InitializeDevice(FakeInitParams());
 
@@ -186,10 +183,8 @@
 TEST_F(VROrientationDeviceProviderTest, InitializationCallbackFailureTest) {
   base::RunLoop wait_for_init;
 
-  provider_->Initialize(DeviceAndIdCallbackFailIfCalled(),
-                        DeviceIdCallbackFailIfCalled(),
-                        ClosureMustBeCalled(&wait_for_init),
-                        base::BindRepeating(&FrameSinkClientFactory));
+  MockOrientationDeviceProviderClient client(nullptr, &wait_for_init);
+  provider_->Initialize(&client);
 
   InitializeDevice(nullptr);
 
diff --git a/device/vr/public/cpp/vr_device_provider.cc b/device/vr/public/cpp/vr_device_provider.cc
index 9919bad..d938f9ab 100644
--- a/device/vr/public/cpp/vr_device_provider.cc
+++ b/device/vr/public/cpp/vr_device_provider.cc
@@ -8,4 +8,7 @@
 
 VRDeviceProvider::VRDeviceProvider() = default;
 VRDeviceProvider::~VRDeviceProvider() = default;
+
+VRDeviceProviderClient::VRDeviceProviderClient() = default;
+VRDeviceProviderClient::~VRDeviceProviderClient() = default;
 }  // namespace device
diff --git a/device/vr/public/cpp/vr_device_provider.h b/device/vr/public/cpp/vr_device_provider.h
index 0725439..1b7a0ab7 100644
--- a/device/vr/public/cpp/vr_device_provider.h
+++ b/device/vr/public/cpp/vr_device_provider.h
@@ -13,22 +13,30 @@
 
 namespace device {
 
+class COMPONENT_EXPORT(VR_PUBLIC_CPP) VRDeviceProviderClient {
+ public:
+  VRDeviceProviderClient();
+  virtual ~VRDeviceProviderClient();
+
+  // TODO(crbug.com/1090029): Wrap XRDeviceId + VRDisplayInfo into XRDeviceData
+  virtual void AddRuntime(
+      device::mojom::XRDeviceId id,
+      device::mojom::VRDisplayInfoPtr info,
+      device::mojom::XRDeviceDataPtr device_data,
+      mojo::PendingRemote<device::mojom::XRRuntime> runtime) = 0;
+  virtual void RemoveRuntime(device::mojom::XRDeviceId id) = 0;
+  virtual void OnProviderInitialized() = 0;
+  virtual XrFrameSinkClientFactory GetXrFrameSinkClientFactory() = 0;
+};
+
 class COMPONENT_EXPORT(VR_PUBLIC_CPP) VRDeviceProvider {
  public:
   VRDeviceProvider();
   virtual ~VRDeviceProvider();
 
   // If the VR API requires initialization that should happen here.
-  virtual void Initialize(
-      base::RepeatingCallback<void(mojom::XRDeviceId id,
-                                   mojom::VRDisplayInfoPtr,
-                                   mojom::XRDeviceDataPtr,
-                                   mojo::PendingRemote<mojom::XRRuntime>)>
-          add_device_callback,
-      base::RepeatingCallback<void(mojom::XRDeviceId id)>
-          remove_device_callback,
-      base::OnceClosure initialization_complete,
-      XrFrameSinkClientFactory xr_frame_sink_client_factory) = 0;
+  // Note that the client must be guaranteed to outlive the device provider.
+  virtual void Initialize(VRDeviceProviderClient* client) = 0;
 
   // Returns true if initialization is complete.
   virtual bool Initialized() = 0;
diff --git a/device/vr/test/fake_vr_device_provider.cc b/device/vr/test/fake_vr_device_provider.cc
index 1888b724..c4a7f65 100644
--- a/device/vr/test/fake_vr_device_provider.cc
+++ b/device/vr/test/fake_vr_device_provider.cc
@@ -17,9 +17,9 @@
   VRDeviceBase* device_base = static_cast<VRDeviceBase*>(device.get());
   devices_.push_back(std::move(device));
   if (initialized_)
-    add_device_callback_.Run(
-        device_base->GetId(), device_base->GetVRDisplayInfo(),
-        device_base->GetDeviceData(), device_base->BindXRRuntime());
+    client_->AddRuntime(device_base->GetId(), device_base->GetVRDisplayInfo(),
+                        device_base->GetDeviceData(),
+                        device_base->BindXRRuntime());
 }
 
 void FakeVRDeviceProvider::RemoveDevice(mojom::XRDeviceId device_id) {
@@ -29,30 +29,21 @@
         return static_cast<VRDeviceBase*>(device.get())->GetId() == device_id;
       });
   if (initialized_)
-    remove_device_callback_.Run(device_id);
+    client_->RemoveRuntime(device_id);
   devices_.erase(it);
 }
 
-void FakeVRDeviceProvider::Initialize(
-    base::RepeatingCallback<void(mojom::XRDeviceId,
-                                 mojom::VRDisplayInfoPtr,
-                                 mojom::XRDeviceDataPtr,
-                                 mojo::PendingRemote<mojom::XRRuntime>)>
-        add_device_callback,
-    base::RepeatingCallback<void(mojom::XRDeviceId)> remove_device_callback,
-    base::OnceClosure initialization_complete,
-    XrFrameSinkClientFactory xr_frame_sink_client_factory) {
-  add_device_callback_ = std::move(add_device_callback);
-  remove_device_callback_ = std::move(remove_device_callback);
+void FakeVRDeviceProvider::Initialize(VRDeviceProviderClient* client) {
+  client_ = client;
 
   for (std::unique_ptr<VRDeviceBase>& device : devices_) {
     auto* device_base = static_cast<VRDeviceBase*>(device.get());
-    add_device_callback_.Run(
-        device_base->GetId(), device_base->GetVRDisplayInfo(),
-        device_base->GetDeviceData(), device_base->BindXRRuntime());
+    client_->AddRuntime(device_base->GetId(), device_base->GetVRDisplayInfo(),
+                        device_base->GetDeviceData(),
+                        device_base->BindXRRuntime());
   }
   initialized_ = true;
-  std::move(initialization_complete).Run();
+  client_->OnProviderInitialized();
 }
 
 bool FakeVRDeviceProvider::Initialized() {
diff --git a/device/vr/test/fake_vr_device_provider.h b/device/vr/test/fake_vr_device_provider.h
index 8eed663..e456a282 100644
--- a/device/vr/test/fake_vr_device_provider.h
+++ b/device/vr/test/fake_vr_device_provider.h
@@ -29,26 +29,13 @@
   void AddDevice(std::unique_ptr<VRDeviceBase> device);
   void RemoveDevice(mojom::XRDeviceId device_id);
 
-  void Initialize(
-      base::RepeatingCallback<void(mojom::XRDeviceId,
-                                   mojom::VRDisplayInfoPtr,
-                                   mojom::XRDeviceDataPtr,
-                                   mojo::PendingRemote<mojom::XRRuntime>)>
-          add_device_callback,
-      base::RepeatingCallback<void(mojom::XRDeviceId)> remove_device_callback,
-      base::OnceClosure initialization_complete,
-      XrFrameSinkClientFactory xr_frame_sink_client_factory) override;
+  void Initialize(VRDeviceProviderClient* client) override;
   bool Initialized() override;
 
  private:
   std::vector<std::unique_ptr<VRDeviceBase>> devices_;
   bool initialized_;
-  base::RepeatingCallback<void(mojom::XRDeviceId,
-                               mojom::VRDisplayInfoPtr,
-                               mojom::XRDeviceDataPtr,
-                               mojo::PendingRemote<mojom::XRRuntime>)>
-      add_device_callback_;
-  base::RepeatingCallback<void(mojom::XRDeviceId)> remove_device_callback_;
+  VRDeviceProviderClient* client_ = nullptr;
 };
 
 }  // namespace device
diff --git a/gin/gin_features.cc b/gin/gin_features.cc
index ab83bb9..84e42279 100644
--- a/gin/gin_features.cc
+++ b/gin/gin_features.cc
@@ -119,15 +119,10 @@
 const base::Feature kV8SlowHistogramsScriptAblation{
     "V8SlowHistogramsScriptAblation", base::FEATURE_DISABLED_BY_DEFAULT};
 
-// Enables the V8 virtual memory cage.
-const base::Feature kV8VirtualMemoryCage {
-  "V8VirtualMemoryCage",
-#if defined(V8_HEAP_SANDBOX)
-      // The cage is required for the V8 Heap Sandbox.
-      base::FEATURE_ENABLED_BY_DEFAULT
-#else
-      base::FEATURE_DISABLED_BY_DEFAULT
-#endif
-};
+// Enables the experimental V8 sandbox. This is called V8VirtualMemoryCage
+// instead of V8Sandbox for historical reasons.
+// TODO(1218005) remove this once the finch trial has ended.
+const base::Feature kV8VirtualMemoryCage{"V8VirtualMemoryCage",
+                                         base::FEATURE_DISABLED_BY_DEFAULT};
 
 }  // namespace features
diff --git a/gin/v8_initializer.cc b/gin/v8_initializer.cc
index f576fc90..63992eb 100644
--- a/gin/v8_initializer.cc
+++ b/gin/v8_initializer.cc
@@ -352,29 +352,40 @@
   // of the virtual memory cage, already use V8's random number generator.
   v8::V8::SetEntropySource(&GenerateEntropy);
 
-#if defined(V8_VIRTUAL_MEMORY_CAGE)
-  static_assert(ARCH_CPU_64_BITS,
-                "V8 virtual memory cage can only work in 64-bit builds");
-  // For now, creating the virtual memory cage is optional, and we only do it
-  // if the correpsonding feature is enabled. In the future, it will be
-  // mandatory when compiling with V8_VIRTUAL_MEMORY_CAGE.
-  bool v8_cage_is_initialized = false;
-  if (base::FeatureList::IsEnabled(features::kV8VirtualMemoryCage)) {
-    v8_cage_is_initialized = v8::V8::InitializeVirtualMemoryCage();
+#if defined(V8_SANDBOX)
+  static_assert(ARCH_CPU_64_BITS, "V8 sandbox can only work in 64-bit builds");
+  // For now, initializing the sandbox is optional, and we only do it if the
+  // correpsonding feature is enabled. In the future, it will be mandatory when
+  // compiling with V8_SANDBOX.
+  // However, if V8 uses sandboxed pointers, then the sandbox must be
+  // initialized as sandboxed pointers are simply offsets inside the sandbox.
+#if defined(V8_SANDBOXED_POINTERS)
+  bool must_initialize_sandbox = true;
+#else
+  bool must_initialize_sandbox = false;
+#endif
 
-    // Record the size of the virtual memory cage, in GB. The size will always
-    // be a power of two, so we use a sparse histogram to capture it.
-    // If the initialization failed, this API will return zero.
-    // The main reason for capturing this histogram here instead of having V8
-    // do it is that there are no Isolates available yet, which are required
-    // for recording histograms in V8.
-    size_t size = v8::V8::GetVirtualMemoryCageSizeInBytes();
+  bool v8_sandbox_is_initialized = false;
+  if (must_initialize_sandbox ||
+      base::FeatureList::IsEnabled(features::kV8VirtualMemoryCage)) {
+    v8_sandbox_is_initialized = v8::V8::InitializeSandbox();
+    CHECK(!must_initialize_sandbox || v8_sandbox_is_initialized);
+
+    // Record the size of the sandbox, in GB. The size will always be a power
+    // of two, so we use a sparse histogram to capture it. If the
+    // initialization failed, this API will return zero. The main reason for
+    // capturing this histogram here instead of having V8 do it is that there
+    // are no Isolates available yet, which are required for recording
+    // histograms in V8.
+    size_t size = v8::V8::GetSandboxSizeInBytes();
     int sizeInGB = size >> 30;
     DCHECK(base::bits::IsPowerOfTwo(size));
     DCHECK(size == 0 || sizeInGB > 0);
+    // This uses the term "cage" instead of "sandbox" for historical reasons.
+    // TODO(1218005) remove this once the finch trial has ended.
     base::UmaHistogramSparse("V8.VirtualMemoryCageSizeGB", sizeInGB);
   }
-#endif
+#endif  // V8_SANDBOX
 
   SetFlags(mode, js_command_line_flags);
 
@@ -390,27 +401,28 @@
 
   v8_is_initialized = true;
 
-#if defined(V8_VIRTUAL_MEMORY_CAGE)
-  if (v8_cage_is_initialized) {
+#if defined(V8_SANDBOX)
+  if (v8_sandbox_is_initialized) {
     // These values are persisted to logs. Entries should not be renumbered and
     // numeric values should never be reused. This should match enum
     // V8VirtualMemoryCageMode in \tools\metrics\histograms\enums.xml
+    // This uses the term "cage" instead of "sandbox" for historical reasons.
+    // TODO(1218005) remove this once the finch trial has ended.
     enum class VirtualMemoryCageMode {
       kSecure = 0,
       kInsecure = 1,
       kMaxValue = kInsecure,
     };
     base::UmaHistogramEnumeration("V8.VirtualMemoryCageMode",
-                                  v8::V8::IsUsingSecureVirtualMemoryCage()
+                                  v8::V8::IsSandboxConfiguredSecurely()
                                       ? VirtualMemoryCageMode::kSecure
                                       : VirtualMemoryCageMode::kInsecure);
 
-    // When the virtual memory cage is enabled, ArrayBuffers must be located
-    // inside the cage. To achieve that, PA's ConfigurablePool is created inside
-    // the cage and Blink will create the ArrayBuffer partition inside that
-    // Pool if it is enabled.
-    v8::PageAllocator* cage_page_allocator =
-        v8::V8::GetVirtualMemoryCagePageAllocator();
+    // When the sandbox is enabled, ArrayBuffers must be allocated inside of
+    // it. To achieve that, PA's ConfigurablePool is created inside the sandbox
+    // and Blink then creates the ArrayBuffer partition in that Pool.
+    v8::VirtualAddressSpace* sandbox_address_space =
+        v8::V8::GetSandboxAddressSpace();
     const size_t max_pool_size =
         base::internal::PartitionAddressSpace::ConfigurablePoolMaxSize();
     const size_t min_pool_size =
@@ -421,7 +433,7 @@
     // virtual memory is expensive on these OSes.
     if (base::win::GetVersion() < base::win::Version::WIN8_1) {
       // The size chosen here should be synchronized with the size of the
-      // virtual memory reservation for the V8 cage on these platforms.
+      // virtual memory reservation for the V8 sandbox on these platforms.
       // Currently, that is 8GB, of which 4GB are used for V8's pointer
       // compression region.
       // TODO(saelo) give this constant a proper name and maybe move it
@@ -436,19 +448,19 @@
     // the size on failure until it succeeds.
     void* pool_base = nullptr;
     while (!pool_base && pool_size >= min_pool_size) {
-      pool_base = cage_page_allocator->AllocatePages(
-          nullptr, pool_size, pool_size, v8::PageAllocator::kNoAccess);
+      pool_base = reinterpret_cast<void*>(sandbox_address_space->AllocatePages(
+          0, pool_size, pool_size, v8::PagePermissions::kNoAccess));
       if (!pool_base) {
         pool_size /= 2;
       }
     }
-    // The V8 cage is guaranteed to be large enough to host the pool.
+    // The V8 sandbox is guaranteed to be large enough to host the pool.
     CHECK(pool_base);
     base::internal::PartitionAddressSpace::InitConfigurablePool(pool_base,
                                                                 pool_size);
     // TODO(saelo) maybe record the size of the Pool into UMA.
   }
-#endif
+#endif  // V8_SANDBOX
 }
 
 // static
diff --git a/gpu/vulkan/generate_bindings.py b/gpu/vulkan/generate_bindings.py
index 9c6b23d..f62f571 100755
--- a/gpu/vulkan/generate_bindings.py
+++ b/gpu/vulkan/generate_bindings.py
@@ -345,7 +345,14 @@
       pdecl += text + tail
     n = len(params)
 
-    callstat = 'return gpu::GetVulkanFunctionPointers()->%s(' % func
+    callstat = ''
+    if (func == 'vkQueueSubmit' or func == 'vkQueueWaitIdle'
+        or func == 'vkQueuePresentKHR'):
+        callstat = '''base::AutoLockMaybe auto_lock
+        (gpu::GetVulkanFunctionPointers()->per_queue_lock_map[queue].get());
+        \n'''
+
+    callstat += 'return gpu::GetVulkanFunctionPointers()->%s(' % func
     paramdecl = '('
     if n > 0:
       paramnames = (''.join(t for t in p.itertext())
@@ -376,7 +383,9 @@
 
 #include "base/compiler_specific.h"
 #include "base/component_export.h"
+#include "base/containers/flat_map.h"
 #include "base/native_library.h"
+#include "base/synchronization/lock.h"
 #include "build/build_config.h"
 #include "ui/gfx/extension_set.h"
 
@@ -431,6 +440,12 @@
 
   base::NativeLibrary vulkan_loader_library = nullptr;
 
+  // This is used to allow thread safe access to a given vulkan queue when
+  // multiple gpu threads are accessing it. Note that this map will be only
+  // accessed by multiple gpu threads concurrently to read the data, so it
+  // should be thread safe to use this map by multiple threads.
+  base::flat_map<VkQueue, std::unique_ptr<base::Lock>> per_queue_lock_map;
+
   template<typename T>
   class VulkanFunction;
   template <typename R, typename ...Args>
diff --git a/gpu/vulkan/vulkan_device_queue.cc b/gpu/vulkan/vulkan_device_queue.cc
index 19b8242..663cf99 100644
--- a/gpu/vulkan/vulkan_device_queue.cc
+++ b/gpu/vulkan/vulkan_device_queue.cc
@@ -392,6 +392,20 @@
     VkQueue vk_queue,
     uint32_t vk_queue_index,
     gfx::ExtensionSet enabled_extensions) {
+  // Currently VulkanDeviceQueue for drdc thread(aka CompositorGpuThread) uses
+  // the same vulkan queue as the gpu main thread. Now since both gpu main and
+  // drdc threads would be accessing/submitting work to the same queue, all the
+  // queue access should be made thread safe. This is done by using locks. This
+  // lock is per |vk_queue|. Note that we are intentionally overwriting a
+  // previous lock if any.
+  // Since the map itself would be accessed by multiple gpu threads, we need to
+  // ensure that the access are thread safe. Here the locks are created and
+  // written into the map only when drdc thread is initialized which happens
+  // during GpuServiceImpl init. At this point none of the gpu threads would be
+  // doing read access until GpuServiceImpl init completed. Hence its safe to
+  // access map here.
+  GetVulkanFunctionPointers()->per_queue_lock_map[vk_queue] =
+      std::make_unique<base::Lock>();
   return InitCommon(vk_physical_device, vk_device, vk_queue, vk_queue_index,
                     enabled_extensions);
 }
@@ -410,6 +424,15 @@
   if (VK_NULL_HANDLE != owned_vk_device_) {
     vkDestroyDevice(owned_vk_device_, nullptr);
     owned_vk_device_ = VK_NULL_HANDLE;
+
+    // Clear all the entries from this map since the device and hence all the
+    // generated queue(and their corresponding lock) from this device is
+    // destroyed.
+    // This happens when VulkanDeviceQueue is destroyed on gpu main thread
+    // during GpuServiceImpl destruction which happens after CompositorGpuThread
+    // is destroyed. Hence CompositorGpuThread would not be accessing the map at
+    // this point and its thread safe to delete map entries here.
+    GetVulkanFunctionPointers()->per_queue_lock_map.clear();
   }
   vk_device_ = VK_NULL_HANDLE;
   vk_queue_ = VK_NULL_HANDLE;
diff --git a/gpu/vulkan/vulkan_function_pointers.h b/gpu/vulkan/vulkan_function_pointers.h
index 79716f2..7df1278 100644
--- a/gpu/vulkan/vulkan_function_pointers.h
+++ b/gpu/vulkan/vulkan_function_pointers.h
@@ -15,7 +15,9 @@
 
 #include "base/compiler_specific.h"
 #include "base/component_export.h"
+#include "base/containers/flat_map.h"
 #include "base/native_library.h"
+#include "base/synchronization/lock.h"
 #include "build/build_config.h"
 #include "ui/gfx/extension_set.h"
 
@@ -69,6 +71,12 @@
 
   base::NativeLibrary vulkan_loader_library = nullptr;
 
+  // This is used to allow thread safe access to a given vulkan queue when
+  // multiple gpu threads are accessing it. Note that this map will be only
+  // accessed by multiple gpu threads concurrently to read the data, so it
+  // should be thread safe to use this map by multiple threads.
+  base::flat_map<VkQueue, std::unique_ptr<base::Lock>> per_queue_lock_map;
+
   template <typename T>
   class VulkanFunction;
   template <typename R, typename... Args>
@@ -959,10 +967,16 @@
                                      uint32_t submitCount,
                                      const VkSubmitInfo* pSubmits,
                                      VkFence fence) {
+  base::AutoLockMaybe auto_lock(
+      gpu::GetVulkanFunctionPointers()->per_queue_lock_map[queue].get());
+
   return gpu::GetVulkanFunctionPointers()->vkQueueSubmit(queue, submitCount,
                                                          pSubmits, fence);
 }
 ALWAYS_INLINE VkResult vkQueueWaitIdle(VkQueue queue) {
+  base::AutoLockMaybe auto_lock(
+      gpu::GetVulkanFunctionPointers()->per_queue_lock_map[queue].get());
+
   return gpu::GetVulkanFunctionPointers()->vkQueueWaitIdle(queue);
 }
 ALWAYS_INLINE VkResult vkResetCommandBuffer(VkCommandBuffer commandBuffer,
@@ -1148,6 +1162,9 @@
 }
 ALWAYS_INLINE VkResult vkQueuePresentKHR(VkQueue queue,
                                          const VkPresentInfoKHR* pPresentInfo) {
+  base::AutoLockMaybe auto_lock(
+      gpu::GetVulkanFunctionPointers()->per_queue_lock_map[queue].get());
+
   return gpu::GetVulkanFunctionPointers()->vkQueuePresentKHR(queue,
                                                              pPresentInfo);
 }
diff --git a/gpu/vulkan/vulkan_util.cc b/gpu/vulkan/vulkan_util.cc
index eaf2533a..cd9961b 100644
--- a/gpu/vulkan/vulkan_util.cc
+++ b/gpu/vulkan/vulkan_util.cc
@@ -11,6 +11,7 @@
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
+#include "base/trace_event/trace_event.h"
 #include "build/build_config.h"
 #include "gpu/config/gpu_info.h"  // nogncheck
 #include "gpu/config/vulkan_info.h"
@@ -32,6 +33,7 @@
 #define GL_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_EXT 0x9531
 
 namespace gpu {
+
 namespace {
 
 #if defined(OS_ANDROID)
@@ -147,6 +149,25 @@
                                    pCreateInfos, pAllocator, pPipelines);
 }
 
+VkResult VulkanQueueSubmitHook(VkQueue queue,
+                               uint32_t submitCount,
+                               const VkSubmitInfo* pSubmits,
+                               VkFence fence) {
+  TRACE_EVENT0("gpu", "VulkanQueueSubmitHook");
+  return vkQueueSubmit(queue, submitCount, pSubmits, fence);
+}
+
+VkResult VulkanQueueWaitIdleHook(VkQueue queue) {
+  TRACE_EVENT0("gpu", "VulkanQueueWaitIdleHook");
+  return vkQueueWaitIdle(queue);
+}
+
+VkResult VulkanQueuePresentKHRHook(VkQueue queue,
+                                   const VkPresentInfoKHR* pPresentInfo) {
+  TRACE_EVENT0("gpu", "VulkanQueuePresentKHRHook");
+  return vkQueuePresentKHR(queue, pPresentInfo);
+}
+
 bool CheckVulkanCompabilities(const VulkanInfo& vulkan_info,
                               const GPUInfo& gpu_info,
                               std::string enable_by_device_name) {
diff --git a/gpu/vulkan/vulkan_util.h b/gpu/vulkan/vulkan_util.h
index c2be5c9..f7f8b8c 100644
--- a/gpu/vulkan/vulkan_util.h
+++ b/gpu/vulkan/vulkan_util.h
@@ -87,6 +87,23 @@
                             const VkAllocationCallbacks* pAllocator,
                             VkPipeline* pPipelines);
 
+// Below vulkanQueue*Hook methods are used to ensure that Skia calls the correct
+// version of those methods which are made thread safe by using locks. See
+// vulkan_function_pointers.h vkQueue* method references for more details.
+COMPONENT_EXPORT(VULKAN)
+VKAPI_ATTR VkResult VKAPI_CALL
+VulkanQueueSubmitHook(VkQueue queue,
+                      uint32_t submitCount,
+                      const VkSubmitInfo* pSubmits,
+                      VkFence fence);
+
+COMPONENT_EXPORT(VULKAN)
+VKAPI_ATTR VkResult VKAPI_CALL VulkanQueueWaitIdleHook(VkQueue queue);
+
+COMPONENT_EXPORT(VULKAN)
+VKAPI_ATTR VkResult VKAPI_CALL
+VulkanQueuePresentKHRHook(VkQueue queue, const VkPresentInfoKHR* pPresentInfo);
+
 COMPONENT_EXPORT(VULKAN)
 bool CheckVulkanCompabilities(const VulkanInfo& vulkan_info,
                               const GPUInfo& gpu_info,
diff --git a/ios/chrome/app/memory_monitor.mm b/ios/chrome/app/memory_monitor.mm
index fec4714..af110b1 100644
--- a/ios/chrome/app/memory_monitor.mm
+++ b/ios/chrome/app/memory_monitor.mm
@@ -35,7 +35,6 @@
                                                 base::BlockingType::WILL_BLOCK);
   const int free_memory =
       static_cast<int>(base::SysInfo::AmountOfAvailablePhysicalMemory() / 1024);
-  crash_keys::SetCurrentFreeMemoryInKB(free_memory);
 
   NSURL* fileURL = [[NSURL alloc] initFileURLWithPath:NSHomeDirectory()];
   NSDictionary* results = [fileURL resourceValuesForKeys:@[
@@ -48,9 +47,14 @@
         results[NSURLVolumeAvailableCapacityForImportantUsageKey];
     free_disk_space_kilobytes = [available_bytes integerValue] / 1024;
   }
-  crash_keys::SetCurrentFreeDiskInKB(free_disk_space_kilobytes);
-  [[PreviousSessionInfo sharedInstance]
-      updateAvailableDeviceStorage:(NSInteger)free_disk_space_kilobytes];
+
+  // As a workaround to crbug.com/1247282, dispatch back to the main thread.
+  dispatch_async(dispatch_get_main_queue(), ^{
+    crash_keys::SetCurrentFreeMemoryInKB(free_memory);
+    crash_keys::SetCurrentFreeDiskInKB(free_disk_space_kilobytes);
+    [[PreviousSessionInfo sharedInstance]
+        updateAvailableDeviceStorage:(NSInteger)free_disk_space_kilobytes];
+  });
 }
 
 // Invokes |UpdateMemoryValues| and schedules itself to be called after
diff --git a/ios/chrome/browser/omaha/omaha_service.mm b/ios/chrome/browser/omaha/omaha_service.mm
index 33b1f58..281c550 100644
--- a/ios/chrome/browser/omaha/omaha_service.mm
+++ b/ios/chrome/browser/omaha/omaha_service.mm
@@ -710,21 +710,24 @@
 }
 
 void OmahaService::PersistStates() {
-  NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
+  // As a workaround to crbug.com/1247282, dispatch back to the main thread.
+  dispatch_async(dispatch_get_main_queue(), ^{
+    NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
 
-  [defaults setDouble:next_tries_time_.ToCFAbsoluteTime()
-               forKey:kNextTriesTimesKey];
-  [defaults setDouble:current_ping_time_.ToCFAbsoluteTime()
-               forKey:kCurrentPingKey];
-  [defaults setDouble:last_sent_time_.ToCFAbsoluteTime()
-               forKey:kLastSentTimeKey];
-  [defaults setInteger:number_of_tries_ forKey:kNumberTriesKey];
-  [defaults setObject:base::SysUTF8ToNSString(last_sent_version_.GetString())
-               forKey:kLastSentVersionKey];
-  [defaults setInteger:last_server_date_ forKey:kLastServerDateKey];
+    [defaults setDouble:next_tries_time_.ToCFAbsoluteTime()
+                 forKey:kNextTriesTimesKey];
+    [defaults setDouble:current_ping_time_.ToCFAbsoluteTime()
+                 forKey:kCurrentPingKey];
+    [defaults setDouble:last_sent_time_.ToCFAbsoluteTime()
+                 forKey:kLastSentTimeKey];
+    [defaults setInteger:number_of_tries_ forKey:kNumberTriesKey];
+    [defaults setObject:base::SysUTF8ToNSString(last_sent_version_.GetString())
+                 forKey:kLastSentVersionKey];
+    [defaults setInteger:last_server_date_ forKey:kLastServerDateKey];
 
-  // Save critical state information for usage reporting.
-  [defaults synchronize];
+    // Save critical state information for usage reporting.
+    [defaults synchronize];
+  });
 }
 
 void OmahaService::OnURLLoadComplete(
diff --git a/ios/chrome/browser/omaha/omaha_service_unittest.mm b/ios/chrome/browser/omaha/omaha_service_unittest.mm
index d349e42..8b34c85 100644
--- a/ios/chrome/browser/omaha/omaha_service_unittest.mm
+++ b/ios/chrome/browser/omaha/omaha_service_unittest.mm
@@ -12,6 +12,7 @@
 #include "base/cxx17_backports.h"
 #include "base/run_loop.h"
 #include "base/strings/stringprintf.h"
+#import "base/test/ios/wait_util.h"
 #include "components/metrics/metrics_pref_names.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/version_info/version_info.h"
@@ -701,6 +702,7 @@
   base::Time now = base::Time::Now();
   OmahaService service(false);
   service.StartInternal();
+  base::test::ios::SpinRunLoopWithMinDelay(base::Milliseconds(1));
 
   service.set_upgrade_recommended_callback(base::BindRepeating(
       &OmahaServiceTest::OnNeedUpdate, base::Unretained(this)));
@@ -710,9 +712,11 @@
   service.current_ping_time_ = now + base::Seconds(3);
   service.last_sent_version_ = base::Version(version_string);
   service.PersistStates();
+  base::test::ios::SpinRunLoopWithMinDelay(base::Milliseconds(1));
 
   OmahaService service2(false);
   service2.StartInternal();
+  base::test::ios::SpinRunLoopWithMinDelay(base::Milliseconds(1));
 
   EXPECT_EQ(service.number_of_tries_, 5);
   EXPECT_EQ(service2.last_sent_time_, now - base::Seconds(1));
diff --git a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
index 9ae1fee1..3d2fe12 100644
--- a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
@@ -1067,8 +1067,7 @@
   // WebStateObserver has a webUsage callback.
   if (!active) {
     if (IsSingleNtpEnabled()) {
-      [_ntpCoordinator stop];
-      [_ntpCoordinator disconnect];
+      [self stopNTP];
     } else {
       for (const auto& element : _ntpCoordinatorsForWebStates)
         [element.second stop];
@@ -2773,6 +2772,32 @@
                          reading_list::ADDED_VIA_CURRENT_APP);
 }
 
+#pragma mark - Private SingleNTP feature helper methods
+
+// Checks if there are any WebStates showing an NTP at this time. If not, then
+// deconstructs |ntpCoordinator|.
+- (void)stopNTPIfNeeded {
+  DCHECK(IsSingleNtpEnabled());
+  BOOL activeNTP = NO;
+  WebStateList* webStateList = self.browser->GetWebStateList();
+  for (int i = 0; i < webStateList->count(); i++) {
+    NewTabPageTabHelper* iterNtpHelper =
+        NewTabPageTabHelper::FromWebState(webStateList->GetWebStateAt(i));
+    if (iterNtpHelper->IsActive()) {
+      activeNTP = YES;
+    }
+  }
+  if (!activeNTP) {
+    [self stopNTP];
+  }
+}
+
+- (void)stopNTP {
+  [_ntpCoordinator stop];
+  [_ntpCoordinator disconnect];
+  _ntpCoordinator = nullptr;
+}
+
 #pragma mark - ** Protocol Implementations and Helpers **
 
 #pragma mark - ThumbStripSupporting
@@ -4088,6 +4113,9 @@
   webState->WasHidden();
   webState->SetKeepRenderProcessAlive(false);
 
+  if (IsSingleNtpEnabled()) {
+    [self stopNTPIfNeeded];
+  }
   [self uninstallDelegatesForWebState:webState];
 }
 
@@ -4707,6 +4735,7 @@
     } else {
       // Set to nullptr to save NTP scroll offset before navigation.
       self.ntpCoordinator.webState = nullptr;
+      [self stopNTPIfNeeded];
     }
   } else {
     if (NTPHelper->IsActive()) {
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_updater.h b/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_updater.h
index 02fc22b..4360bdc6 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_updater.h
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_updater.h
@@ -11,7 +11,6 @@
 @class ContentSuggestionsSectionInformation;
 @class ContentSuggestionsViewController;
 @protocol ContentSuggestionsDataSource;
-@protocol SnackbarCommands;
 @protocol SuggestedContent;
 
 // Enum defining the type of a ContentSuggestions.
@@ -37,8 +36,6 @@
 @property(nonatomic, weak)
     ContentSuggestionsViewController* collectionViewController;
 
-@property(nonatomic, weak) id<SnackbarCommands> dispatcher;
-
 // Returns whether the section should use the default, non-card style.
 - (BOOL)shouldUseCustomStyleForSection:(NSInteger)section;
 
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_updater.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_updater.mm
index f448313..d8ecbcbe 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_updater.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_updater.mm
@@ -11,7 +11,6 @@
 #include "components/strings/grit/components_strings.h"
 #import "ios/chrome/browser/ui/collection_view/collection_view_controller.h"
 #import "ios/chrome/browser/ui/collection_view/collection_view_model.h"
-#import "ios/chrome/browser/ui/commands/snackbar_commands.h"
 #import "ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_header_item.h"
 #import "ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_text_item.h"
 #import "ios/chrome/browser/ui/content_suggestions/cells/suggested_content.h"
@@ -105,9 +104,6 @@
   }
 }
 
-NSString* const kContentSuggestionsCollectionUpdaterSnackbarCategory =
-    @"ContentSuggestionsCollectionUpdaterSnackbarCategory";
-
 }  // namespace
 
 @interface ContentSuggestionsCollectionUpdater ()<ContentSuggestionsDataSink>
@@ -134,7 +130,6 @@
 @synthesize promoAdded = _promoAdded;
 @synthesize sectionIdentifiersFromContentSuggestions =
     _sectionIdentifiersFromContentSuggestions;
-@synthesize dispatcher = _dispatcher;
 
 - (instancetype)init {
   self = [super init];
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm
index 4718e8c..87b682c 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_coordinator.mm
@@ -225,9 +225,6 @@
       setDataSource:self.contentSuggestionsMediator];
   self.suggestionsViewController.suggestionCommandHandler = self.ntpMediator;
   self.suggestionsViewController.audience = self;
-  id<SnackbarCommands> dispatcher =
-      static_cast<id<SnackbarCommands>>(self.browser->GetCommandDispatcher());
-  self.suggestionsViewController.dispatcher = dispatcher;
   self.suggestionsViewController.contentSuggestionsEnabled =
       self.contentSuggestionsEnabled;
 
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.h b/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.h
index b406ab0..d9766a8 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.h
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.h
@@ -16,7 +16,6 @@
 @protocol ContentSuggestionsHeaderControlling;
 @protocol ContentSuggestionsMenuProvider;
 @protocol ContentSuggestionsViewControllerAudience;
-@protocol SnackbarCommands;
 @protocol SuggestedContent;
 
 // CollectionViewController to display the suggestions items.
@@ -53,7 +52,6 @@
 @property(nonatomic, weak) id<ContentSuggestionsMenuProvider> menuProvider;
 
 - (void)setDataSource:(id<ContentSuggestionsDataSource>)dataSource;
-- (void)setDispatcher:(id<SnackbarCommands>)dispatcher;
 
 // Removes the entry at |indexPath|, from the collection and its model.
 - (void)dismissEntryAtIndexPath:(NSIndexPath*)indexPath;
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.mm
index ca086bc..e8ddcc6 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.mm
@@ -74,10 +74,6 @@
   self.collectionUpdater.dataSource = dataSource;
 }
 
-- (void)setDispatcher:(id<SnackbarCommands>)dispatcher {
-  self.collectionUpdater.dispatcher = dispatcher;
-}
-
 - (void)dismissEntryAtIndexPath:(NSIndexPath*)indexPath {
   if (!indexPath || ![self.collectionViewModel hasItemAtIndexPath:indexPath]) {
     return;
diff --git a/ios/chrome/browser/ui/follow/BUILD.gn b/ios/chrome/browser/ui/follow/BUILD.gn
new file mode 100644
index 0000000..0e3c605
--- /dev/null
+++ b/ios/chrome/browser/ui/follow/BUILD.gn
@@ -0,0 +1,25 @@
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("utils") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  sources = [
+    "follow_util.h",
+    "follow_util.mm",
+  ]
+  deps = [
+    ":enums",
+    "//ios/chrome/browser/browser_state",
+    "//ios/chrome/browser/signin",
+    "//ios/chrome/browser/ui/ntp:feature_flags",
+    "//ios/web/public",
+    "//url",
+  ]
+}
+
+source_set("enums") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  sources = [ "follow_action_state.h" ]
+  deps = []
+}
diff --git a/ios/chrome/browser/ui/follow/follow_action_state.h b/ios/chrome/browser/ui/follow/follow_action_state.h
new file mode 100644
index 0000000..dcc45ddf
--- /dev/null
+++ b/ios/chrome/browser/ui/follow/follow_action_state.h
@@ -0,0 +1,21 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_FOLLOW_FOLLOW_ACTION_STATE_H_
+#define IOS_CHROME_BROWSER_UI_FOLLOW_FOLLOW_ACTION_STATE_H_
+
+#import <Foundation/Foundation.h>
+
+// The state of the "Follow" action. e.g. The state the Follow button in the
+// Overflow menu.
+typedef NS_ENUM(NSInteger, FollowActionState) {
+  // "Follow" action is hidden.
+  FollowActionStateHidden,
+  // "Follow" action is shown but disabled.
+  FollowActionStateDisabled,
+  // "Follow" action is shown and enabled.
+  FollowActionStateEnabld,
+};
+
+#endif  // IOS_CHROME_BROWSER_UI_FOLLOW_FOLLOW_ACTION_STATE_H_
diff --git a/ios/chrome/browser/ui/follow/follow_util.h b/ios/chrome/browser/ui/follow/follow_util.h
new file mode 100644
index 0000000..b1f48f6
--- /dev/null
+++ b/ios/chrome/browser/ui/follow/follow_util.h
@@ -0,0 +1,20 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_UI_FOLLOW_FOLLOW_UTIL_H_
+#define IOS_CHROME_BROWSER_UI_FOLLOW_FOLLOW_UTIL_H_
+
+#import "ios/chrome/browser/ui/follow/follow_action_state.h"
+
+namespace web {
+class WebState;
+}
+
+class ChromeBrowserState;
+
+// Returns the Follow action state for |webState| and |browserState|.
+FollowActionState GetFollowActionState(web::WebState* webState,
+                                       ChromeBrowserState* browserState);
+
+#endif  // IOS_CHROME_BROWSER_UI_FOLLOW_FOLLOW_UTIL_H_
diff --git a/ios/chrome/browser/ui/follow/follow_util.mm b/ios/chrome/browser/ui/follow/follow_util.mm
new file mode 100644
index 0000000..e362b41
--- /dev/null
+++ b/ios/chrome/browser/ui/follow/follow_util.mm
@@ -0,0 +1,45 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/ui/follow/follow_util.h"
+
+#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#import "ios/chrome/browser/signin/authentication_service.h"
+#import "ios/chrome/browser/signin/authentication_service_factory.h"
+#import "ios/chrome/browser/ui/ntp/new_tab_page_feature.h"
+#include "ios/web/public/web_client.h"
+#import "ios/web/public/web_state.h"
+#include "url/gurl.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+FollowActionState GetFollowActionState(web::WebState* webState,
+                                       ChromeBrowserState* browserState) {
+  // This method should be called only if the feature flag has been enabled.
+  DCHECK(IsWebChannelsEnabled());
+
+  if (!webState) {
+    return FollowActionStateHidden;
+  }
+  const GURL& URL = webState->GetLastCommittedURL();
+  // Show the follow action when:
+  // 1. The page url is valid;
+  // 2. Users are not on NTP or Chrome internal pages.
+  if (URL.is_valid() && !web::GetWebClient()->IsAppSpecificURL(URL)) {
+    AuthenticationService* authenticationService =
+        AuthenticationServiceFactory::GetForBrowserState(browserState);
+    // Enable the follow action when:
+    // 1. Users are not in incognito mode;
+    // 2. Users have signed in.
+    if (!browserState->IsOffTheRecord() &&
+        authenticationService->GetPrimaryIdentity(
+            signin::ConsentLevel::kSignin)) {
+      return FollowActionStateEnabld;
+    }
+    return FollowActionStateDisabled;
+  }
+  return FollowActionStateHidden;
+}
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 59fed81..664274c 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 @@
-9e047a28d10f5b853fe0dfb64fe67d179099152c
\ No newline at end of file
+95a39bd853c4a850d82d434b322533a2cf0590f9
\ 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 63ebf9d..e18454d 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 @@
-aa6edb01f2b107a0938adafd8d8463176f4042ce
\ No newline at end of file
+8a38987169ae6e831b0e4d3ca88e0d73fd9f2fd6
\ 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 683486a..a4038fa 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 @@
-e59d5b4441830b3d9a0b7354e54fab8015e9bb87
\ No newline at end of file
+cb3396a7bd4c91fb3774082f210707b7665756f6
\ 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 f3afa38d..2d9c89d 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 @@
-10f1152b1c97d9b515f624b9d05e1c9a114a3646
\ No newline at end of file
+ae92db71fb0111f45ad7cae7e1e6f8f20d88ad5f
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
index 8143ab6c..6500f2e 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-dba0d452b306e945bb03337b860ca2adaa2a8247
\ No newline at end of file
+5f6ca1c0036a0369cc56e6c9a47268dc2c83eaa6
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
index 89abbda..0da7cee0 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-9fb8cc2a92e61c92e0990275a09d043d27e2fd96
\ No newline at end of file
+024b670b5a6e00d74e56ab72c3f43e118c4638be
\ 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 22fd1f6..06d389d4 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 @@
-54582bcfc5760d57cca499bbc7a2566f8058aa48
\ No newline at end of file
+07589b09e0a517c5b802cb6124c195c01499120c
\ 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 d216e14..cbf3f39 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 @@
-fb14c686c6bb3a097b99b910f312ea1cc16ebf5a
\ No newline at end of file
+449afd79f99e49c767c89cdc901bacd9a549a930
\ 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 0ff8a20..bb01fcc 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 @@
-8d7b0af697109952d9761d1c320aa9b144e88864
\ No newline at end of file
+5bdaec458dab64e911682eda7f4a1db4396939f3
\ 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 912566d..9eb08154 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 @@
-0c626e59c734eecd29969a07dceefa9b6d8caa2c
\ No newline at end of file
+5d35711ca11e6a3157dea16a298372c89441ebb6
\ No newline at end of file
diff --git a/media/BUILD.gn b/media/BUILD.gn
index 81e70ad..732da98 100644
--- a/media/BUILD.gn
+++ b/media/BUILD.gn
@@ -37,6 +37,7 @@
     "ENABLE_HLS_DEMUXER=$enable_hls_demuxer",
     "ENABLE_LIBGAV1_DECODER=$enable_libgav1_decoder",
     "ENABLE_LIBRARY_CDMS=$enable_library_cdms",
+    "ENABLE_LIBAOM=$enable_libaom",
     "ENABLE_LIBVPX=$media_use_libvpx",
     "ENABLE_LOGGING_OVERRIDE=$enable_logging_override",
     "ENABLE_MEDIA_DRM_STORAGE=$enable_media_drm_storage",
diff --git a/media/DEPS b/media/DEPS
index 380bdc8eb..0cd29bc 100644
--- a/media/DEPS
+++ b/media/DEPS
@@ -17,6 +17,7 @@
   "+third_party/re2",
   "+third_party/dav1d",
   "+third_party/ffmpeg",
+  "+third_party/libaom",
   "+third_party/libdrm",
   "+third_party/libgav1",
   "+third_party/libvpx",
diff --git a/media/gpu/v4l2/test/v4l2_ioctl_shim.cc b/media/gpu/v4l2/test/v4l2_ioctl_shim.cc
index 861e22ca..4a535a9 100644
--- a/media/gpu/v4l2/test/v4l2_ioctl_shim.cc
+++ b/media/gpu/v4l2/test/v4l2_ioctl_shim.cc
@@ -49,7 +49,8 @@
         V4L2_REQUEST_CODE_AND_STRING(VIDIOC_STREAMON),
         V4L2_REQUEST_CODE_AND_STRING(VIDIOC_S_EXT_CTRLS),
         V4L2_REQUEST_CODE_AND_STRING(MEDIA_IOC_REQUEST_ALLOC),
-        V4L2_REQUEST_CODE_AND_STRING(MEDIA_REQUEST_IOC_QUEUE)};
+        V4L2_REQUEST_CODE_AND_STRING(MEDIA_REQUEST_IOC_QUEUE),
+        V4L2_REQUEST_CODE_AND_STRING(MEDIA_REQUEST_IOC_REINIT)};
 
 // Finds corresponding defined V4L2 request code name
 // for a given V4L2 request code value.
@@ -251,7 +252,8 @@
 
 template <>
 bool V4L2IoctlShim::Ioctl(int request_code, int arg) const {
-  DCHECK(request_code == static_cast<int>(MEDIA_REQUEST_IOC_QUEUE));
+  DCHECK(request_code == static_cast<int>(MEDIA_REQUEST_IOC_QUEUE) ||
+         request_code == static_cast<int>(MEDIA_REQUEST_IOC_REINIT));
 
   const int ret = ioctl(arg, request_code);
 
@@ -471,8 +473,6 @@
 }
 
 bool V4L2IoctlShim::MediaIocRequestAlloc(int* media_request_fd) const {
-  // TODO(stevecho): need to use the file descriptor representing the request
-  // for MEDIA_REQUEST_IOC_QUEUE() call to queue to the request.
   LOG_ASSERT(media_request_fd != nullptr)
       << "|media_request_fd| check failed.\n";
 
@@ -495,6 +495,15 @@
   return ret;
 }
 
+bool V4L2IoctlShim::MediaRequestIocReinit(
+    const std::unique_ptr<V4L2Queue>& queue) const {
+  int req_fd = queue->media_request_fd();
+
+  const bool ret = Ioctl(MEDIA_REQUEST_IOC_REINIT, req_fd);
+
+  return ret;
+}
+
 bool V4L2IoctlShim::QueryFormat(enum v4l2_buf_type type,
                                 uint32_t fourcc) const {
   struct v4l2_fmtdesc fmtdesc;
diff --git a/media/gpu/v4l2/test/v4l2_ioctl_shim.h b/media/gpu/v4l2/test/v4l2_ioctl_shim.h
index 4ce56c2..cc9dcfc 100644
--- a/media/gpu/v4l2/test/v4l2_ioctl_shim.h
+++ b/media/gpu/v4l2/test/v4l2_ioctl_shim.h
@@ -174,6 +174,10 @@
   bool MediaRequestIocQueue(const std::unique_ptr<V4L2Queue>& queue) const
       WARN_UNUSED_RESULT;
 
+  // Re-initializes the previously allocated request for reuse.
+  bool MediaRequestIocReinit(const std::unique_ptr<V4L2Queue>& queue) const
+      WARN_UNUSED_RESULT;
+
   // Verifies |v4l_fd| supports |compressed_format| for OUTPUT queues
   // and |uncompressed_format| for CAPTURE queues, respectively.
   bool VerifyCapabilities(uint32_t compressed_format,
diff --git a/media/gpu/v4l2/test/vp9_decoder.cc b/media/gpu/v4l2/test/vp9_decoder.cc
index f50e2f19..4e66fadf 100644
--- a/media/gpu/v4l2/test/vp9_decoder.cc
+++ b/media/gpu/v4l2/test/vp9_decoder.cc
@@ -418,6 +418,9 @@
 
   // TODO(stevecho): call RefreshReferenceSlots() once decoded buffer is ready.
 
+  if (!v4l2_ioctl_->MediaRequestIocReinit(OUTPUT_queue_))
+    LOG(ERROR) << "MEDIA_REQUEST_IOC_REINIT failed.";
+
   return Vp9Decoder::kOk;
 }
 
diff --git a/media/media_options.gni b/media/media_options.gni
index 2f3ad54..7462349 100644
--- a/media/media_options.gni
+++ b/media/media_options.gni
@@ -9,6 +9,7 @@
 import("//build/config/features.gni")
 import("//media/gpu/args.gni")
 import("//testing/libfuzzer/fuzzer_test.gni")
+import("//third_party/libaom/options.gni")
 import("//third_party/libgav1/options.gni")
 
 declare_args() {
diff --git a/media/renderers/BUILD.gn b/media/renderers/BUILD.gn
index 00083fe..e240678c 100644
--- a/media/renderers/BUILD.gn
+++ b/media/renderers/BUILD.gn
@@ -76,6 +76,8 @@
       "win/media_foundation_source_wrapper.h",
       "win/media_foundation_stream_wrapper.cc",
       "win/media_foundation_stream_wrapper.h",
+      "win/media_foundation_texture_pool.cc",
+      "win/media_foundation_texture_pool.h",
       "win/media_foundation_video_stream.cc",
       "win/media_foundation_video_stream.h",
     ]
@@ -146,6 +148,7 @@
     sources += [
       "win/media_foundation_renderer_integration_test.cc",
       "win/media_foundation_renderer_unittest.cc",
+      "win/media_foundation_texture_pool_unittest.cc",
     ]
     deps += [ "//media/test:pipeline_integration_test_base" ]
     libs = [
diff --git a/media/renderers/win/media_foundation_renderer.cc b/media/renderers/win/media_foundation_renderer.cc
index 3180b36..f0c8be5 100644
--- a/media/renderers/win/media_foundation_renderer.cc
+++ b/media/renderers/win/media_foundation_renderer.cc
@@ -27,6 +27,7 @@
 #include "media/base/cdm_context.h"
 #include "media/base/media_log.h"
 #include "media/base/timestamp_constants.h"
+#include "media/base/win/dxgi_device_manager.h"
 #include "media/base/win/mf_helpers.h"
 #include "media/base/win/mf_initializer.h"
 
@@ -184,6 +185,30 @@
   if (dxgi_device_manager_) {
     RETURN_IF_FAILED(creation_attributes->SetUnknown(
         MF_MEDIA_ENGINE_DXGI_MANAGER, dxgi_device_manager_.Get()));
+
+    // TODO(crbug.com/1276067): We'll investigate scenarios to see if we can use
+    // the on-screen video window size and not the native video size.
+    if (in_frame_server_mode_) {
+      gfx::Size max_video_size;
+      bool has_video = false;
+      for (auto* stream : media_resource->GetAllStreams()) {
+        if (stream->type() == media::DemuxerStream::VIDEO) {
+          has_video = true;
+          gfx::Size video_size = stream->video_decoder_config().natural_size();
+          if (video_size.height() > max_video_size.height()) {
+            max_video_size.set_height(video_size.height());
+          }
+
+          if (video_size.width() > max_video_size.width()) {
+            max_video_size.set_width(video_size.width());
+          }
+        }
+      }
+
+      if (has_video) {
+        RETURN_IF_FAILED(InitializeTexturePool(max_video_size));
+      }
+    }
   }
 
   RETURN_IF_FAILED(
@@ -492,6 +517,29 @@
   std::move(callback).Run(true);
 }
 
+HRESULT MediaFoundationRenderer::InitializeTexturePool(const gfx::Size& size) {
+  DXGIDeviceScopedHandle dxgi_device_handle(dxgi_device_manager_.Get());
+  ComPtr<ID3D11Device> d3d11_device = dxgi_device_handle.GetDevice();
+
+  if (d3d11_device.Get() == nullptr) {
+    return E_UNEXPECTED;
+  }
+
+  // TODO(crbug.com/1276067): change |size| to instead use the required
+  // size of the output (for example if the video is only 1280x720 instead
+  // of a source frame of 1920x1080 we'd use the 1280x720 texture size).
+  // However we also need to investigate the scenario of WebGL and 360 video
+  // where they need the original frame size instead of the window size due
+  // to later image processing.
+  auto callback = [](std::vector<MediaFoundationFrameInfo> frame_textures,
+                     const gfx::Size& texture_size) {};
+
+  RETURN_IF_FAILED(texture_pool_.Initialize(
+      d3d11_device.Get(), base::BindRepeating(callback), size));
+
+  return S_OK;
+}
+
 HRESULT MediaFoundationRenderer::UpdateVideoStream(const gfx::Rect& rect) {
   ComPtr<IMFMediaEngineEx> mf_media_engine_ex;
   RETURN_IF_FAILED(mf_media_engine_.As(&mf_media_engine_ex));
@@ -734,6 +782,10 @@
     ignore_result(UpdateVideoStream(test_rect));
   }
 
+  if (in_frame_server_mode_) {
+    InitializeTexturePool(native_video_size_);
+  }
+
   renderer_client_->OnVideoNaturalSizeChange(native_video_size_);
 }
 
diff --git a/media/renderers/win/media_foundation_renderer.h b/media/renderers/win/media_foundation_renderer.h
index 64933657..64b20d7 100644
--- a/media/renderers/win/media_foundation_renderer.h
+++ b/media/renderers/win/media_foundation_renderer.h
@@ -29,6 +29,7 @@
 #include "media/renderers/win/media_foundation_protection_manager.h"
 #include "media/renderers/win/media_foundation_renderer_extension.h"
 #include "media/renderers/win/media_foundation_source_wrapper.h"
+#include "media/renderers/win/media_foundation_texture_pool.h"
 
 namespace media {
 
@@ -119,7 +120,7 @@
   HRESULT SetSourceOnMediaEngine();
   HRESULT UpdateVideoStream(const gfx::Rect& rect);
   HRESULT PauseInternal();
-
+  HRESULT InitializeTexturePool(const gfx::Size& size);
   void OnVideoNaturalSizeChange();
 
   // Renderer methods are running in the same sequence.
@@ -139,7 +140,7 @@
   Microsoft::WRL::ComPtr<MediaEngineExtension> mf_media_engine_extension_;
   Microsoft::WRL::ComPtr<MediaFoundationSourceWrapper> mf_source_;
   // This enables MFMediaEngine to use hardware acceleration for video decoding
-  // and vdieo processing.
+  // and video processing.
   Microsoft::WRL::ComPtr<IMFDXGIDeviceManager> dxgi_device_manager_;
 
   // Current duration of the media.
@@ -172,6 +173,16 @@
   Microsoft::WRL::ComPtr<MediaFoundationProtectionManager>
       content_protection_manager_;
 
+  // Texture pool of ID3D11Texture2D for the media engine to draw video frames
+  // when the media engine is in frame server mode instead of Direct
+  // Composition mode.
+  MediaFoundationTexturePool texture_pool_;
+
+  // When in frame server mode we need to manage the DX textures and provide
+  // frames to the renderer.
+  // Disabled until we move
+  bool in_frame_server_mode_ = false;
+
   // NOTE: Weak pointers must be invalidated before all other member variables.
   base::WeakPtrFactory<MediaFoundationRenderer> weak_factory_{this};
 };
diff --git a/media/renderers/win/media_foundation_texture_pool.cc b/media/renderers/win/media_foundation_texture_pool.cc
new file mode 100644
index 0000000..8aeead3
--- /dev/null
+++ b/media/renderers/win/media_foundation_texture_pool.cc
@@ -0,0 +1,130 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <dxgi1_2.h>
+
+#include "media/base/win/mf_helpers.h"
+#include "media/renderers/win/media_foundation_texture_pool.h"
+
+namespace {
+
+using Microsoft::WRL::ComPtr;
+
+// The Texture Count was determined empirically initially having a count of 30
+// and running many different video presentations in frame server mode and
+// recording the number of textures in use and the count never exceeded 2.
+// Therefore for a max of 2 in flight with the 3 being written requires that
+// we allocate 3 textures.
+constexpr int kTexturePoolCount = 3;
+
+}  // namespace
+
+namespace media {
+
+MediaFoundationFrameInfo::MediaFoundationFrameInfo() = default;
+MediaFoundationFrameInfo::~MediaFoundationFrameInfo() = default;
+MediaFoundationFrameInfo::MediaFoundationFrameInfo(
+    MediaFoundationFrameInfo&& other) = default;
+
+MediaFoundationTexturePool::TextureInfo::TextureInfo()
+    : texture_in_use_(false) {}
+MediaFoundationTexturePool::TextureInfo::~TextureInfo() = default;
+MediaFoundationTexturePool::TextureInfo::TextureInfo(const TextureInfo& other) =
+    default;
+MediaFoundationTexturePool::TextureInfo&
+MediaFoundationTexturePool::TextureInfo::operator=(
+    const MediaFoundationTexturePool::TextureInfo& other) = default;
+
+MediaFoundationTexturePool::MediaFoundationTexturePool() = default;
+MediaFoundationTexturePool::~MediaFoundationTexturePool() = default;
+
+// TODO(crbug.com/1278157): The pool should release the textures when the media
+// engine is idling to save resources.
+HRESULT MediaFoundationTexturePool::Initialize(
+    ID3D11Device* device,
+    FramePoolInitializedCallback frame_pool_cb,
+    const gfx::Size& frame_size) {
+  D3D11_TEXTURE2D_DESC desc{
+      static_cast<UINT>(frame_size.width()),
+      static_cast<UINT>(frame_size.height()),
+      1,
+      1,
+      // TODO(crbug.com/1276134): Need to handle higher bit-depths like HDR.
+      DXGI_FORMAT_R8G8B8A8_UNORM,
+      {1, 0},
+      D3D11_USAGE_DEFAULT,
+      D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE,
+      0,
+      D3D11_RESOURCE_MISC_SHARED_NTHANDLE |
+          D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX};
+
+  std::vector<MediaFoundationFrameInfo> frame_infos;
+  bool callback_is_valid = !frame_pool_cb.is_null();
+  if (callback_is_valid) {
+    frame_infos.reserve(kTexturePoolCount);
+  }
+
+  // We can be reinitialized so remove all the previous textures from our
+  // pool.
+  texture_pool_.clear();
+
+  for (int i = 0; i < kTexturePoolCount; ++i) {
+    auto texture_info_element = std::make_unique<TextureInfo>();
+    auto texture_token = base::UnguessableToken::Create();
+
+    ComPtr<ID3D11Texture2D> d3d11_video_frame;
+    RETURN_IF_FAILED(
+        device->CreateTexture2D(&desc, nullptr, &d3d11_video_frame));
+    SetDebugName(d3d11_video_frame.Get(), "Media_MFFrameServerMode_Pool");
+
+    ComPtr<IDXGIResource1> d3d11_video_frame_resource;
+    RETURN_IF_FAILED(d3d11_video_frame.As(&d3d11_video_frame_resource));
+
+    HANDLE shared_texture_handle;
+    RETURN_IF_FAILED(d3d11_video_frame_resource->CreateSharedHandle(
+        nullptr, DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE,
+        nullptr, &shared_texture_handle));
+
+    base::win::ScopedHandle scoped_shared_texture_handle;
+    scoped_shared_texture_handle.Set(shared_texture_handle);
+    shared_texture_handle = nullptr;
+    texture_pool_[texture_token].texture_ = std::move(d3d11_video_frame);
+    texture_pool_[texture_token].texture_in_use_ = false;
+
+    if (callback_is_valid) {
+      MediaFoundationFrameInfo frame_info;
+      frame_info.dxgi_handle = std::move(scoped_shared_texture_handle);
+      frame_info.token = texture_token;
+      frame_infos.emplace_back(std::move(frame_info));
+    }
+  }
+
+  if (callback_is_valid) {
+    frame_pool_cb.Run(std::move(frame_infos), frame_size);
+  }
+
+  return S_OK;
+}
+
+ComPtr<ID3D11Texture2D> MediaFoundationTexturePool::AcquireTexture(
+    base::UnguessableToken* texture_token) {
+  for (auto& texture_item : texture_pool_) {
+    if (!texture_item.second.texture_in_use_) {
+      *texture_token = texture_item.first;
+      texture_item.second.texture_in_use_ = true;
+      return texture_item.second.texture_;
+    }
+  }
+
+  return nullptr;
+}
+
+void MediaFoundationTexturePool::ReleaseTexture(
+    const base::UnguessableToken& texture_token) {
+  if (texture_pool_.count(texture_token) > 0) {
+    texture_pool_.at(texture_token).texture_in_use_ = false;
+  }
+}
+
+}  // namespace media
\ No newline at end of file
diff --git a/media/renderers/win/media_foundation_texture_pool.h b/media/renderers/win/media_foundation_texture_pool.h
new file mode 100644
index 0000000..fe01055
--- /dev/null
+++ b/media/renderers/win/media_foundation_texture_pool.h
@@ -0,0 +1,79 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_RENDERERS_WIN_MEDIA_FOUNDATION_TEXTURE_POOL_H_
+#define MEDIA_RENDERERS_WIN_MEDIA_FOUNDATION_TEXTURE_POOL_H_
+
+#include <d3d11.h>
+#include <wrl/client.h>
+#include <map>
+
+#include "base/callback.h"
+#include "base/containers/flat_map.h"
+#include "base/unguessable_token.h"
+#include "base/win/scoped_handle.h"
+#include "media/base/media_export.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace media {
+
+struct MEDIA_EXPORT MediaFoundationFrameInfo {
+  MediaFoundationFrameInfo();
+  ~MediaFoundationFrameInfo();
+  MediaFoundationFrameInfo(MediaFoundationFrameInfo&& other);
+  base::win::ScopedHandle dxgi_handle;
+  base::UnguessableToken token;
+};
+
+using FramePoolInitializedCallback = base::RepeatingCallback<void(
+    std::vector<MediaFoundationFrameInfo> frame_textures,
+    const gfx::Size& texture_size)>;
+
+// This object will create a pool D3D11Texture2Ds that the video frames in the
+// Media Foundation Media Engine will draw to. By having this pool we don't
+// need to create a D3D11Texture2D and associated Shared Image mailbox for
+// each video frame. To coordinate the Shared Images in the
+// MediaFoundationRendererClient each texture has a |texture_token| to signal
+// which texture is ready to be displayed and which texture is ready for
+// reuse.
+class MEDIA_EXPORT MediaFoundationTexturePool {
+ public:
+  MediaFoundationTexturePool();
+  ~MediaFoundationTexturePool();
+  MediaFoundationTexturePool(const MediaFoundationTexturePool& other) = delete;
+  MediaFoundationTexturePool& operator=(
+      const MediaFoundationTexturePool& other) = delete;
+
+  // Initializes the texture pool with a specific size. Once the textures are
+  // created the callback will be called passing the information about the
+  // textures. The method can be called multiple times, which will release the
+  // previously allocated textures and create a new set of textures on the
+  // device with the frame size.
+  // Any event that changes the frame size will be calling this method to
+  // change the texture size. The callback will eventually call into the Media
+  // Foundation Renderer which will create the Shared Images with the DX shared
+  // handle. Examples of callers are CreateMediaEngine and
+  // OnVideoNaturalSizeChange in the MediaFoundationRenderer
+  HRESULT Initialize(ID3D11Device* device,
+                     FramePoolInitializedCallback frame_pool_cb,
+                     const gfx::Size& frame_size);
+  Microsoft::WRL::ComPtr<ID3D11Texture2D> AcquireTexture(
+      base::UnguessableToken* texture_token);
+  void ReleaseTexture(const base::UnguessableToken& texture_token);
+
+ private:
+  struct TextureInfo {
+    TextureInfo();
+    ~TextureInfo();
+    TextureInfo(const TextureInfo& other);
+    TextureInfo& operator=(const TextureInfo& other);
+
+    Microsoft::WRL::ComPtr<ID3D11Texture2D> texture_;
+    bool texture_in_use_;
+  };
+
+  base::flat_map<base::UnguessableToken, TextureInfo> texture_pool_;
+};
+}  // namespace media
+#endif
\ No newline at end of file
diff --git a/media/renderers/win/media_foundation_texture_pool_unittest.cc b/media/renderers/win/media_foundation_texture_pool_unittest.cc
new file mode 100644
index 0000000..f4b7939
--- /dev/null
+++ b/media/renderers/win/media_foundation_texture_pool_unittest.cc
@@ -0,0 +1,542 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/renderers/win/media_foundation_texture_pool.h"
+
+#include "base/test/mock_callback.h"
+#include "base/test/task_environment.h"
+#include "media/base/mock_filters.h"
+#include "media/base/test_helpers.h"
+#include "media/base/win/test_utils.h"
+
+#include <d3d11.h>
+#include <dxgi1_2.h>
+#include <wrl/client.h>
+
+using Microsoft::WRL::ComPtr;
+
+namespace media {
+class MockD3D11Texture2D;
+
+class MockD3D11Resource final : public IDXGIResource1 {
+ public:
+  MockD3D11Resource() {}
+  HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid,
+                                           void** ppvObject) override;
+  ULONG STDMETHODCALLTYPE AddRef(void) override {
+    return InterlockedIncrement(&refcount_);
+  }
+
+  ULONG STDMETHODCALLTYPE Release(void) override {
+    ULONG refcount = InterlockedDecrement(&refcount_);
+    if (refcount == 0) {
+      refcount_ = 0xBAADF00D;
+      delete this;
+    }
+    return refcount;
+  }
+
+  // IDXGIResource1
+  HRESULT STDMETHODCALLTYPE
+  CreateSubresourceSurface(UINT index, IDXGISurface2** ppSurface) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE
+  CreateSharedHandle(const SECURITY_ATTRIBUTES* pAttributes,
+                     DWORD dwAccess,
+                     LPCWSTR lpName,
+                     HANDLE* pHandle) override;
+
+  // IDXGIResource
+  HRESULT STDMETHODCALLTYPE GetSharedHandle(HANDLE* pSharedHandle) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE GetUsage(DXGI_USAGE* pUsage) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE
+  SetEvictionPriority(UINT eviction_priority) override;
+  HRESULT STDMETHODCALLTYPE
+  GetEvictionPriority(UINT* eviction_priority) override;
+
+  // IDXGIDeviceSubObject
+  HRESULT STDMETHODCALLTYPE GetDevice(REFIID riid, void** ppDevice) override {
+    return E_NOTIMPL;
+  }
+
+  // IDXGIObject
+  HRESULT STDMETHODCALLTYPE GetParent(REFIID riid, void** ppParent) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE GetPrivateData(REFGUID guid,
+                                           UINT* pDataSize,
+                                           void* pData) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE SetPrivateData(REFGUID guid,
+                                           UINT DataSize,
+                                           const void* pData) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE
+  SetPrivateDataInterface(REFGUID guid, const IUnknown* pData) override;
+
+ private:
+  MockD3D11Texture2D* parent_;
+  volatile ULONG refcount_ = 1;
+};
+
+class MockD3D11Texture2D final : public ID3D11Texture2D {
+ private:
+  MockD3D11Texture2D(const D3D11_TEXTURE2D_DESC* texture_description)
+      : resource_(new MockD3D11Resource()) {
+    memcpy(&texture_description_, texture_description,
+           sizeof(D3D11_TEXTURE2D_DESC));
+  }
+
+ public:
+  static HRESULT CreateInstance(const D3D11_TEXTURE2D_DESC* texture_description,
+                                ID3D11Texture2D** texture2D);
+  // IUnknown
+  HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid,
+                                           void** ppvObject) override {
+    if (ppvObject == nullptr) {
+      return E_POINTER;
+    }
+    if (FAILED(QueryInterfaceInternal(riid, ppvObject))) {
+      if (resource_ != nullptr)
+        return resource_->QueryInterface(riid, ppvObject);
+      else
+        return E_NOINTERFACE;
+    }
+
+    AddRef();
+    return S_OK;
+  }
+
+  HRESULT STDMETHODCALLTYPE QueryInterfaceInternal(REFIID riid,
+                                                   void** ppvObject) {
+    if (ppvObject == nullptr) {
+      return E_POINTER;
+    }
+
+    if (riid == IID_ID3D11Texture2D || riid == IID_IUnknown) {
+      *ppvObject = static_cast<ID3D11Texture2D*>(this);
+    } else if (riid == IID_ID3D11Resource) {
+      *ppvObject = static_cast<ID3D11Resource*>(this);
+    } else if (riid == IID_ID3D11DeviceChild) {
+      *ppvObject = static_cast<ID3D11DeviceChild*>(this);
+    } else {
+      return E_NOINTERFACE;
+    }
+
+    AddRef();
+    return S_OK;
+  }
+
+  ULONG STDMETHODCALLTYPE AddRef(void) override {
+    return InterlockedIncrement(&refcount_);
+  }
+
+  ULONG STDMETHODCALLTYPE Release(void) override {
+    ULONG refcount = InterlockedDecrement(&refcount_);
+    if (refcount == 0) {
+      refcount_ = 0xBAADF00D;
+      delete this;
+    }
+    return refcount;
+  }
+
+  // ID3D11Texture2D
+  void STDMETHODCALLTYPE GetDesc(D3D11_TEXTURE2D_DESC* description) override {
+    memset(description, 0, sizeof(D3D11_TEXTURE2D_DESC));
+  }
+
+  // ID3D11Resource
+  void STDMETHODCALLTYPE
+  GetType(D3D11_RESOURCE_DIMENSION* resource_dimension) override {
+    *resource_dimension =
+        D3D11_RESOURCE_DIMENSION::D3D11_RESOURCE_DIMENSION_TEXTURE2D;
+  }
+  void STDMETHODCALLTYPE SetEvictionPriority(UINT eviction_priority) override {
+    eviction_priority_ = eviction_priority;
+  }
+  UINT STDMETHODCALLTYPE GetEvictionPriority() override {
+    return eviction_priority_;
+  }
+
+  // ID3D11DeviceChild
+  void STDMETHODCALLTYPE GetDevice(ID3D11Device** ppDevice) override {
+    device_.CopyTo(ppDevice);
+  }
+  HRESULT STDMETHODCALLTYPE GetPrivateData(REFGUID guid,
+                                           UINT* pDataSize,
+                                           void* pData) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE SetPrivateData(REFGUID guid,
+                                           UINT DataSize,
+                                           const void* pData) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE
+  SetPrivateDataInterface(REFGUID guid, const IUnknown* pData) override {
+    // Our tests aren't checking for this right now
+    return S_OK;
+  }
+
+ private:
+  volatile ULONG refcount_ = 1;
+  UINT eviction_priority_ = 0;
+  D3D11_TEXTURE2D_DESC texture_description_;
+  ComPtr<ID3D11Device> device_;
+  ComPtr<IDXGIResource1> resource_;
+};
+
+class MockD3D11Device : public ID3D11Device {
+ public:
+  HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid,
+                                           void** ppvObject) override {
+    if (ppvObject == nullptr) {
+      return E_POINTER;
+    }
+
+    if (riid == IID_ID3D11Device || riid == IID_IUnknown) {
+      *ppvObject = this;
+      return S_OK;
+    }
+
+    return E_NOINTERFACE;
+  }
+
+  ULONG STDMETHODCALLTYPE AddRef(void) override { return 1; }
+  ULONG STDMETHODCALLTYPE Release(void) override { return 1; }
+
+  HRESULT STDMETHODCALLTYPE
+  CreateBuffer(const D3D11_BUFFER_DESC* pDesc,
+               const D3D11_SUBRESOURCE_DATA* pInitialData,
+               ID3D11Buffer** ppBuffer) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE
+  CreateTexture1D(const D3D11_TEXTURE1D_DESC* pDesc,
+                  const D3D11_SUBRESOURCE_DATA* pInitialData,
+                  ID3D11Texture1D** ppTexture1D) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE
+  CreateTexture2D(const D3D11_TEXTURE2D_DESC* pDesc,
+                  const D3D11_SUBRESOURCE_DATA* pInitialData,
+                  ID3D11Texture2D** ppTexture2D) override;
+  HRESULT STDMETHODCALLTYPE
+  CreateTexture3D(const D3D11_TEXTURE3D_DESC* pDesc,
+                  const D3D11_SUBRESOURCE_DATA* pInitialData,
+                  ID3D11Texture3D** ppTexture3D) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE
+  CreateShaderResourceView(ID3D11Resource* pResource,
+                           const D3D11_SHADER_RESOURCE_VIEW_DESC* pDesc,
+                           ID3D11ShaderResourceView** ppSRView) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE
+  CreateUnorderedAccessView(ID3D11Resource* pResource,
+                            const D3D11_UNORDERED_ACCESS_VIEW_DESC* pDesc,
+                            ID3D11UnorderedAccessView** ppUAView) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE
+  CreateRenderTargetView(ID3D11Resource* pResource,
+                         const D3D11_RENDER_TARGET_VIEW_DESC* pDesc,
+                         ID3D11RenderTargetView** ppRTView) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE
+  CreateDepthStencilView(ID3D11Resource* pResource,
+                         const D3D11_DEPTH_STENCIL_VIEW_DESC* pDesc,
+                         ID3D11DepthStencilView** ppDepthStencilView) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE
+  CreateInputLayout(const D3D11_INPUT_ELEMENT_DESC* pInputElementDescs,
+                    UINT NumElements,
+                    const void* pShaderBytecodeWithInputSignature,
+                    SIZE_T BytecodeLength,
+                    ID3D11InputLayout** ppInputLayout) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE
+  CreateVertexShader(const void* pShaderBytecode,
+                     SIZE_T BytecodeLength,
+                     ID3D11ClassLinkage* pClassLinkage,
+                     ID3D11VertexShader** ppVertexShader) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE
+  CreateGeometryShader(const void* pShaderBytecode,
+                       SIZE_T BytecodeLength,
+                       ID3D11ClassLinkage* pClassLinkage,
+                       ID3D11GeometryShader** ppGeometryShader) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE CreateGeometryShaderWithStreamOutput(
+      const void* pShaderBytecode,
+      SIZE_T BytecodeLength,
+      const D3D11_SO_DECLARATION_ENTRY* pSODeclaration,
+      UINT NumEntries,
+      const UINT* pBufferStrides,
+      UINT NumStrides,
+      UINT RasterizedStream,
+      ID3D11ClassLinkage* pClassLinkage,
+      ID3D11GeometryShader** ppGeometryShader) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE
+  CreatePixelShader(const void* pShaderBytecode,
+                    SIZE_T BytecodeLength,
+                    ID3D11ClassLinkage* pClassLinkage,
+                    ID3D11PixelShader** ppPixelShader) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE
+  CreateHullShader(const void* pShaderBytecode,
+                   SIZE_T BytecodeLength,
+                   ID3D11ClassLinkage* pClassLinkage,
+                   ID3D11HullShader** ppHullShader) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE
+  CreateDomainShader(const void* pShaderBytecode,
+                     SIZE_T BytecodeLength,
+                     ID3D11ClassLinkage* pClassLinkage,
+                     ID3D11DomainShader** ppDomainShader) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE
+  CreateComputeShader(const void* pShaderBytecode,
+                      SIZE_T BytecodeLength,
+                      ID3D11ClassLinkage* pClassLinkage,
+                      ID3D11ComputeShader** ppComputeShader) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE
+  CreateClassLinkage(ID3D11ClassLinkage** ppLinkage) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE
+  CreateBlendState(const D3D11_BLEND_DESC* pBlendStateDesc,
+                   ID3D11BlendState** ppBlendState) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE CreateDepthStencilState(
+      const D3D11_DEPTH_STENCIL_DESC* pDepthStencilDesc,
+      ID3D11DepthStencilState** ppDepthStencilState) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE
+  CreateRasterizerState(const D3D11_RASTERIZER_DESC* pRasterizerDesc,
+                        ID3D11RasterizerState** ppRasterizerState) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE
+  CreateSamplerState(const D3D11_SAMPLER_DESC* pSamplerDesc,
+                     ID3D11SamplerState** ppSamplerState) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE CreateQuery(const D3D11_QUERY_DESC* pQueryDesc,
+                                        ID3D11Query** ppQuery) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE
+  CreatePredicate(const D3D11_QUERY_DESC* pPredicateDesc,
+                  ID3D11Predicate** ppPredicate) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE
+  CreateCounter(const D3D11_COUNTER_DESC* pCounterDesc,
+                ID3D11Counter** ppCounter) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE
+  CreateDeferredContext(UINT ContextFlags,
+                        ID3D11DeviceContext** ppDeferredContext) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE OpenSharedResource(HANDLE hResource,
+                                               REFIID ReturnedInterface,
+                                               void** ppResource) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE CheckFormatSupport(DXGI_FORMAT Format,
+                                               UINT* pFormatSupport) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE
+  CheckMultisampleQualityLevels(DXGI_FORMAT Format,
+                                UINT SampleCount,
+                                UINT* pNumQualityLevels) override {
+    return E_NOTIMPL;
+  }
+  void STDMETHODCALLTYPE
+  CheckCounterInfo(D3D11_COUNTER_INFO* pCounterInfo) override {}
+  HRESULT STDMETHODCALLTYPE CheckCounter(const D3D11_COUNTER_DESC* pDesc,
+                                         D3D11_COUNTER_TYPE* pType,
+                                         UINT* pActiveCounters,
+                                         LPSTR szName,
+                                         UINT* pNameLength,
+                                         LPSTR szUnits,
+                                         UINT* pUnitsLength,
+                                         LPSTR szDescription,
+                                         UINT* pDescriptionLength) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE
+  CheckFeatureSupport(D3D11_FEATURE Feature,
+                      void* pFeatureSupportData,
+                      UINT FeatureSupportDataSize) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE GetPrivateData(REFGUID guid,
+                                           UINT* pDataSize,
+                                           void* pData) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE SetPrivateData(REFGUID guid,
+                                           UINT DataSize,
+                                           const void* pData) override {
+    return E_NOTIMPL;
+  }
+  HRESULT STDMETHODCALLTYPE
+  SetPrivateDataInterface(REFGUID guid, const IUnknown* pData) override {
+    return E_NOTIMPL;
+  }
+  D3D_FEATURE_LEVEL STDMETHODCALLTYPE GetFeatureLevel(void) override {
+    return D3D_FEATURE_LEVEL_11_1;
+  }
+  UINT STDMETHODCALLTYPE GetCreationFlags(void) override { return 0; }
+  HRESULT STDMETHODCALLTYPE GetDeviceRemovedReason(void) override {
+    return E_NOTIMPL;
+  }
+  void STDMETHODCALLTYPE
+  GetImmediateContext(ID3D11DeviceContext** ppImmediateContext) override {}
+  HRESULT STDMETHODCALLTYPE SetExceptionMode(UINT RaiseFlags) override {
+    return E_NOTIMPL;
+  }
+  UINT STDMETHODCALLTYPE GetExceptionMode(void) override { return 0; }
+};
+
+// MockD3D11Resource
+HRESULT STDMETHODCALLTYPE MockD3D11Resource::QueryInterface(REFIID riid,
+                                                            void** ppvObject) {
+  if (ppvObject == nullptr) {
+    return E_POINTER;
+  }
+
+  if (riid == IID_IDXGIResource1) {
+    *ppvObject = static_cast<IDXGIResource1*>(this);
+  } else if (riid == IID_IDXGIResource) {
+    *ppvObject = static_cast<IDXGIResource*>(this);
+  } else if (riid == IID_IDXGIDeviceSubObject) {
+    *ppvObject = static_cast<IDXGIDeviceSubObject*>(this);
+  } else if (riid == IID_IDXGIObject) {
+    *ppvObject = static_cast<IDXGIObject*>(this);
+  } else if (parent_ != nullptr) {
+    return parent_->QueryInterfaceInternal(riid, ppvObject);
+  } else {
+    return E_NOINTERFACE;
+  }
+
+  AddRef();
+  return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE
+MockD3D11Resource::SetEvictionPriority(UINT eviction_priority) {
+  parent_->SetEvictionPriority(eviction_priority);
+  return S_OK;
+}
+HRESULT STDMETHODCALLTYPE
+MockD3D11Resource::GetEvictionPriority(UINT* eviction_priority) {
+  *eviction_priority = parent_->GetEvictionPriority();
+  return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE
+MockD3D11Resource::SetPrivateDataInterface(REFGUID guid,
+                                           const IUnknown* pData) {
+  return parent_->SetPrivateDataInterface(guid, pData);
+}
+
+HRESULT STDMETHODCALLTYPE
+MockD3D11Resource::CreateSharedHandle(const SECURITY_ATTRIBUTES* pAttributes,
+                                      DWORD dwAccess,
+                                      LPCWSTR lpName,
+                                      HANDLE* pHandle) {
+  // Using an event to create a valid nt handle
+  *pHandle = CreateEvent(nullptr, FALSE, FALSE, nullptr);
+  if (*pHandle == nullptr) {
+    return HRESULT_FROM_WIN32(GetLastError());
+  }
+  return S_OK;
+}
+
+HRESULT MockD3D11Texture2D::CreateInstance(
+    const D3D11_TEXTURE2D_DESC* texture_description,
+    ID3D11Texture2D** texture2D) {
+  MockD3D11Texture2D* mock_texture =
+      new MockD3D11Texture2D(texture_description);
+  if (!mock_texture) {
+    return E_OUTOFMEMORY;
+  }
+
+  *texture2D = mock_texture;
+  return S_OK;
+}
+
+HRESULT STDMETHODCALLTYPE
+MockD3D11Device::CreateTexture2D(const D3D11_TEXTURE2D_DESC* pDesc,
+                                 const D3D11_SUBRESOURCE_DATA* pInitialData,
+                                 ID3D11Texture2D** ppTexture2D) {
+  return MockD3D11Texture2D::CreateInstance(pDesc, ppTexture2D);
+}
+
+class MediaFoundationTexturePoolTest : public testing::Test {
+ public:
+  MediaFoundationTexturePoolTest() {}
+  base::WeakPtrFactory<MediaFoundationTexturePoolTest> weak_factory_{this};
+};
+
+TEST_F(MediaFoundationTexturePoolTest, VerifyTextureInitialization) {
+  MockD3D11Device mock_d3d_device;
+  media::MediaFoundationTexturePool test;
+  base::WaitableEvent wait_event;
+  gfx::Size frame_size(1920, 1080);
+
+  class SpecialCallback {
+   private:
+    base::WaitableEvent* wait_event_;
+    gfx::Size* frame_size_;
+
+   public:
+    SpecialCallback(base::WaitableEvent* wait_event, gfx::Size* frame_size)
+        : wait_event_(wait_event), frame_size_(frame_size) {}
+
+    void Invoke(std::vector<media::MediaFoundationFrameInfo> frame_textures,
+                const gfx::Size& texture_size) {
+      EXPECT_EQ(texture_size.width(), frame_size_->width());
+      EXPECT_EQ(texture_size.height(), frame_size_->height());
+      wait_event_->Signal();
+    }
+  } callback(&wait_event, &frame_size);
+
+  EXPECT_HRESULT_SUCCEEDED(
+      test.Initialize(&mock_d3d_device,
+                      base::BindRepeating(&SpecialCallback::Invoke,
+                                          base::Unretained(&callback)),
+                      frame_size));
+  wait_event.Wait();
+}
+
+}  // namespace media
\ No newline at end of file
diff --git a/media/video/BUILD.gn b/media/video/BUILD.gn
index a17b1df..e267390 100644
--- a/media/video/BUILD.gn
+++ b/media/video/BUILD.gn
@@ -76,6 +76,15 @@
 
   configs += [ "//media:subcomponent_config" ]
 
+  if (enable_libaom) {
+    sources += [
+      "av1_video_encoder.cc",
+      "av1_video_encoder.h",
+    ]
+
+    public_deps += [ "//third_party/libaom" ]
+  }
+
   if (media_use_libvpx) {
     sources += [
       "vpx_video_encoder.cc",
diff --git a/media/video/av1_video_encoder.cc b/media/video/av1_video_encoder.cc
new file mode 100644
index 0000000..c1a669f
--- /dev/null
+++ b/media/video/av1_video_encoder.cc
@@ -0,0 +1,406 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/video/av1_video_encoder.h"
+
+#include <cmath>
+
+#include "base/cxx17_backports.h"
+#include "base/logging.h"
+#include "base/numerics/checked_math.h"
+#include "base/strings/stringprintf.h"
+#include "base/system/sys_info.h"
+#include "base/time/time.h"
+#include "base/trace_event/trace_event.h"
+#include "media/base/bind_to_current_loop.h"
+#include "media/base/svc_scalability_mode.h"
+#include "media/base/timestamp_constants.h"
+#include "media/base/video_frame.h"
+#include "media/base/video_util.h"
+#include "third_party/libaom/source/libaom/aom/aomcx.h"
+#include "third_party/libyuv/include/libyuv/convert.h"
+
+namespace media {
+
+namespace {
+
+void FreeCodecCtx(aom_codec_ctx_t* codec_ctx) {
+  if (codec_ctx->name) {
+    // Codec has been initialized, we need to destroy it.
+    auto error = aom_codec_destroy(codec_ctx);
+    DCHECK_EQ(error, AOM_CODEC_OK);
+  }
+  delete codec_ctx;
+}
+
+int GetNumberOfThreads(int width) {
+  // Default to 1 thread for less than VGA.
+  int desired_threads = 1;
+
+  if (width >= 3840)
+    desired_threads = 16;
+  else if (width >= 2560)
+    desired_threads = 8;
+  else if (width >= 1280)
+    desired_threads = 4;
+  else if (width >= 640)
+    desired_threads = 2;
+
+  // Clamp to the number of available logical processors/cores.
+  desired_threads =
+      std::min(desired_threads, base::SysInfo::NumberOfProcessors());
+
+  return desired_threads;
+}
+
+Status SetUpAomConfig(const VideoEncoder::Options& opts,
+                      aom_codec_enc_cfg_t* config) {
+  if (opts.frame_size.width() <= 0 || opts.frame_size.height() <= 0)
+    return Status(StatusCode::kEncoderUnsupportedConfig,
+                  "Negative width or height values.");
+
+  if (!opts.frame_size.GetCheckedArea().IsValid())
+    return Status(StatusCode::kEncoderUnsupportedConfig, "Frame is too large.");
+
+  config->g_profile = 0;  // main
+  config->g_input_bit_depth = 8;
+  config->g_pass = AOM_RC_ONE_PASS;
+  config->g_lag_in_frames = 0;
+  config->rc_max_quantizer = 56;
+  config->rc_min_quantizer = 10;
+  config->rc_dropframe_thresh = 0;  // Don't drop frames
+
+  config->rc_undershoot_pct = 50;
+  config->rc_overshoot_pct = 50;
+  config->rc_buf_initial_sz = 600;
+  config->rc_buf_optimal_sz = 600;
+  config->rc_buf_sz = 1000;
+  config->g_error_resilient = 0;
+
+  config->g_timebase.num = 1;
+  config->g_timebase.den = base::Time::kMicrosecondsPerSecond;
+
+  // Set the number of threads based on the image width and num of cores.
+  config->g_threads = GetNumberOfThreads(opts.frame_size.width());
+
+  // Insert keyframes at will with a given max interval
+  if (opts.keyframe_interval.has_value()) {
+    config->kf_mode = AOM_KF_AUTO;
+    config->kf_min_dist = 0;
+    config->kf_max_dist = opts.keyframe_interval.value();
+  }
+
+  if (opts.bitrate.has_value()) {
+    auto& bitrate = opts.bitrate.value();
+    config->rc_target_bitrate = bitrate.target() / 1000;
+    switch (bitrate.mode()) {
+      case Bitrate::Mode::kVariable:
+        config->rc_end_usage = AOM_VBR;
+        break;
+      case Bitrate::Mode::kConstant:
+        config->rc_end_usage = AOM_CBR;
+        break;
+    }
+  } else {
+    // Default that gives about 2mbps to HD video
+    config->rc_end_usage = AOM_VBR;
+    config->rc_target_bitrate =
+        int{(opts.frame_size.GetCheckedArea() * 2)
+                .ValueOrDefault(std::numeric_limits<int>::max())};
+  }
+
+  config->g_w = opts.frame_size.width();
+  config->g_h = opts.frame_size.height();
+
+  if (opts.scalability_mode.has_value()) {
+    return Status(StatusCode::kEncoderUnsupportedConfig,
+                  "Unsupported number of temporal layers.");
+  }
+  return OkStatus();
+}
+
+}  // namespace
+
+Av1VideoEncoder::Av1VideoEncoder() : codec_(nullptr, FreeCodecCtx) {}
+
+void Av1VideoEncoder::Initialize(VideoCodecProfile profile,
+                                 const Options& options,
+                                 OutputCB output_cb,
+                                 StatusCB done_cb) {
+  done_cb = BindToCurrentLoop(std::move(done_cb));
+  if (codec_) {
+    std::move(done_cb).Run(StatusCode::kEncoderInitializeTwice);
+    return;
+  }
+  profile_ = profile;
+  if (profile < AV1PROFILE_MIN || profile > AV1PROFILE_MAX) {
+    auto status = Status(StatusCode::kEncoderUnsupportedProfile)
+                      .WithData("profile", profile);
+    std::move(done_cb).Run(status);
+    return;
+  }
+
+  // libaom is compiled with CONFIG_REALTIME_ONLY, so we can't use anything
+  // but AOM_USAGE_REALTIME.
+  auto error = aom_codec_enc_config_default(aom_codec_av1_cx(), &config_,
+                                            AOM_USAGE_REALTIME);
+  if (error != AOM_CODEC_OK) {
+    auto status = Status(StatusCode::kEncoderInitializationError,
+                         "Failed to get default AOM config.")
+                      .WithData("error_code", error);
+    std::move(done_cb).Run(status);
+    return;
+  }
+
+  Status status = SetUpAomConfig(options, &config_);
+  if (!status.is_ok()) {
+    std::move(done_cb).Run(status);
+    return;
+  }
+
+  // Initialize an encoder instance.
+  aom_codec_unique_ptr codec(new aom_codec_ctx_t, FreeCodecCtx);
+  codec->name = nullptr;
+  aom_codec_flags_t flags = 0;
+  error = aom_codec_enc_init(codec.get(), aom_codec_av1_cx(), &config_, flags);
+  if (error != AOM_CODEC_OK) {
+    status = Status(StatusCode::kEncoderInitializationError,
+                    "aom_codec_enc_init() failed.")
+                 .WithData("error_code", error)
+                 .WithData("error_message", aom_codec_err_to_string(error));
+    std::move(done_cb).Run(status);
+    return;
+  }
+  DCHECK_NE(codec->name, nullptr);
+
+#define CALL_AOM_CONTROL(key, value)                                           \
+  do {                                                                         \
+    error = aom_codec_control(codec.get(), (key), (value));                    \
+    if (error != AOM_CODEC_OK) {                                               \
+      status = Status(StatusCode::kEncoderInitializationError,                 \
+                      "Setting " #key " failed.")                              \
+                   .WithData("error_code", error)                              \
+                   .WithData("error_message", aom_codec_err_to_string(error)); \
+      std::move(done_cb).Run(status);                                          \
+      return;                                                                  \
+    }                                                                          \
+  } while (false)
+
+  CALL_AOM_CONTROL(AV1E_SET_ROW_MT, 1);
+  CALL_AOM_CONTROL(AV1E_SET_COEFF_COST_UPD_FREQ, 3);
+  CALL_AOM_CONTROL(AV1E_SET_MODE_COST_UPD_FREQ, 3);
+  CALL_AOM_CONTROL(AV1E_SET_MV_COST_UPD_FREQ, 3);
+
+  CALL_AOM_CONTROL(AV1E_SET_ENABLE_TPL_MODEL, 0);
+  CALL_AOM_CONTROL(AV1E_SET_DELTAQ_MODE, 0);
+  CALL_AOM_CONTROL(AV1E_SET_ENABLE_ORDER_HINT, 0);
+  CALL_AOM_CONTROL(AV1E_SET_ENABLE_OBMC, 0);
+  CALL_AOM_CONTROL(AV1E_SET_ENABLE_WARPED_MOTION, 0);
+  CALL_AOM_CONTROL(AV1E_SET_ENABLE_GLOBAL_MOTION, 0);
+  CALL_AOM_CONTROL(AV1E_SET_ENABLE_REF_FRAME_MVS, 0);
+
+  CALL_AOM_CONTROL(AV1E_SET_ENABLE_CFL_INTRA, 0);
+  CALL_AOM_CONTROL(AV1E_SET_ENABLE_SMOOTH_INTRA, 0);
+  CALL_AOM_CONTROL(AV1E_SET_ENABLE_ANGLE_DELTA, 0);
+  CALL_AOM_CONTROL(AV1E_SET_ENABLE_FILTER_INTRA, 0);
+  CALL_AOM_CONTROL(AV1E_SET_INTRA_DEFAULT_TX_ONLY, 1);
+
+  if (config_.rc_end_usage == AOM_CBR)
+    CALL_AOM_CONTROL(AV1E_SET_AQ_MODE, 3);
+
+  CALL_AOM_CONTROL(AV1E_SET_TILE_COLUMNS,
+                   static_cast<int>(std::log2(config_.g_threads)));
+
+  // AOME_SET_CPUUSED determines tradeoff between video quality and compression
+  // time. Valid range: 0..10. 0 runs the slowest, and 10 runs the fastest.
+  // Values 6 to 9 are usually used for realtime applications. Here we choose
+  // two sides of realtime range for our 'realtime' and 'quality' modes
+  // because we don't want encoding speed to drop into single digit fps
+  // even in quality mode.
+  const int cpu_speed =
+      (options.latency_mode == VideoEncoder::LatencyMode::Realtime) ? 9 : 7;
+  CALL_AOM_CONTROL(AOME_SET_CPUUSED, cpu_speed);
+#undef CALL_AOM_CONTROL
+
+  options_ = options;
+  originally_configured_size_ = options.frame_size;
+  output_cb_ = BindToCurrentLoop(std::move(output_cb));
+  codec_ = std::move(codec);
+  std::move(done_cb).Run(OkStatus());
+}
+
+void Av1VideoEncoder::Encode(scoped_refptr<VideoFrame> frame,
+                             bool key_frame,
+                             StatusCB done_cb) {
+  done_cb = BindToCurrentLoop(std::move(done_cb));
+  if (!codec_) {
+    std::move(done_cb).Run(StatusCode::kEncoderInitializeNeverCompleted);
+    return;
+  }
+
+  if (!frame) {
+    std::move(done_cb).Run(Status(StatusCode::kEncoderFailedEncode,
+                                  "No frame provided for encoding."));
+    return;
+  }
+
+  bool supported_format = frame->format() == PIXEL_FORMAT_NV12 ||
+                          frame->format() == PIXEL_FORMAT_I420 ||
+                          frame->format() == PIXEL_FORMAT_XBGR ||
+                          frame->format() == PIXEL_FORMAT_XRGB ||
+                          frame->format() == PIXEL_FORMAT_ABGR ||
+                          frame->format() == PIXEL_FORMAT_ARGB;
+  if ((!frame->IsMappable() && !frame->HasGpuMemoryBuffer()) ||
+      !supported_format) {
+    Status status =
+        Status(StatusCode::kEncoderFailedEncode, "Unexpected frame format.")
+            .WithData("IsMappable", frame->IsMappable())
+            .WithData("HasGpuMemoryBuffer", frame->HasGpuMemoryBuffer())
+            .WithData("format", frame->format());
+    std::move(done_cb).Run(std::move(status));
+    return;
+  }
+
+  if (frame->HasGpuMemoryBuffer()) {
+    frame = ConvertToMemoryMappedFrame(frame);
+    if (!frame) {
+      std::move(done_cb).Run(
+          Status(StatusCode::kEncoderFailedEncode,
+                 "Convert GMB frame to MemoryMappedFrame failed."));
+      return;
+    }
+  }
+
+  const bool is_yuv = IsYuvPlanar(frame->format());
+  if (frame->visible_rect().size() != options_.frame_size || !is_yuv) {
+    auto resized_frame = frame_pool_.CreateFrame(
+        is_yuv ? frame->format() : PIXEL_FORMAT_I420, options_.frame_size,
+        gfx::Rect(options_.frame_size), options_.frame_size,
+        frame->timestamp());
+    Status status;
+    if (resized_frame) {
+      status = ConvertAndScaleFrame(*frame, *resized_frame, resize_buf_);
+    } else {
+      status = Status(StatusCode::kEncoderFailedEncode,
+                      "Can't allocate a resized frame.");
+    }
+    if (!status.is_ok()) {
+      std::move(done_cb).Run(std::move(status));
+      return;
+    }
+    frame = std::move(resized_frame);
+  }
+
+  aom_image_t* image = aom_img_wrap(
+      &image_, AOM_IMG_FMT_I420, options_.frame_size.width(),
+      options_.frame_size.height(), 1, frame->data(VideoFrame::kYPlane));
+  DCHECK_EQ(image, &image_);
+
+  image->planes[AOM_PLANE_Y] = frame->visible_data(VideoFrame::kYPlane);
+  image->planes[AOM_PLANE_U] = frame->visible_data(VideoFrame::kUPlane);
+  image->planes[AOM_PLANE_V] = frame->visible_data(VideoFrame::kVPlane);
+  image->stride[AOM_PLANE_Y] = frame->stride(VideoFrame::kYPlane);
+  image->stride[AOM_PLANE_U] = frame->stride(VideoFrame::kUPlane);
+  image->stride[AOM_PLANE_V] = frame->stride(VideoFrame::kVPlane);
+
+  auto duration_us = GetFrameDuration(*frame).InMicroseconds();
+  last_frame_timestamp_ = frame->timestamp();
+  if (last_frame_color_space_ != frame->ColorSpace()) {
+    last_frame_color_space_ = frame->ColorSpace();
+    key_frame = true;
+  }
+
+  TRACE_EVENT0("media", "aom_codec_encode");
+  // Use artificial timestamps, so the encoder will not be misled by frame's
+  // fickle timestamps when doing rate control.
+  auto error =
+      aom_codec_encode(codec_.get(), image, artificial_timestamp_, duration_us,
+                       key_frame ? AOM_EFLAG_FORCE_KF : 0);
+  artificial_timestamp_ += duration_us;
+
+  if (error != AOM_CODEC_OK) {
+    auto msg =
+        base::StringPrintf("AOM encoding error: %s (%d)",
+                           aom_codec_error_detail(codec_.get()), codec_->err);
+    DLOG(ERROR) << msg;
+    std::move(done_cb).Run(Status(StatusCode::kEncoderFailedEncode, msg));
+    return;
+  }
+  DrainOutputs(frame->timestamp(), frame->ColorSpace());
+  std::move(done_cb).Run(OkStatus());
+}
+
+void Av1VideoEncoder::ChangeOptions(const Options& options,
+                                    OutputCB output_cb,
+                                    StatusCB done_cb) {
+  done_cb = BindToCurrentLoop(std::move(done_cb));
+  if (!codec_) {
+    std::move(done_cb).Run(StatusCode::kEncoderInitializeNeverCompleted);
+    return;
+  }
+  // TODO(crbug.com/1208280) Try to actually adjust setting instead of
+  // immediately dismissing configuration change.
+  std::move(done_cb).Run(StatusCode::kEncoderUnsupportedConfig);
+}
+
+base::TimeDelta Av1VideoEncoder::GetFrameDuration(const VideoFrame& frame) {
+  // Frame has duration in metadata, use it.
+  if (frame.metadata().frame_duration.has_value())
+    return frame.metadata().frame_duration.value();
+
+  // Options have framerate specified, use it.
+  if (options_.framerate.has_value())
+    return base::Seconds(1.0 / options_.framerate.value());
+
+  // No real way to figure out duration, use time passed since the last frame
+  // as an educated guess, but clamp it within reasonable limits.
+  constexpr auto min_duration = base::Seconds(1.0 / 60.0);
+  constexpr auto max_duration = base::Seconds(1.0 / 24.0);
+  auto duration = frame.timestamp() - last_frame_timestamp_;
+  return base::clamp(duration, min_duration, max_duration);
+}
+
+void Av1VideoEncoder::DrainOutputs(base::TimeDelta ts,
+                                   gfx::ColorSpace color_space) {
+  const aom_codec_cx_pkt_t* pkt = nullptr;
+  aom_codec_iter_t iter = nullptr;
+  while ((pkt = aom_codec_get_cx_data(codec_.get(), &iter)) != nullptr) {
+    if (pkt->kind != AOM_CODEC_CX_FRAME_PKT)
+      continue;
+
+    VideoEncoderOutput result;
+    result.key_frame = (pkt->data.frame.flags & AOM_FRAME_IS_KEY) != 0;
+    result.timestamp = ts;
+    result.color_space = color_space;
+    result.size = pkt->data.frame.sz;
+    result.data.reset(new uint8_t[result.size]);
+    memcpy(result.data.get(), pkt->data.frame.buf, result.size);
+    output_cb_.Run(std::move(result), {});
+  }
+}
+
+Av1VideoEncoder::~Av1VideoEncoder() = default;
+
+void Av1VideoEncoder::Flush(StatusCB done_cb) {
+  done_cb = BindToCurrentLoop(std::move(done_cb));
+  if (!codec_) {
+    std::move(done_cb).Run(StatusCode::kEncoderInitializeNeverCompleted);
+    return;
+  }
+
+  auto error = aom_codec_encode(codec_.get(), nullptr, 0, 0, 0);
+
+  if (error != AOM_CODEC_OK) {
+    auto msg =
+        base::StringPrintf("AOM encoding error: %s (%d)",
+                           aom_codec_error_detail(codec_.get()), codec_->err);
+    DLOG(ERROR) << msg;
+    std::move(done_cb).Run(Status(StatusCode::kEncoderFailedEncode, msg));
+    return;
+  }
+  DrainOutputs(base::TimeDelta(), gfx::ColorSpace());
+  std::move(done_cb).Run(OkStatus());
+}
+
+}  // namespace media
diff --git a/media/video/av1_video_encoder.h b/media/video/av1_video_encoder.h
new file mode 100644
index 0000000..3474ecc
--- /dev/null
+++ b/media/video/av1_video_encoder.h
@@ -0,0 +1,67 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_VIDEO_AV1_VIDEO_ENCODER_H_
+#define MEDIA_VIDEO_AV1_VIDEO_ENCODER_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/time/time.h"
+#include "media/base/media_export.h"
+#include "media/base/video_encoder.h"
+#include "media/base/video_frame_pool.h"
+#include "third_party/libaom/source/libaom/aom/aom_encoder.h"
+#include "ui/gfx/color_space.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace media {
+
+class MEDIA_EXPORT Av1VideoEncoder : public VideoEncoder {
+ public:
+  Av1VideoEncoder();
+  ~Av1VideoEncoder() override;
+
+  // VideoDecoder implementation.
+  void Initialize(VideoCodecProfile profile,
+                  const Options& options,
+                  OutputCB output_cb,
+                  StatusCB done_cb) override;
+  void Encode(scoped_refptr<VideoFrame> frame,
+              bool key_frame,
+              StatusCB done_cb) override;
+  void ChangeOptions(const Options& options,
+                     OutputCB output_cb,
+                     StatusCB done_cb) override;
+  void Flush(StatusCB done_cb) override;
+
+ private:
+  base::TimeDelta GetFrameDuration(const VideoFrame& frame);
+  void DrainOutputs(base::TimeDelta ts, gfx::ColorSpace color_space);
+
+  using aom_codec_unique_ptr =
+      std::unique_ptr<aom_codec_ctx_t, void (*)(aom_codec_ctx_t*)>;
+
+  aom_codec_unique_ptr codec_;
+  aom_codec_enc_cfg_t config_;
+  aom_image_t image_ = {};
+
+  // This is a timestamp that is always increasing by frame's duration.
+  // It's used only for rate control and has nothing to do with timestamps
+  // coming from real frames.
+  aom_codec_pts_t artificial_timestamp_ = 0;
+
+  gfx::Size originally_configured_size_;
+  base::TimeDelta last_frame_timestamp_;
+  gfx::ColorSpace last_frame_color_space_;
+
+  VideoCodecProfile profile_ = VIDEO_CODEC_PROFILE_UNKNOWN;
+  VideoFramePool frame_pool_;
+  std::vector<uint8_t> resize_buf_;
+  Options options_;
+  OutputCB output_cb_;
+};
+
+}  // namespace media
+#endif  // MEDIA_VIDEO_AV1_VIDEO_ENCODER_H_
diff --git a/media/video/software_video_encoder_test.cc b/media/video/software_video_encoder_test.cc
index 8e4ed08..70c9155 100644
--- a/media/video/software_video_encoder_test.cc
+++ b/media/video/software_video_encoder_test.cc
@@ -39,6 +39,14 @@
 #include "media/video/vpx_video_encoder.h"
 #endif
 
+#if BUILDFLAG(ENABLE_LIBAOM)
+#include "media/video/av1_video_encoder.h"
+#endif
+
+#if BUILDFLAG(ENABLE_DAV1D_DECODER)
+#include "media/filters/dav1d_video_decoder.h"
+#endif
+
 namespace media {
 
 struct SwVideoTestParams {
@@ -85,6 +93,10 @@
 #if BUILDFLAG(ENABLE_LIBVPX)
       decoder_ = std::make_unique<VpxVideoDecoder>();
 #endif
+    } else if (codec_ == VideoCodec::kAV1) {
+#if BUILDFLAG(ENABLE_DAV1D_DECODER)
+      decoder_ = std::make_unique<Dav1dVideoDecoder>(&media_log_);
+#endif
     }
 
     EXPECT_NE(decoder_, nullptr);
@@ -151,6 +163,12 @@
 
   std::unique_ptr<VideoEncoder> CreateEncoder(VideoCodec codec) {
     switch (codec) {
+      case media::VideoCodec::kAV1:
+#if BUILDFLAG(ENABLE_LIBAOM)
+        return std::make_unique<media::Av1VideoEncoder>();
+#else
+        return nullptr;
+#endif
       case media::VideoCodec::kVP8:
       case media::VideoCodec::kVP9:
 #if BUILDFLAG(ENABLE_LIBVPX)
@@ -844,6 +862,17 @@
                          PrintTestParams);
 #endif  // ENABLE_LIBVPX
 
+#if BUILDFLAG(ENABLE_LIBAOM)
+SwVideoTestParams kAv1Params[] = {
+    {VideoCodec::kAV1, AV1PROFILE_PROFILE_MAIN, PIXEL_FORMAT_I420},
+    {VideoCodec::kAV1, AV1PROFILE_PROFILE_MAIN, PIXEL_FORMAT_XRGB}};
+
+INSTANTIATE_TEST_SUITE_P(Av1Generic,
+                         SoftwareVideoEncoderTest,
+                         ::testing::ValuesIn(kAv1Params),
+                         PrintTestParams);
+#endif  // ENABLE_LIBAOM
+
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(H264VideoEncoderTest);
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SVCVideoEncoderTest);
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SoftwareVideoEncoderTest);
diff --git a/services/network/public/cpp/corb/corb_impl.h b/services/network/public/cpp/corb/corb_impl.h
index ea518d26..c9b4a5f4 100644
--- a/services/network/public/cpp/corb/corb_impl.h
+++ b/services/network/public/cpp/corb/corb_impl.h
@@ -119,6 +119,25 @@
     Decision HandleEndOfSniffableResponseBody() override;
     bool ShouldReportBlockedResponse() const override;
 
+    class ConfirmationSniffer;
+    class SimpleConfirmationSniffer;
+
+    // Returns true if the response has a nosniff header.
+    static bool HasNoSniff(const network::mojom::URLResponseHead& response);
+
+   private:
+    FRIEND_TEST_ALL_PREFIXES(CrossOriginReadBlockingTest,
+                             SeemsSensitiveFromCORSHeuristic);
+    FRIEND_TEST_ALL_PREFIXES(CrossOriginReadBlockingTest,
+                             SeemsSensitiveFromCacheHeuristic);
+    FRIEND_TEST_ALL_PREFIXES(CrossOriginReadBlockingTest,
+                             SeemsSensitiveWithBothHeuristics);
+    FRIEND_TEST_ALL_PREFIXES(CrossOriginReadBlockingTest,
+                             SupportsRangeRequests);
+    FRIEND_TEST_ALL_PREFIXES(content::CrossSiteDocumentResourceHandlerTest,
+                             CORBProtectionLogging);
+    FRIEND_TEST_ALL_PREFIXES(ResponseAnalyzerTest, CORBProtectionLogging);
+
     // true if either 1) ShouldBlockBasedOnHeaders decided to allow the response
     // based on headers alone or 2) ShouldBlockBasedOnHeaders decided to sniff
     // the response body and SniffResponseBody decided to allow the response
@@ -142,31 +161,6 @@
              corb_protection_logging_needs_sniffing_;
     }
 
-    // The MIME type determined by ShouldBlockBasedOnHeaders.
-    const CrossOriginReadBlocking::MimeType& canonical_mime_type_for_testing()
-        const {
-      return canonical_mime_type_;
-    }
-
-    class ConfirmationSniffer;
-    class SimpleConfirmationSniffer;
-
-    // Returns true if the response has a nosniff header.
-    static bool HasNoSniff(const network::mojom::URLResponseHead& response);
-
-   private:
-    FRIEND_TEST_ALL_PREFIXES(CrossOriginReadBlockingTest,
-                             SeemsSensitiveFromCORSHeuristic);
-    FRIEND_TEST_ALL_PREFIXES(CrossOriginReadBlockingTest,
-                             SeemsSensitiveFromCacheHeuristic);
-    FRIEND_TEST_ALL_PREFIXES(CrossOriginReadBlockingTest,
-                             SeemsSensitiveWithBothHeuristics);
-    FRIEND_TEST_ALL_PREFIXES(CrossOriginReadBlockingTest,
-                             SupportsRangeRequests);
-    FRIEND_TEST_ALL_PREFIXES(content::CrossSiteDocumentResourceHandlerTest,
-                             CORBProtectionLogging);
-    FRIEND_TEST_ALL_PREFIXES(ResponseAnalyzerTest, CORBProtectionLogging);
-
     // Helper for translating ShouldAllow(), ShouldBlock() and needs_sniffing()
     // into corb::Decision.
     Decision GetCorbDecision();
diff --git a/services/network/public/cpp/corb/corb_impl_unittest.cc b/services/network/public/cpp/corb/corb_impl_unittest.cc
index 99faf46e..b06100af 100644
--- a/services/network/public/cpp/corb/corb_impl_unittest.cc
+++ b/services/network/public/cpp/corb/corb_impl_unittest.cc
@@ -170,6 +170,7 @@
   }
 
   return os << "\n  description           = " << scenario.description
+            << "\n  source_line           = " << scenario.source_line
             << "\n  target_url            = " << scenario.target_url
             << "\n  initiator_origin      = " << scenario.initiator_origin
             << "\n  response_headers      = " << response_headers_formatted
@@ -1885,10 +1886,11 @@
     return response;
   }
 
-  // Instantiate and run CORB analyzer on the current scenario. Allow the
-  // analyzer to sniff the response body if needed and confirm it correctly
-  // decides to block or allow.
-  void RunAnalyzerOnScenario(const mojom::URLResponseHead& response) {
+  // Take and run ResponseAnalyzer on the current scenario. Allow the analyzer
+  // to sniff the response body if needed and confirm it correctly decides to
+  // block or allow.
+  void RunAnalyzerOnScenario(const mojom::URLResponseHead& response,
+                             std::unique_ptr<ResponseAnalyzer> analyzer) {
     TestScenario scenario = GetParam();
     // Initialize |request| from the parameters.
     std::unique_ptr<net::URLRequest> request =
@@ -1904,23 +1906,13 @@
     auto request_mode = cors_header_value == "" ? mojom::RequestMode::kNoCors
                                                 : mojom::RequestMode::kCors;
 
-    // Create a ResponseAnalyzer to test.
+    // Initialize the `analyzer`.
     //
-    // The ResponseAnalyzer will be destructed when `analyzer` goes out of scope
-    // (the destructor triggers logging of UMAs that some callers of
+    // Note that the `analyzer` will be destructed when `analyzer` goes out of
+    // scope (the destructor may trigger logging of UMAs that some callers of
     // RunAnalyzerOnScenario attempt to verify).
-    auto analyzer = std::make_unique<CorbResponseAnalyzer>();
-    analyzer->Init(request->url(), request->initiator(), request_mode,
-                   response);
-
-    // Verify MIME type was classified correctly.
-    EXPECT_EQ(scenario.canonical_mime_type,
-              analyzer->canonical_mime_type_for_testing());
-
-    // Verify that the verdict packet is >= 0 if CORB expects to sniff.
-    bool expected_to_sniff =
-        scenario.verdict_packet != kVerdictPacketForHeadersBasedVerdict;
-    ASSERT_EQ(expected_to_sniff, analyzer->needs_sniffing());
+    ResponseAnalyzer::Decision decision = analyzer->Init(
+        request->url(), request->initiator(), request_mode, response);
 
     // This vector holds the packets to be delivered.
     std::vector<const char*> packets_vector(scenario.packets);
@@ -1931,84 +1923,78 @@
     // then the sniffing loop below will be skipped.
     EXPECT_LT(scenario.verdict_packet, static_cast<int>(packets_vector.size()));
 
-    // If we don't expect to sniff then CORB should have already made a blockng
-    // decision based on the headers.
-    if (!expected_to_sniff) {
-      EXPECT_FALSE(analyzer->needs_sniffing());
+    // Verify that the ResponseAnalyzer asks for sniffing if this is what the
+    // testcase expects.
+    bool expected_to_sniff =
+        scenario.verdict_packet != kVerdictPacketForHeadersBasedVerdict;
+    if (expected_to_sniff) {
+      EXPECT_EQ(decision, ResponseAnalyzer::Decision::kSniffMore);
+    } else {
+      // If we don't expect to sniff then ResponseAnalyzer should have already
+      // made a blockng decision based on the headers.
       if (scenario.verdict == Verdict::kBlock) {
-        ASSERT_FALSE(analyzer->ShouldAllow());
-        ASSERT_TRUE(analyzer->ShouldBlock());
+        EXPECT_EQ(decision, ResponseAnalyzer::Decision::kBlock);
       } else {
-        ASSERT_FALSE(analyzer->ShouldBlock());
-        ASSERT_TRUE(analyzer->ShouldAllow());
+        EXPECT_EQ(decision, ResponseAnalyzer::Decision::kAllow);
       }
-      return;
     }
 
     // Simulate the behaviour of the URLLoader by appending the packets into
     // |data_buffer| and feeding this to |analyzer|.
-    std::string data_buffer;
-    size_t data_offset = 0;
-    bool reached_final_packet = false;
-    for (int packet_index = 0; packet_index <= scenario.verdict_packet;
-         packet_index++) {
-      SCOPED_TRACE(testing::Message()
-                   << "While delivering packet #" << packet_index);
+    bool run_out_of_data_to_sniff = false;
+    if (decision == ResponseAnalyzer::Decision::kSniffMore) {
+      std::string data_buffer;
+      size_t data_offset = 0;
+      for (int packet_index = 0; packet_index <= scenario.verdict_packet;
+           packet_index++) {
+        SCOPED_TRACE(testing::Message()
+                     << "While delivering packet #" << packet_index);
 
-      // At each iteration of the loop we feed a new packet to |analyzer|,
-      // breaking at the |verdict_packet|. Since we haven't given the next
-      // packet to |analyzer| yet at this point in the loop, it shouldn't have
-      // made a decision yet.
-      EXPECT_TRUE(analyzer->needs_sniffing());
-      EXPECT_FALSE(analyzer->ShouldBlock());
-      EXPECT_FALSE(analyzer->ShouldAllow());
+        // At each iteration of the loop we feed a new packet to |analyzer|,
+        // breaking at the |verdict_packet|. Since we haven't given the next
+        // packet to |analyzer| yet at this point in the loop, it shouldn't have
+        // made a decision yet.
+        EXPECT_EQ(decision, ResponseAnalyzer::Decision::kSniffMore);
 
-      // Append the next packet of the response body. If appending the entire
-      // packet would exceed net::kMaxBytesToSniff we truncate the data.
-      size_t bytes_to_append = strlen(packets_vector[packet_index]);
-      if (data_offset + bytes_to_append > net::kMaxBytesToSniff)
-        bytes_to_append = net::kMaxBytesToSniff - data_offset;
-      data_buffer.append(packets_vector[packet_index], bytes_to_append);
+        // Append the next packet of the response body. If appending the entire
+        // packet would exceed net::kMaxBytesToSniff we truncate the data.
+        size_t bytes_to_append = strlen(packets_vector[packet_index]);
+        if (data_offset + bytes_to_append > net::kMaxBytesToSniff)
+          bytes_to_append = net::kMaxBytesToSniff - data_offset;
+        data_buffer.append(packets_vector[packet_index], bytes_to_append);
 
-      // Hand |analyzer_| the data to sniff.
-      analyzer->Sniff(data_buffer);
-      data_offset += bytes_to_append;
-
-      // If the latest packet was empty, or we reached net::kMaxBytesToSniff
-      // then sniffing should be over. Furthermore, if the |analyzer| hasn't
-      // decided to block or allow, then (in the real implementation) URLLoader
-      // will default to allowing. We check here that this occurs only when it
-      // is supposed to.
-      if ((bytes_to_append == 0 || data_offset == net::kMaxBytesToSniff)) {
-        reached_final_packet = true;
-        // Sanity check sniffing is over.
-        EXPECT_EQ(packet_index, scenario.verdict_packet);
-        // Check we have run out of data if and only if we expected to.
-        bool expected_to_run_out_of_data =
-            scenario.verdict == Verdict::kAllowBecauseOutOfData;
-        bool did_run_out_of_data =
-            !analyzer->ShouldAllow() && !analyzer->ShouldBlock();
-        EXPECT_EQ(expected_to_run_out_of_data, did_run_out_of_data);
+        // Hand |analyzer_| the data to sniff.
+        decision = analyzer->Sniff(data_buffer);
+        data_offset += bytes_to_append;
+        if (decision != ResponseAnalyzer::Decision::kSniffMore)
+          break;
       }
     }
 
+    // Handle scenarios where no decision can be made before running out of data
+    // to sniff.
+    if (decision == ResponseAnalyzer::Decision::kSniffMore) {
+      run_out_of_data_to_sniff = true;
+      decision = analyzer->HandleEndOfSniffableResponseBody();
+
+      // HandleEndOfSniffableResponseBody should never return kSniffMore.
+      EXPECT_NE(decision, ResponseAnalyzer::Decision::kSniffMore);
+    }
+
     // Confirm the analyzer is blocking or allowing correctly (now that we have
     // performed any needed sniffing).
     if (scenario.verdict == Verdict::kBlock) {
-      ASSERT_FALSE(analyzer->ShouldAllow());
-      ASSERT_TRUE(analyzer->ShouldBlock());
+      EXPECT_EQ(decision, ResponseAnalyzer::Decision::kBlock);
     } else {
+      EXPECT_EQ(decision, ResponseAnalyzer::Decision::kAllow);
+
       // In this case either the |analyzer| has decided to allow the response,
       // or run out of data and so the response will be allowed by default.
-      ASSERT_FALSE(analyzer->ShouldBlock());
       if (scenario.verdict == Verdict::kAllow) {
-        ASSERT_TRUE(analyzer->ShouldAllow());
+        EXPECT_FALSE(run_out_of_data_to_sniff);
       } else {
-        // In this case |scenario.verdict| == Verdict::kAllowBecauseOutOfData,
-        // so double-check that sniffing actually occurred and failed.
         EXPECT_EQ(Verdict::kAllowBecauseOutOfData, scenario.verdict);
-        ASSERT_FALSE(analyzer->ShouldAllow());
-        EXPECT_TRUE(reached_final_packet);
+        EXPECT_TRUE(run_out_of_data_to_sniff);
       }
     }
   }
@@ -2039,7 +2025,7 @@
                      scenario.initiator_origin);
 
   // Run the analyzer and confirm it allows/blocks correctly.
-  RunAnalyzerOnScenario(*response);
+  RunAnalyzerOnScenario(*response, std::make_unique<CorbResponseAnalyzer>());
 
   // Verify that histograms are correctly incremented.
   base::HistogramTester::CountsMap expected_counts;
@@ -2125,7 +2111,7 @@
   const bool expect_nosniff = CorbResponseAnalyzer::HasNoSniff(*response);
 
   // Run the analyzer and confirm it allows/blocks correctly.
-  RunAnalyzerOnScenario(*response);
+  RunAnalyzerOnScenario(*response, std::make_unique<CorbResponseAnalyzer>());
 
   base::HistogramTester::CountsMap expected_counts;
   expected_counts["SiteIsolation.CORBProtection.SensitiveResource"] = 1;
diff --git a/services/network/public/cpp/simple_url_loader.cc b/services/network/public/cpp/simple_url_loader.cc
index 9c49ef60..4f69404 100644
--- a/services/network/public/cpp/simple_url_loader.cc
+++ b/services/network/public/cpp/simple_url_loader.cc
@@ -140,7 +140,7 @@
           std::min(static_cast<size_t>(32 * 1024),
                    upload_string_.length() - write_position_));
       if (write_size == 0) {
-        // Upload is done. Close the uplaod body pipe and wait for another call
+        // Upload is done. Close the upload body pipe and wait for another call
         // to Read().
         ResetBodyPipe();
         return;
@@ -253,7 +253,7 @@
   // Called by BodyHandler when the BodyHandler body handler is done. If |error|
   // is not net::OK, some error occurred reading or consuming the body. If it is
   // net::OK, the pipe was closed and all data received was successfully
-  // handled. This could indicate an error, concellation, or completion. To
+  // handled. This could indicate an error, cancellation, or completion. To
   // determine which case this is, the size will also be compared to the size
   // reported in URLLoaderCompletionStatus(), if
   // URLLoaderCompletionStatus indicates a success.
@@ -1031,7 +1031,7 @@
       }
     }
 
-    // These are set on cosntruction and accessed on both task runners.
+    // These are set on construction and accessed on both task runners.
     const scoped_refptr<base::SequencedTaskRunner> body_handler_task_runner_;
     const scoped_refptr<base::SequencedTaskRunner> file_writer_task_runner_;
 
diff --git a/testing/buildbot/chrome.json b/testing/buildbot/chrome.json
index 84031f6..47048e650 100644
--- a/testing/buildbot/chrome.json
+++ b/testing/buildbot/chrome.json
@@ -1967,29 +1967,6 @@
           "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test_id_prefix": "ninja://chrome/test:chrome_sizes/"
-      },
-      {
-        "isolate_name": "variations_smoke_tests",
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "variations_smoke_tests",
-        "resultdb": {
-          "enable": true,
-          "result_format": "single"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04",
-              "pool": "chrome.tests"
-            }
-          ],
-          "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test_id_prefix": "ninja://chrome/test:variations_smoke_tests/"
       }
     ]
   },
@@ -3854,30 +3831,6 @@
           "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test_id_prefix": "ninja://chrome/test:chrome_sizes/"
-      },
-      {
-        "isolate_name": "variations_smoke_tests",
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "variations_smoke_tests",
-        "resultdb": {
-          "enable": true,
-          "result_format": "single"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Mac-10.15",
-              "pool": "chrome.tests"
-            }
-          ],
-          "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test_id_prefix": "ninja://chrome/test:variations_smoke_tests/"
       }
     ]
   },
@@ -3928,29 +3881,6 @@
           "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test_id_prefix": "ninja://chrome/test:chrome_sizes/"
-      },
-      {
-        "isolate_name": "variations_smoke_tests",
-        "merge": {
-          "args": [],
-          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
-        },
-        "name": "variations_smoke_tests",
-        "resultdb": {
-          "enable": true,
-          "result_format": "single"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Windows-10-19042",
-              "pool": "chrome.tests"
-            }
-          ],
-          "service_account": "chrome-tester@chops-service-accounts.iam.gserviceaccount.com"
-        },
-        "test_id_prefix": "ninja://chrome/test:variations_smoke_tests/"
       }
     ]
   },
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index c7452515..bef73cc 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -546,13 +546,6 @@
           'has_native_resultdb_integration',
         ],
       },
-      'variations_smoke_tests': {
-        'isolate_name': 'variations_smoke_tests',
-        'resultdb': {
-          'enable': True,
-          'result_format': 'single'
-        },
-      },
     },
 
     'chrome_public_tests': {
@@ -5308,16 +5301,6 @@
       'upload_trace_processor': {},
     },
 
-    'variations_smoke_tests': {
-      'variations_smoke_tests': {
-        'isolate_name': 'variations_smoke_tests',
-        'resultdb': {
-          'enable': True,
-          'result_format': 'single'
-        },
-      },
-    },
-
     # Not applicable for android x86 & x64 since the targets here assert
     # "enable_vr" in GN which is only true for android arm & arm64.
     # For details, see the following files:
diff --git a/third_party/android_deps/BUILD.gn b/third_party/android_deps/BUILD.gn
index ad62a459..9474fb6 100644
--- a/third_party/android_deps/BUILD.gn
+++ b/third_party/android_deps/BUILD.gn
@@ -382,7 +382,7 @@
 
 # This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
 java_prebuilt("org_jetbrains_kotlin_kotlin_stdlib_java") {
-  jar_path = "libs/org_jetbrains_kotlin_kotlin_stdlib/kotlin-stdlib-1.6.0.jar"
+  jar_path = "libs/org_jetbrains_kotlin_kotlin_stdlib/kotlin-stdlib-1.6.10.jar"
   output_name = "org_jetbrains_kotlin_kotlin_stdlib"
   supports_android = true
   deps = [
@@ -702,7 +702,7 @@
 
 # This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
 java_prebuilt("org_jetbrains_kotlin_kotlin_stdlib_common_java") {
-  jar_path = "libs/org_jetbrains_kotlin_kotlin_stdlib_common/kotlin-stdlib-common-1.6.0.jar"
+  jar_path = "libs/org_jetbrains_kotlin_kotlin_stdlib_common/kotlin-stdlib-common-1.6.10.jar"
   output_name = "org_jetbrains_kotlin_kotlin_stdlib_common"
   supports_android = true
 
diff --git a/third_party/arcore-android-sdk-client/OWNERS b/third_party/arcore-android-sdk-client/OWNERS
index 8f984dc5..d95d28c 100644
--- a/third_party/arcore-android-sdk-client/OWNERS
+++ b/third_party/arcore-android-sdk-client/OWNERS
@@ -1,2 +1 @@
-bialpio@chromium.org
-klausw@chromium.org
+file://components/webxr/OWNERS
diff --git a/third_party/arcore-android-sdk/OWNERS b/third_party/arcore-android-sdk/OWNERS
index 8f984dc5..d95d28c 100644
--- a/third_party/arcore-android-sdk/OWNERS
+++ b/third_party/arcore-android-sdk/OWNERS
@@ -1,2 +1 @@
-bialpio@chromium.org
-klausw@chromium.org
+file://components/webxr/OWNERS
diff --git a/third_party/blink/renderer/core/css/affected_by_pseudo_test.cc b/third_party/blink/renderer/core/css/affected_by_pseudo_test.cc
index a69bb9e..6a3fb44 100644
--- a/third_party/blink/renderer/core/css/affected_by_pseudo_test.cc
+++ b/third_party/blink/renderer/core/css/affected_by_pseudo_test.cc
@@ -483,19 +483,19 @@
   UpdateAllLifecyclePhasesForTest();
   EXPECT_TRUE(
       GetElementById("div2")->GetComputedStyle()->AffectedByPseudoInHas());
-  EXPECT_FALSE(GetElementById("div2")
-                   ->GetComputedStyle()
-                   ->AncestorsAffectedByHoverInHas());
+  EXPECT_TRUE(GetElementById("div2")
+                  ->GetComputedStyle()
+                  ->AncestorsAffectedByHoverInHas());
   EXPECT_FALSE(
       GetElementById("div3")->GetComputedStyle()->AffectedByPseudoInHas());
-  EXPECT_FALSE(GetElementById("div3")
-                   ->GetComputedStyle()
-                   ->AncestorsAffectedByHoverInHas());
+  EXPECT_TRUE(GetElementById("div3")
+                  ->GetComputedStyle()
+                  ->AncestorsAffectedByHoverInHas());
   EXPECT_FALSE(
       GetElementById("div4")->GetComputedStyle()->AffectedByPseudoInHas());
-  EXPECT_FALSE(GetElementById("div4")
-                   ->GetComputedStyle()
-                   ->AncestorsAffectedByHoverInHas());
+  EXPECT_TRUE(GetElementById("div4")
+                  ->GetComputedStyle()
+                  ->AncestorsAffectedByHoverInHas());
   EXPECT_TRUE(
       GetElementById("div5")->GetComputedStyle()->AffectedByPseudoInHas());
   EXPECT_TRUE(GetElementById("div5")
@@ -547,7 +547,7 @@
   UpdateAllLifecyclePhasesForTest();
   unsigned element_count =
       GetStyleEngine().StyleForElementCount() - start_count;
-  ASSERT_EQ(0U, element_count);
+  ASSERT_EQ(1U, element_count);
   GetElementById("div3")->SetHovered(false);
   UpdateAllLifecyclePhasesForTest();
 
@@ -555,7 +555,7 @@
   GetElementById("div4")->SetHovered(true);
   UpdateAllLifecyclePhasesForTest();
   element_count = GetStyleEngine().StyleForElementCount() - start_count;
-  ASSERT_EQ(0U, element_count);
+  ASSERT_EQ(1U, element_count);
   GetElementById("div4")->SetHovered(false);
   UpdateAllLifecyclePhasesForTest();
 
@@ -563,28 +563,6 @@
   GetElementById("div4")->setAttribute(html_names::kClassAttr, "b");
   UpdateAllLifecyclePhasesForTest();
   element_count = GetStyleEngine().StyleForElementCount() - start_count;
-  ASSERT_EQ(3U, element_count);
-
-  EXPECT_TRUE(
-      GetElementById("div2")->GetComputedStyle()->AffectedByPseudoInHas());
-  EXPECT_TRUE(GetElementById("div2")
-                  ->GetComputedStyle()
-                  ->AncestorsAffectedByHoverInHas());
-  EXPECT_FALSE(
-      GetElementById("div3")->GetComputedStyle()->AffectedByPseudoInHas());
-  EXPECT_TRUE(GetElementById("div3")
-                  ->GetComputedStyle()
-                  ->AncestorsAffectedByHoverInHas());
-  EXPECT_FALSE(
-      GetElementById("div4")->GetComputedStyle()->AffectedByPseudoInHas());
-  EXPECT_TRUE(GetElementById("div4")
-                  ->GetComputedStyle()
-                  ->AncestorsAffectedByHoverInHas());
-
-  start_count = GetStyleEngine().StyleForElementCount();
-  GetElementById("div3")->setAttribute(html_names::kClassAttr, "b");
-  UpdateAllLifecyclePhasesForTest();
-  element_count = GetStyleEngine().StyleForElementCount() - start_count;
   ASSERT_EQ(1U, element_count);
 
   EXPECT_TRUE(
@@ -604,66 +582,6 @@
                   ->AncestorsAffectedByHoverInHas());
 
   start_count = GetStyleEngine().StyleForElementCount();
-  GetElementById("div3")->SetHovered(true);
-  UpdateAllLifecyclePhasesForTest();
-  element_count = GetStyleEngine().StyleForElementCount() - start_count;
-  ASSERT_EQ(1U, element_count);
-  GetElementById("div3")->SetHovered(false);
-  UpdateAllLifecyclePhasesForTest();
-
-  start_count = GetStyleEngine().StyleForElementCount();
-  GetElementById("div4")->SetHovered(true);
-  UpdateAllLifecyclePhasesForTest();
-  element_count = GetStyleEngine().StyleForElementCount() - start_count;
-  ASSERT_EQ(1U, element_count);
-  GetElementById("div4")->SetHovered(false);
-  UpdateAllLifecyclePhasesForTest();
-
-  start_count = GetStyleEngine().StyleForElementCount();
-  GetElementById("div3")->setAttribute(html_names::kClassAttr, "");
-  UpdateAllLifecyclePhasesForTest();
-  element_count = GetStyleEngine().StyleForElementCount() - start_count;
-  ASSERT_EQ(1U, element_count);
-
-  EXPECT_TRUE(
-      GetElementById("div2")->GetComputedStyle()->AffectedByPseudoInHas());
-  EXPECT_TRUE(GetElementById("div2")
-                  ->GetComputedStyle()
-                  ->AncestorsAffectedByHoverInHas());
-  EXPECT_FALSE(
-      GetElementById("div3")->GetComputedStyle()->AffectedByPseudoInHas());
-  EXPECT_TRUE(GetElementById("div3")
-                  ->GetComputedStyle()
-                  ->AncestorsAffectedByHoverInHas());
-  EXPECT_FALSE(
-      GetElementById("div4")->GetComputedStyle()->AffectedByPseudoInHas());
-  EXPECT_TRUE(GetElementById("div4")
-                  ->GetComputedStyle()
-                  ->AncestorsAffectedByHoverInHas());
-
-  start_count = GetStyleEngine().StyleForElementCount();
-  GetElementById("div4")->setAttribute(html_names::kClassAttr, "");
-  UpdateAllLifecyclePhasesForTest();
-  element_count = GetStyleEngine().StyleForElementCount() - start_count;
-  ASSERT_EQ(3U, element_count);
-
-  EXPECT_TRUE(
-      GetElementById("div2")->GetComputedStyle()->AffectedByPseudoInHas());
-  EXPECT_FALSE(GetElementById("div2")
-                   ->GetComputedStyle()
-                   ->AncestorsAffectedByHoverInHas());
-  EXPECT_FALSE(
-      GetElementById("div3")->GetComputedStyle()->AffectedByPseudoInHas());
-  EXPECT_FALSE(GetElementById("div3")
-                   ->GetComputedStyle()
-                   ->AncestorsAffectedByHoverInHas());
-  EXPECT_FALSE(
-      GetElementById("div4")->GetComputedStyle()->AffectedByPseudoInHas());
-  EXPECT_FALSE(GetElementById("div4")
-                   ->GetComputedStyle()
-                   ->AncestorsAffectedByHoverInHas());
-
-  start_count = GetStyleEngine().StyleForElementCount();
   GetElementById("div6")->SetHovered(true);
   UpdateAllLifecyclePhasesForTest();
   element_count = GetStyleEngine().StyleForElementCount() - start_count;
diff --git a/third_party/blink/renderer/core/css/css_selector.cc b/third_party/blink/renderer/core/css/css_selector.cc
index 1321970..2ceb47e 100644
--- a/third_party/blink/renderer/core/css/css_selector.cc
+++ b/third_party/blink/renderer/core/css/css_selector.cc
@@ -1058,11 +1058,6 @@
   data_.rare_data_->selector_list_ = std::move(selector_list);
 }
 
-void CSSSelector::SetContainsPseudoInsideHasPseudoClass() {
-  CreateRareData();
-  data_.rare_data_->contains_pseudo_inside_has_pseudo_class_ = true;
-}
-
 static bool ValidateSubSelector(const CSSSelector* selector) {
   switch (selector->Match()) {
     case CSSSelector::kTag:
@@ -1263,8 +1258,7 @@
       serializing_value_(value),
       bits_(),
       attribute_(AnyQName()),
-      argument_(g_null_atom),
-      contains_pseudo_inside_has_pseudo_class_(false) {}
+      argument_(g_null_atom) {}
 
 CSSSelector::RareData::~RareData() = default;
 
diff --git a/third_party/blink/renderer/core/css/css_selector.h b/third_party/blink/renderer/core/css/css_selector.h
index 85c7d64b..027a42e 100644
--- a/third_party/blink/renderer/core/css/css_selector.h
+++ b/third_party/blink/renderer/core/css/css_selector.h
@@ -354,11 +354,6 @@
   const Vector<AtomicString>* PartNames() const {
     return has_rare_data_ ? data_.rare_data_->part_names_.get() : nullptr;
   }
-  bool ContainsPseudoInsideHasPseudoClass() const {
-    return has_rare_data_
-               ? data_.rare_data_->contains_pseudo_inside_has_pseudo_class_
-               : false;
-  }
 
 #ifndef NDEBUG
   void Show() const;
@@ -371,7 +366,6 @@
   void SetArgument(const AtomicString&);
   void SetSelectorList(std::unique_ptr<CSSSelectorList>);
   void SetPartNames(std::unique_ptr<Vector<AtomicString>>);
-  void SetContainsPseudoInsideHasPseudoClass();
 
   void SetNth(int a, int b);
   bool MatchNth(unsigned count) const;
@@ -492,7 +486,6 @@
         selector_list_;  // Used for :-webkit-any and :not
     std::unique_ptr<Vector<AtomicString>>
         part_names_;  // Used for ::part() selectors.
-    bool contains_pseudo_inside_has_pseudo_class_;  // Used for :has() selectors
 
    private:
     RareData(const AtomicString& value);
diff --git a/third_party/blink/renderer/core/css/parser/css_parser_selector.cc b/third_party/blink/renderer/core/css/parser/css_parser_selector.cc
index 737a646..362d4fa 100644
--- a/third_party/blink/renderer/core/css/parser/css_parser_selector.cc
+++ b/third_party/blink/renderer/core/css/parser/css_parser_selector.cc
@@ -58,10 +58,6 @@
   selector_->SetSelectorList(std::move(selector_list));
 }
 
-void CSSParserSelector::SetContainsPseudoInsideHasPseudoClass() {
-  selector_->SetContainsPseudoInsideHasPseudoClass();
-}
-
 void CSSParserSelector::AppendTagHistory(
     CSSSelector::RelationType relation,
     std::unique_ptr<CSSParserSelector> selector) {
diff --git a/third_party/blink/renderer/core/css/parser/css_parser_selector.h b/third_party/blink/renderer/core/css/parser/css_parser_selector.h
index 2e244c65..fff17e1 100644
--- a/third_party/blink/renderer/core/css/parser/css_parser_selector.h
+++ b/third_party/blink/renderer/core/css/parser/css_parser_selector.h
@@ -79,7 +79,6 @@
       Vector<std::unique_ptr<CSSParserSelector>>& selector_vector);
   void SetSelectorList(std::unique_ptr<CSSSelectorList>);
   void SetAtomics(std::unique_ptr<CSSSelectorList>);
-  void SetContainsPseudoInsideHasPseudoClass();
 
   bool IsHostPseudoSelector() const;
 
diff --git a/third_party/blink/renderer/core/css/parser/css_selector_parser.cc b/third_party/blink/renderer/core/css/parser/css_selector_parser.cc
index 8c2f145..8f8074b 100644
--- a/third_party/blink/renderer/core/css/parser/css_selector_parser.cc
+++ b/third_party/blink/renderer/core/css/parser/css_selector_parser.cc
@@ -784,9 +784,6 @@
   bool has_arguments = token.GetType() == kFunctionToken;
   selector->UpdatePseudoType(value, *context_, has_arguments, context_->Mode());
 
-  if (UNLIKELY(is_inside_has_argument_))
-    found_pseudo_in_has_argument_ = true;
-
   if (selector->Match() == CSSSelector::kPseudoElement) {
     switch (selector->GetPseudoType()) {
       case CSSSelector::kPseudoBefore:
@@ -880,11 +877,6 @@
       DisallowPseudoElementsScope scope(this);
       base::AutoReset<bool> resist_namespace(&resist_default_namespace_, true);
 
-      base::AutoReset<bool> is_inside_has_argument(&is_inside_has_argument_,
-                                                   true);
-      base::AutoReset<bool> found_pseudo_in_has_argument(
-          &found_pseudo_in_has_argument_, false);
-
       std::unique_ptr<CSSSelectorList> selector_list =
           std::make_unique<CSSSelectorList>();
       *selector_list = ConsumeRelativeSelectorList(block);
@@ -892,8 +884,6 @@
         return nullptr;
 
       selector->SetSelectorList(std::move(selector_list));
-      if (UNLIKELY(found_pseudo_in_has_argument_))
-        selector->SetContainsPseudoInsideHasPseudoClass();
       return selector;
     }
     case CSSSelector::kPseudoNot: {
diff --git a/third_party/blink/renderer/core/css/parser/css_selector_parser.h b/third_party/blink/renderer/core/css/parser/css_selector_parser.h
index b227d97..b2047bd 100644
--- a/third_party/blink/renderer/core/css/parser/css_selector_parser.h
+++ b/third_party/blink/renderer/core/css/parser/css_selector_parser.h
@@ -150,11 +150,6 @@
   // the default namespace is '*' while this flag is true.
   bool ignore_default_namespace_ = false;
 
-  // The 'found_pseudo_in_has_argument flag is true when we found any pseudo in
-  // ':has()' argument while parsing.
-  bool found_pseudo_in_has_argument_ = false;
-  bool is_inside_has_argument_ = false;
-
   class DisallowPseudoElementsScope {
     STACK_ALLOCATED();
 
diff --git a/third_party/blink/renderer/core/css/resolver/style_adjuster.cc b/third_party/blink/renderer/core/css/resolver/style_adjuster.cc
index 03f8dd08..fc69f68 100644
--- a/third_party/blink/renderer/core/css/resolver/style_adjuster.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_adjuster.cc
@@ -874,14 +874,6 @@
       style.OverflowY() != EOverflow::kVisible)
     AdjustOverflow(style, element);
 
-  // overflow-clip-margin only applies if 'overflow: clip' is set along both
-  // axis or 'contain: paint'.
-  if (!style.ContainsPaint() && !(style.OverflowX() == EOverflow::kClip &&
-                                  style.OverflowY() == EOverflow::kClip)) {
-    style.SetOverflowClipMargin(
-        ComputedStyleInitialValues::InitialOverflowClipMargin());
-  }
-
   if (StopPropagateTextDecorations(style, element))
     style.ClearAppliedTextDecorations();
   else
diff --git a/third_party/blink/renderer/core/css/resolver/style_adjuster_test.cc b/third_party/blink/renderer/core/css/resolver/style_adjuster_test.cc
index 10bb5dd..0783d5c0 100644
--- a/third_party/blink/renderer/core/css/resolver/style_adjuster_test.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_adjuster_test.cc
@@ -110,77 +110,6 @@
             target->GetComputedStyle()->GetEffectiveTouchAction());
 }
 
-TEST_F(StyleAdjusterTest, AdjustOverflow) {
-  ScopedOverflowClipForTest overflow_clip_feature_enabler(true);
-  GetDocument().SetBaseURLOverride(KURL("http://test.com"));
-  SetBodyInnerHTML(R"HTML(
-    <div id='clipauto' style='overflow-x: clip; overflow-y: auto;
-         overflow-clip-margin: 1px;'>
-    <div id='autoclip' style='overflow-x: auto; overflow-y: clip;
-         overflow-clip-margin: 1px;'>
-    <div id='clipclip' style='overflow-x: clip; overflow-y: clip;
-         overflow-clip-margin: 1px;'>
-    <div id='visclip' style='overflow-x: visible; overflow-y: clip;
-         overflow-clip-margin: 1px;'>
-    <div id='clipvis' style='overflow-x: clip; overflow-y: visible;
-         overflow-clip-margin: 1px;'>
-    <div id='hiddenvis' style='overflow-x: hidden; overflow-y: visible;
-         overflow-clip-margin: 1px;'>
-    <div id='vishidden' style='overflow-x: visible; overflow-y: hidden;
-         overflow-clip-margin: 1px;'>
-    <div id='containpaint' style='contain: paint; overflow-clip-margin: 1px;'>
-    </div>
-  )HTML");
-  UpdateAllLifecyclePhasesForTest();
-
-  Element* target = GetDocument().getElementById("clipauto");
-  ASSERT_TRUE(target);
-  EXPECT_EQ(EOverflow::kHidden, target->GetComputedStyle()->OverflowX());
-  EXPECT_EQ(EOverflow::kAuto, target->GetComputedStyle()->OverflowY());
-  EXPECT_EQ(LayoutUnit(), target->GetComputedStyle()->OverflowClipMargin());
-
-  target = GetDocument().getElementById("autoclip");
-  ASSERT_TRUE(target);
-  EXPECT_EQ(EOverflow::kAuto, target->GetComputedStyle()->OverflowX());
-  EXPECT_EQ(EOverflow::kHidden, target->GetComputedStyle()->OverflowY());
-  EXPECT_EQ(LayoutUnit(), target->GetComputedStyle()->OverflowClipMargin());
-
-  target = GetDocument().getElementById("clipclip");
-  ASSERT_TRUE(target);
-  EXPECT_EQ(EOverflow::kClip, target->GetComputedStyle()->OverflowX());
-  EXPECT_EQ(EOverflow::kClip, target->GetComputedStyle()->OverflowY());
-  EXPECT_EQ(LayoutUnit(1), target->GetComputedStyle()->OverflowClipMargin());
-
-  target = GetDocument().getElementById("visclip");
-  ASSERT_TRUE(target);
-  EXPECT_EQ(EOverflow::kVisible, target->GetComputedStyle()->OverflowX());
-  EXPECT_EQ(EOverflow::kClip, target->GetComputedStyle()->OverflowY());
-  EXPECT_EQ(LayoutUnit(), target->GetComputedStyle()->OverflowClipMargin());
-
-  target = GetDocument().getElementById("clipvis");
-  ASSERT_TRUE(target);
-  EXPECT_EQ(EOverflow::kClip, target->GetComputedStyle()->OverflowX());
-  EXPECT_EQ(EOverflow::kVisible, target->GetComputedStyle()->OverflowY());
-  EXPECT_EQ(LayoutUnit(), target->GetComputedStyle()->OverflowClipMargin());
-
-  target = GetDocument().getElementById("vishidden");
-  ASSERT_TRUE(target);
-  EXPECT_EQ(EOverflow::kAuto, target->GetComputedStyle()->OverflowX());
-  EXPECT_EQ(EOverflow::kHidden, target->GetComputedStyle()->OverflowY());
-  EXPECT_EQ(LayoutUnit(), target->GetComputedStyle()->OverflowClipMargin());
-
-  target = GetDocument().getElementById("hiddenvis");
-  ASSERT_TRUE(target);
-  EXPECT_EQ(EOverflow::kHidden, target->GetComputedStyle()->OverflowX());
-  EXPECT_EQ(EOverflow::kAuto, target->GetComputedStyle()->OverflowY());
-  EXPECT_EQ(LayoutUnit(), target->GetComputedStyle()->OverflowClipMargin());
-
-  target = GetDocument().getElementById("containpaint");
-  ASSERT_TRUE(target);
-  EXPECT_TRUE(target->GetComputedStyle()->ContainsPaint());
-  EXPECT_EQ(LayoutUnit(1), target->GetComputedStyle()->OverflowClipMargin());
-}
-
 TEST_F(StyleAdjusterTest, TouchActionContentEditableArea) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitWithFeatures({::features::kSwipeToMoveCursor}, {});
diff --git a/third_party/blink/renderer/core/css/selector_checker.cc b/third_party/blink/renderer/core/css/selector_checker.cc
index 145ee2d..da5378a 100644
--- a/third_party/blink/renderer/core/css/selector_checker.cc
+++ b/third_party/blink/renderer/core/css/selector_checker.cc
@@ -664,8 +664,6 @@
   sub_context.scope = context.scope;
   // sub_context.is_inside_visited_link is false (by default) to disable
   // :visited matching when it is in the :has argument
-  sub_context.is_inside_has_pseudo_class = true;
-  sub_context.pseudo_has_in_rightmost_compound = context.in_rightmost_compound;
 
   DCHECK(context.selector->SelectorList());
   for (const CSSSelector* selector = context.selector->SelectorList()->First();
@@ -817,6 +815,69 @@
   return false;
 }
 
+namespace {
+
+struct HasPseudoClassArgumentInfo {
+  bool contains_pseudo = false;
+  bool contains_hover = false;
+  bool contains_active = false;
+  bool contains_focus = false;
+  bool contains_focus_visible = false;
+
+  explicit HasPseudoClassArgumentInfo(const CSSSelectorList* selector_list) {
+    DCHECK(selector_list);
+
+    for (const CSSSelector* relative_selector = selector_list->First();
+         relative_selector;
+         relative_selector = CSSSelectorList::Next(*relative_selector)) {
+      DCHECK(relative_selector);
+
+      for (const CSSSelector* simple = relative_selector;
+           simple &&
+           simple->GetPseudoType() != CSSSelector::kPseudoRelativeLeftmost;
+           simple = simple->TagHistory()) {
+        switch (simple->GetPseudoType()) {
+          case CSSSelector::kPseudoUnknown:
+            break;
+          case CSSSelector::kPseudoHover:
+            contains_pseudo = true;
+            contains_hover = true;
+            break;
+          case CSSSelector::kPseudoActive:
+            contains_pseudo = true;
+            contains_active = true;
+            break;
+          case CSSSelector::kPseudoFocus:
+          case CSSSelector::kPseudoFocusWithin:
+            contains_pseudo = true;
+            contains_focus = true;
+            break;
+          case CSSSelector::kPseudoFocusVisible:
+            contains_pseudo = true;
+            contains_focus_visible = true;
+            break;
+          default:
+            contains_pseudo = true;
+            break;
+        }
+      }
+    }
+  }
+
+  void SetDynamicRestyleFlagsForHas(ComputedStyle* style) {
+    if (contains_hover)
+      style->SetAncestorsAffectedByHoverInHas();
+    if (contains_active)
+      style->SetAncestorsAffectedByActiveInHas();
+    if (contains_focus)
+      style->SetAncestorsAffectedByFocusInHas();
+    if (contains_focus_visible)
+      style->SetAncestorsAffectedByFocusVisibleInHas();
+  }
+};
+
+}  // namespace
+
 bool SelectorChecker::CheckPseudoClass(const SelectorCheckingContext& context,
                                        MatchResult& result) const {
   Element& element = *context.element;
@@ -1012,38 +1073,19 @@
       }
       return element.IsDragged();
     case CSSSelector::kPseudoFocus:
-      if (mode_ == kResolvingStyle) {
-        if (UNLIKELY(context.is_inside_has_pseudo_class)) {
-          if (context.pseudo_has_in_rightmost_compound)
-            element_style_->SetAncestorsAffectedByFocusInHas();
-        } else {
-          if (!context.in_rightmost_compound)
-            element.SetChildrenOrSiblingsAffectedByFocus();
-        }
-      }
+      if (mode_ == kResolvingStyle && !context.in_rightmost_compound)
+        element.SetChildrenOrSiblingsAffectedByFocus();
       return MatchesFocusPseudoClass(element);
     case CSSSelector::kPseudoFocusVisible:
-      if (mode_ == kResolvingStyle) {
-        if (UNLIKELY(context.is_inside_has_pseudo_class)) {
-          if (context.pseudo_has_in_rightmost_compound)
-            element_style_->SetAncestorsAffectedByFocusVisibleInHas();
-        } else {
-          if (!context.in_rightmost_compound)
-            element.SetChildrenOrSiblingsAffectedByFocusVisible();
-        }
-      }
+      if (mode_ == kResolvingStyle && !context.in_rightmost_compound)
+        element.SetChildrenOrSiblingsAffectedByFocusVisible();
       return MatchesFocusVisiblePseudoClass(element);
     case CSSSelector::kPseudoFocusWithin:
       if (mode_ == kResolvingStyle) {
-        if (UNLIKELY(context.is_inside_has_pseudo_class)) {
-          if (context.pseudo_has_in_rightmost_compound)
-            element_style_->SetAncestorsAffectedByFocusInHas();
-        } else {
-          if (context.in_rightmost_compound)
-            element_style_->SetAffectedByFocusWithin();
-          else
-            element.SetChildrenOrSiblingsAffectedByFocusWithin();
-        }
+        if (context.in_rightmost_compound)
+          element_style_->SetAffectedByFocusWithin();
+        else
+          element.SetChildrenOrSiblingsAffectedByFocusWithin();
       }
       probe::ForcePseudoState(&element, CSSSelector::kPseudoFocusWithin,
                               &force_pseudo_state);
@@ -1052,15 +1094,10 @@
       return element.HasFocusWithin();
     case CSSSelector::kPseudoHover:
       if (mode_ == kResolvingStyle) {
-        if (UNLIKELY(context.is_inside_has_pseudo_class)) {
-          if (context.pseudo_has_in_rightmost_compound)
-            element_style_->SetAncestorsAffectedByHoverInHas();
-        } else {
-          if (context.in_rightmost_compound)
-            element_style_->SetAffectedByHover();
-          else
-            element.SetChildrenOrSiblingsAffectedByHover();
-        }
+        if (context.in_rightmost_compound)
+          element_style_->SetAffectedByHover();
+        else
+          element.SetChildrenOrSiblingsAffectedByHover();
       }
       if (!ShouldMatchHoverOrActive(context))
         return false;
@@ -1071,15 +1108,10 @@
       return element.IsHovered();
     case CSSSelector::kPseudoActive:
       if (mode_ == kResolvingStyle) {
-        if (UNLIKELY(context.is_inside_has_pseudo_class)) {
-          if (context.pseudo_has_in_rightmost_compound)
-            element_style_->SetAncestorsAffectedByActiveInHas();
-        } else {
-          if (context.in_rightmost_compound)
-            element_style_->SetAffectedByActive();
-          else
-            element.SetChildrenOrSiblingsAffectedByActive();
-        }
+        if (context.in_rightmost_compound)
+          element_style_->SetAffectedByActive();
+        else
+          element.SetChildrenOrSiblingsAffectedByActive();
       }
       if (!ShouldMatchHoverOrActive(context))
         return false;
@@ -1289,8 +1321,14 @@
           // ':has()' selector.
           element_style_->SetAffectedByHas();
           element_style_->SetAncestorsAffectedByHas();
-          if (selector.ContainsPseudoInsideHasPseudoClass())
+
+          // TODO(blee@igalia.com) Move the logic in HasPseudoClassArgumentInfo
+          // to HasArgumentMatchContext
+          HasPseudoClassArgumentInfo argument(selector.SelectorList());
+          if (argument.contains_pseudo)
             element_style_->SetAffectedByPseudoInHas();
+          if (context.in_rightmost_compound)
+            argument.SetDynamicRestyleFlagsForHas(element_style_);
         }
         // TODO(blee@igalia.com) non-terminal ':has() is not supported yet
       }
diff --git a/third_party/blink/renderer/core/css/selector_checker.h b/third_party/blink/renderer/core/css/selector_checker.h
index 038dc688..1b9deca 100644
--- a/third_party/blink/renderer/core/css/selector_checker.h
+++ b/third_party/blink/renderer/core/css/selector_checker.h
@@ -122,8 +122,6 @@
     bool in_nested_complex_selector = false;
     bool is_inside_visited_link = false;
     const ContainerNode* relative_leftmost_element = nullptr;
-    bool is_inside_has_pseudo_class = false;
-    bool pseudo_has_in_rightmost_compound = true;
   };
 
   struct MatchResult {
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index 3f1e732..6974ba2 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -3740,9 +3740,6 @@
 void Document::DispatchUnloadEvents(
     SecurityOrigin* committing_origin,
     absl::optional<Document::UnloadEventTiming>* unload_timing) {
-  // TODO(crbug.com/1161996): Remove this VLOG once the investigation is done.
-  VLOG(1) << "Document::DispatchUnloadEvents() URL = " << Url();
-
   PluginScriptForbiddenScope forbid_plugin_destructor_scripting;
   PageDismissalScope in_page_dismissal;
   if (parser_)
@@ -3818,9 +3815,6 @@
 
   GetFrame()->Loader().SaveScrollAnchor();
 
-  // TODO(crbug.com/1161996): Remove this VLOG once the investigation is done.
-  VLOG(1) << "Actually dispatching an UnloadEvent: URL = " << Url();
-
   load_event_progress_ = kUnloadEventInProgress;
   Event& unload_event = *Event::Create(event_type_names::kUnload);
   const base::TimeTicks unload_event_start = base::TimeTicks::Now();
diff --git a/third_party/blink/renderer/core/frame/local_frame_mojo_handler.cc b/third_party/blink/renderer/core/frame/local_frame_mojo_handler.cc
index 9c780940..2736937 100644
--- a/third_party/blink/renderer/core/frame/local_frame_mojo_handler.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_mojo_handler.cc
@@ -1109,9 +1109,6 @@
     mojom::blink::LocalMainFrame::ClosePageCallback completion_callback) {
   SECURITY_CHECK(frame_->IsMainFrame());
 
-  // TODO(crbug.com/1161996): Remove this VLOG once the investigation is done.
-  VLOG(1) << "LocalFrame::ClosePage() URL = " << frame_->GetDocument()->Url();
-
   // There are two ways to close a page:
   //
   // 1/ Via webview()->Close() that currently sets the WebView's delegate_ to
diff --git a/third_party/blink/renderer/core/layout/layout_inline_test.cc b/third_party/blink/renderer/core/layout/layout_inline_test.cc
index 1000e0c..df1490cf 100644
--- a/third_party/blink/renderer/core/layout/layout_inline_test.cc
+++ b/third_party/blink/renderer/core/layout/layout_inline_test.cc
@@ -719,7 +719,9 @@
             GetLayoutObjectByElementId("target5")
                 ->AbsoluteBoundingBoxRectHandlingEmptyInline());
   // This rect covers the overflowing images and continuations.
-  EXPECT_EQ(PhysicalRect(390, 70, 160, 100),
+  const int height =
+      RuntimeEnabledFeatures::LayoutNGBlockInInlineEnabled() ? 400 : 100;
+  EXPECT_EQ(PhysicalRect(390, 70, 160, height),
             GetLayoutObjectByElementId("target6")
                 ->AbsoluteBoundingBoxRectHandlingEmptyInline());
 }
diff --git a/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.cc
index 83d0d6b..c48f09394 100644
--- a/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/grid/ng_grid_layout_algorithm.cc
@@ -3433,7 +3433,7 @@
   wtf_size_t breakpoint_row_set_index;
   bool has_subsequent_children;
 
-  const LayoutUnit fragmentainer_space =
+  LayoutUnit fragmentainer_space =
       FragmentainerSpaceAtBfcStart(ConstraintSpace());
   const LayoutUnit previous_consumed_block_size =
       BreakToken() ? BreakToken()->ConsumedBlockSize() : LayoutUnit();
@@ -3633,12 +3633,20 @@
   auto ShiftBreakpointIntoNextFragmentainer = [&]() -> bool {
     if (breakpoint_row_set_index == kNotFound)
       return false;
-    DCHECK_NE(fragmentainer_space, kIndefiniteSize);
 
     const LayoutUnit fragment_relative_row_offset =
         grid_geometry->row_geometry.sets[breakpoint_row_set_index].offset +
         (*row_offset_adjustments)[breakpoint_row_set_index] -
         previous_consumed_block_size;
+
+    // We may be within the initial column-balancing pass (where we have an
+    // indefinite fragmentainer size). If we have a forced break, re-run
+    // |PlaceItems()| assuming the breakpoint offset is the fragmentainer size.
+    if (fragmentainer_space == kIndefiniteSize) {
+      fragmentainer_space = fragment_relative_row_offset;
+      return true;
+    }
+
     const LayoutUnit row_offset_delta =
         fragmentainer_space - fragment_relative_row_offset;
 
diff --git a/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.cc
index f5372fc..18794b8 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.cc
@@ -646,10 +646,35 @@
   // block-start border/padding), or in the outer fragmentation context), it may
   // be better to push some of the content to the next outer fragmentainer and
   // retry there.
-  bool may_have_more_space_in_next_outer_fragmentainer =
-      may_resume_in_next_outer_fragmentainer &&
-      (intrinsic_block_size_ ||
-       ConstraintSpace().FragmentainerOffsetAtBfc() > LayoutUnit());
+  bool may_have_more_space_in_next_outer_fragmentainer = false;
+  if (may_resume_in_next_outer_fragmentainer) {
+    if (intrinsic_block_size_) {
+      may_have_more_space_in_next_outer_fragmentainer = true;
+    } else {
+      if (ConstraintSpace().FragmentainerOffsetAtBfc() > LayoutUnit()) {
+        if (!BreakToken()) {
+          may_have_more_space_in_next_outer_fragmentainer = true;
+        } else {
+          // We have already added a break for this multicol container, but we
+          // think that we're not at the start of the fragmentainer. This may be
+          // the case if we have a nested floated multicol container with a
+          // non-zero block-start margin. Float margins are unbreakable and are
+          // never truncated. So we may have broken before it, and kept the
+          // margin for the next fragmentainer (as we should), but since a
+          // fragmentainer offset is a border-box offset, we'll now think that
+          // we're past the fragmentainer start (the margin edge is at the
+          // start, but the border edge isn't). This is not ideal, but for now
+          // just avoid breaking before *again*, which would cause an infinite
+          // loop with no content progress. DCHECK that this is the situation.
+          // TODO(mstensho): Find a solution to this. We may have added an
+          // unnecessary break now. Pushing the float to the next fragmentainer
+          // may not have solved anything.
+          DCHECK(Node().IsFloating());
+          DCHECK(BreakToken()->IsBreakBefore());
+        }
+      }
+    }
+  }
 
   scoped_refptr<const NGLayoutResult> result;
   absl::optional<NGBreakAppeal> min_break_appeal;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc b/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc
index ad8c08a..4d6f909d 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc
@@ -713,9 +713,6 @@
     return self_rect;
 
   const OverflowClipAxes overflow_clip_axes = GetOverflowClipAxes();
-  // overflow_clip_margin should only be set if we clip both axes.
-  DCHECK(overflow_clip_axes == kOverflowClipBothAxis ||
-         !style.OverflowClipMargin());
   if (overflow_clip_axes == kNoOverflowClip) {
     return UnionRect(self_rect,
                      ink_overflow_.Contents(InkOverflowType(), Size()));
diff --git a/third_party/blink/renderer/modules/beacon/navigator_beacon.cc b/third_party/blink/renderer/modules/beacon/navigator_beacon.cc
index f2e4e23..003327e 100644
--- a/third_party/blink/renderer/modules/beacon/navigator_beacon.cc
+++ b/third_party/blink/renderer/modules/beacon/navigator_beacon.cc
@@ -78,16 +78,9 @@
   ExecutionContext* execution_context = ExecutionContext::From(script_state);
   KURL url = execution_context->CompleteURL(url_string);
   if (!CanSendBeacon(execution_context, url, exception_state)) {
-    // TODO(crbug.com/1161996): Remove this VLOG once the investigation is done.
-    VLOG(1) << "Cannot send a beacon to " << url.ElidedString()
-            << ", initiator = " << execution_context->Url();
     return false;
   }
 
-  // TODO(crbug.com/1161996): Remove this VLOG once the investigation is done.
-  VLOG(1) << "Send a beacon to " << url.ElidedString()
-          << ", initiator = " << execution_context->Url();
-
   bool allowed;
   LocalFrame* frame = GetSupplementable()->DomWindow()->GetFrame();
   if (data) {
diff --git a/third_party/blink/renderer/modules/webcodecs/video_encoder.cc b/third_party/blink/renderer/modules/webcodecs/video_encoder.cc
index a8d1bd2..27c0367 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_encoder.cc
+++ b/third_party/blink/renderer/modules/webcodecs/video_encoder.cc
@@ -69,6 +69,10 @@
 #include "third_party/blink/renderer/platform/scheduler/public/thread.h"
 #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
 
+#if BUILDFLAG(ENABLE_LIBAOM)
+#include "media/video/av1_video_encoder.h"
+#endif
+
 #if BUILDFLAG(ENABLE_OPENH264)
 #include "media/video/openh264_video_encoder.h"
 #endif
@@ -287,9 +291,41 @@
   return result;
 }
 
+const base::Feature kWebCodecsAv1Encoding{"WebCodecsAv1Encoding",
+                                          base::FEATURE_DISABLED_BY_DEFAULT};
+
 bool VerifyCodecSupportStatic(VideoEncoderTraits::ParsedConfig* config,
                               ExceptionState* exception_state) {
   switch (config->codec) {
+    case media::VideoCodec::kAV1:
+      if (!base::FeatureList::IsEnabled(kWebCodecsAv1Encoding)) {
+        if (exception_state) {
+          exception_state->ThrowDOMException(
+              DOMExceptionCode::kNotSupportedError,
+              "AV1 encoding is not supported yet.");
+        }
+        return false;
+      }
+
+      if (config->options.scalability_mode.has_value()) {
+        if (exception_state) {
+          exception_state->ThrowDOMException(
+              DOMExceptionCode::kNotSupportedError,
+              "SVC encoding is not supported for AV1 yet.");
+        }
+        return false;
+      }
+      if (config->profile !=
+          media::VideoCodecProfile::AV1PROFILE_PROFILE_MAIN) {
+        if (exception_state) {
+          exception_state->ThrowDOMException(
+              DOMExceptionCode::kNotSupportedError, "Unsupported av1 profile.");
+        }
+        return false;
+      }
+      // TODO(crbug.com/1208280): Check for supported AV1 levels
+      break;
+
     case media::VideoCodec::kVP8:
       break;
 
@@ -455,6 +491,14 @@
                                                              callback_runner_));
 }
 
+std::unique_ptr<media::VideoEncoder> CreateAv1VideoEncoder() {
+#if BUILDFLAG(ENABLE_LIBAOM)
+  return std::make_unique<media::Av1VideoEncoder>();
+#else
+  return nullptr;
+#endif  // BUILDFLAG(ENABLE_LIBAOM)
+}
+
 std::unique_ptr<media::VideoEncoder> CreateVpxVideoEncoder() {
 #if BUILDFLAG(ENABLE_LIBVPX)
   return std::make_unique<media::VpxVideoEncoder>();
@@ -480,6 +524,10 @@
     return nullptr;
   std::unique_ptr<media::VideoEncoder> result;
   switch (codec) {
+    case media::VideoCodec::kAV1:
+      result = CreateAv1VideoEncoder();
+      self->UpdateEncoderLog("Av1VideoEncoder", false);
+      break;
     case media::VideoCodec::kVP8:
     case media::VideoCodec::kVP9:
       result = CreateVpxVideoEncoder();
diff --git a/third_party/blink/renderer/modules/webgpu/dawn_enum_conversions.cc b/third_party/blink/renderer/modules/webgpu/dawn_enum_conversions.cc
index 43cfde6..b6a44f9 100644
--- a/third_party/blink/renderer/modules/webgpu/dawn_enum_conversions.cc
+++ b/third_party/blink/renderer/modules/webgpu/dawn_enum_conversions.cc
@@ -299,6 +299,9 @@
   if (webgpu_enum == "depth24plus-stencil8") {
     return WGPUTextureFormat_Depth24PlusStencil8;
   }
+  if (webgpu_enum == "depth16unorm") {
+    return WGPUTextureFormat_Depth16Unorm;
+  }
 
   // Block Compression (BC) formats
   if (webgpu_enum == "bc1-rgba-unorm") {
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_texture_descriptor.idl b/third_party/blink/renderer/modules/webgpu/gpu_texture_descriptor.idl
index 7fcb5bedc..c560a7c 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_texture_descriptor.idl
+++ b/third_party/blink/renderer/modules/webgpu/gpu_texture_descriptor.idl
@@ -66,6 +66,7 @@
     "depth32float",
     "depth24plus",
     "depth24plus-stencil8",
+    "depth16unorm",
     /* Block Compression (BC) formats */
     "bc1-rgba-unorm",
     "bc1-rgba-unorm-srgb",
diff --git a/third_party/blink/renderer/modules/xr/OWNERS b/third_party/blink/renderer/modules/xr/OWNERS
index 9b95aec..d95d28c 100644
--- a/third_party/blink/renderer/modules/xr/OWNERS
+++ b/third_party/blink/renderer/modules/xr/OWNERS
@@ -1,3 +1 @@
-alcooper@chromium.org
-bajones@chromium.org
-klausw@chromium.org
+file://components/webxr/OWNERS
diff --git a/third_party/blink/renderer/modules/xr/xr_bounded_reference_space.cc b/third_party/blink/renderer/modules/xr/xr_bounded_reference_space.cc
index 58ab5185..7de6466 100644
--- a/third_party/blink/renderer/modules/xr/xr_bounded_reference_space.cc
+++ b/third_party/blink/renderer/modules/xr/xr_bounded_reference_space.cc
@@ -47,7 +47,7 @@
 
 XRBoundedReferenceSpace::~XRBoundedReferenceSpace() = default;
 
-void XRBoundedReferenceSpace::EnsureUpdated() {
+void XRBoundedReferenceSpace::EnsureUpdated() const {
   // Check first to see if the stage parameters have updated since the last
   // call. We only need to update the transform and bounds if it has.
   if (stage_parameters_id_ == session()->StageParametersId())
@@ -89,10 +89,15 @@
     offset_bounds_geometry_.clear();
   }
 
-  DispatchEvent(*XRReferenceSpaceEvent::Create(event_type_names::kReset, this));
+  // DispatchEvent inherited from core/dom/events/event_target.h isn't const.
+  XRBoundedReferenceSpace* mutable_this =
+      const_cast<XRBoundedReferenceSpace*>(this);
+  mutable_this->DispatchEvent(
+      *XRReferenceSpaceEvent::Create(event_type_names::kReset, mutable_this));
 }
 
-absl::optional<TransformationMatrix> XRBoundedReferenceSpace::MojoFromNative() {
+absl::optional<TransformationMatrix> XRBoundedReferenceSpace::MojoFromNative()
+    const {
   EnsureUpdated();
 
   if (!mojo_from_bounded_native_)
@@ -118,7 +123,7 @@
 }
 
 XRBoundedReferenceSpace* XRBoundedReferenceSpace::cloneWithOriginOffset(
-    XRRigidTransform* origin_offset) {
+    XRRigidTransform* origin_offset) const {
   return MakeGarbageCollected<XRBoundedReferenceSpace>(this->session(),
                                                        origin_offset);
 }
diff --git a/third_party/blink/renderer/modules/xr/xr_bounded_reference_space.h b/third_party/blink/renderer/modules/xr/xr_bounded_reference_space.h
index 006d723..055509a2 100644
--- a/third_party/blink/renderer/modules/xr/xr_bounded_reference_space.h
+++ b/third_party/blink/renderer/modules/xr/xr_bounded_reference_space.h
@@ -22,7 +22,7 @@
   XRBoundedReferenceSpace(XRSession*, XRRigidTransform*);
   ~XRBoundedReferenceSpace() override;
 
-  absl::optional<TransformationMatrix> MojoFromNative() override;
+  absl::optional<TransformationMatrix> MojoFromNative() const override;
 
   HeapVector<Member<DOMPointReadOnly>> boundsGeometry();
 
@@ -32,13 +32,13 @@
 
  private:
   XRBoundedReferenceSpace* cloneWithOriginOffset(
-      XRRigidTransform* origin_offset) override;
+      XRRigidTransform* origin_offset) const override;
 
-  void EnsureUpdated();
+  void EnsureUpdated() const;
 
-  HeapVector<Member<DOMPointReadOnly>> offset_bounds_geometry_;
-  std::unique_ptr<TransformationMatrix> mojo_from_bounded_native_;
-  uint32_t stage_parameters_id_ = 0;
+  mutable HeapVector<Member<DOMPointReadOnly>> offset_bounds_geometry_;
+  mutable std::unique_ptr<TransformationMatrix> mojo_from_bounded_native_;
+  mutable uint32_t stage_parameters_id_ = 0;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/xr/xr_grip_space.cc b/third_party/blink/renderer/modules/xr/xr_grip_space.cc
index 8b79d29..7153a0e 100644
--- a/third_party/blink/renderer/modules/xr/xr_grip_space.cc
+++ b/third_party/blink/renderer/modules/xr/xr_grip_space.cc
@@ -14,7 +14,7 @@
 XRGripSpace::XRGripSpace(XRSession* session, XRInputSource* source)
     : XRSpace(session), input_source_(source) {}
 
-absl::optional<TransformationMatrix> XRGripSpace::MojoFromNative() {
+absl::optional<TransformationMatrix> XRGripSpace::MojoFromNative() const {
   // Grip is only available when using tracked pointer for input.
   if (input_source_->TargetRayMode() !=
       device::mojom::XRTargetRayMode::POINTING) {
diff --git a/third_party/blink/renderer/modules/xr/xr_grip_space.h b/third_party/blink/renderer/modules/xr/xr_grip_space.h
index e81b3f8..297f7b9 100644
--- a/third_party/blink/renderer/modules/xr/xr_grip_space.h
+++ b/third_party/blink/renderer/modules/xr/xr_grip_space.h
@@ -16,7 +16,7 @@
  public:
   XRGripSpace(XRSession* session, XRInputSource* input_source);
 
-  absl::optional<TransformationMatrix> MojoFromNative() override;
+  absl::optional<TransformationMatrix> MojoFromNative() const override;
   bool EmulatedPosition() const override;
 
   device::mojom::blink::XRNativeOriginInformationPtr NativeOrigin()
diff --git a/third_party/blink/renderer/modules/xr/xr_joint_space.cc b/third_party/blink/renderer/modules/xr/xr_joint_space.cc
index 24cb004..4a910e7 100644
--- a/third_party/blink/renderer/modules/xr/xr_joint_space.cc
+++ b/third_party/blink/renderer/modules/xr/xr_joint_space.cc
@@ -27,7 +27,7 @@
       radius_(radius),
       handedness_(handedness) {}
 
-absl::optional<TransformationMatrix> XRJointSpace::MojoFromNative() {
+absl::optional<TransformationMatrix> XRJointSpace::MojoFromNative() const {
   return *mojo_from_joint_space_.get();
 }
 
@@ -45,7 +45,7 @@
   return false;
 }
 
-XRPose* XRJointSpace::getPose(XRSpace* other_space) {
+XRPose* XRJointSpace::getPose(const XRSpace* other_space) const {
   // If any of the spaces belonging to the same XRHand return null when
   // populating the pose, all the spaces of that XRHand must also return
   // null when populating the pose.
diff --git a/third_party/blink/renderer/modules/xr/xr_joint_space.h b/third_party/blink/renderer/modules/xr/xr_joint_space.h
index f14c756..cf5d71f 100644
--- a/third_party/blink/renderer/modules/xr/xr_joint_space.h
+++ b/third_party/blink/renderer/modules/xr/xr_joint_space.h
@@ -34,11 +34,11 @@
   const String jointName() const;
   device::mojom::XRHandedness handedness() const { return handedness_; }
 
-  absl::optional<TransformationMatrix> MojoFromNative() override;
+  absl::optional<TransformationMatrix> MojoFromNative() const override;
   device::mojom::blink::XRNativeOriginInformationPtr NativeOrigin()
       const override;
   bool EmulatedPosition() const override;
-  XRPose* getPose(XRSpace* other_space) override;
+  XRPose* getPose(const XRSpace* other_space) const override;
 
   void UpdateTracking(std::unique_ptr<TransformationMatrix> mojo_from_joint,
                       float radius);
diff --git a/third_party/blink/renderer/modules/xr/xr_object_space.h b/third_party/blink/renderer/modules/xr/xr_object_space.h
index d537dc4..10813a9 100644
--- a/third_party/blink/renderer/modules/xr/xr_object_space.h
+++ b/third_party/blink/renderer/modules/xr/xr_object_space.h
@@ -36,7 +36,7 @@
         object_(object),
         is_stationary_(object->IsStationary()) {}
 
-  absl::optional<TransformationMatrix> MojoFromNative() override {
+  absl::optional<TransformationMatrix> MojoFromNative() const override {
     return object_->MojoFromObject();
   }
 
diff --git a/third_party/blink/renderer/modules/xr/xr_reference_space.cc b/third_party/blink/renderer/modules/xr/xr_reference_space.cc
index 1eaf218..8bc4885c 100644
--- a/third_party/blink/renderer/modules/xr/xr_reference_space.cc
+++ b/third_party/blink/renderer/modules/xr/xr_reference_space.cc
@@ -51,7 +51,7 @@
 
 XRReferenceSpace::~XRReferenceSpace() = default;
 
-XRPose* XRReferenceSpace::getPose(XRSpace* other_space) {
+XRPose* XRReferenceSpace::getPose(const XRSpace* other_space) const {
   if (type_ == ReferenceSpaceType::kViewer) {
     absl::optional<TransformationMatrix> other_offset_from_viewer =
         other_space->OffsetFromViewer();
@@ -71,7 +71,7 @@
   }
 }
 
-void XRReferenceSpace::SetMojoFromFloor() {
+void XRReferenceSpace::SetMojoFromFloor() const {
   const device::mojom::blink::VRStageParametersPtr& stage_parameters =
       session()->GetStageParameters();
 
@@ -86,7 +86,7 @@
   stage_parameters_id_ = session()->StageParametersId();
 }
 
-absl::optional<TransformationMatrix> XRReferenceSpace::MojoFromNative() {
+absl::optional<TransformationMatrix> XRReferenceSpace::MojoFromNative() const {
   DVLOG(3) << __func__ << ": type_=" << type_;
 
   switch (type_) {
@@ -139,7 +139,7 @@
 }
 
 absl::optional<TransformationMatrix> XRReferenceSpace::NativeFromViewer(
-    const absl::optional<TransformationMatrix>& mojo_from_viewer) {
+    const absl::optional<TransformationMatrix>& mojo_from_viewer) const {
   if (type_ == ReferenceSpaceType::kViewer) {
     // Special case for viewer space, always return an identity matrix
     // explicitly. In theory the default behavior of multiplying NativeFromMojo
@@ -159,11 +159,11 @@
   return native_from_viewer;
 }
 
-TransformationMatrix XRReferenceSpace::NativeFromOffsetMatrix() {
+TransformationMatrix XRReferenceSpace::NativeFromOffsetMatrix() const {
   return origin_offset_->TransformMatrix();
 }
 
-TransformationMatrix XRReferenceSpace::OffsetFromNativeMatrix() {
+TransformationMatrix XRReferenceSpace::OffsetFromNativeMatrix() const {
   return origin_offset_->InverseTransformMatrix();
 }
 
@@ -184,7 +184,7 @@
 }
 
 XRReferenceSpace* XRReferenceSpace::getOffsetReferenceSpace(
-    XRRigidTransform* additional_offset) {
+    XRRigidTransform* additional_offset) const {
   auto matrix =
       NativeFromOffsetMatrix().Multiply(additional_offset->TransformMatrix());
 
@@ -193,7 +193,7 @@
 }
 
 XRReferenceSpace* XRReferenceSpace::cloneWithOriginOffset(
-    XRRigidTransform* origin_offset) {
+    XRRigidTransform* origin_offset) const {
   return MakeGarbageCollected<XRReferenceSpace>(this->session(), origin_offset,
                                                 type_);
 }
@@ -219,8 +219,10 @@
 
 void XRReferenceSpace::OnReset() {
   if (type_ != ReferenceSpaceType::kViewer) {
-    DispatchEvent(
-        *XRReferenceSpaceEvent::Create(event_type_names::kReset, this));
+    // DispatchEvent inherited from core/dom/events/event_target.h isn't const.
+    XRReferenceSpace* mutable_this = const_cast<XRReferenceSpace*>(this);
+    mutable_this->DispatchEvent(
+        *XRReferenceSpaceEvent::Create(event_type_names::kReset, mutable_this));
   }
 }
 
diff --git a/third_party/blink/renderer/modules/xr/xr_reference_space.h b/third_party/blink/renderer/modules/xr/xr_reference_space.h
index 42bd15f..4b5ba73 100644
--- a/third_party/blink/renderer/modules/xr/xr_reference_space.h
+++ b/third_party/blink/renderer/modules/xr/xr_reference_space.h
@@ -31,22 +31,23 @@
   ~XRReferenceSpace() override;
 
   absl::optional<TransformationMatrix> NativeFromViewer(
-      const absl::optional<TransformationMatrix>& mojo_from_viewer) override;
+      const absl::optional<TransformationMatrix>& mojo_from_viewer)
+      const override;
 
-  absl::optional<TransformationMatrix> MojoFromNative() override;
+  absl::optional<TransformationMatrix> MojoFromNative() const override;
 
   bool IsStationary() const override;
 
-  TransformationMatrix NativeFromOffsetMatrix() override;
-  TransformationMatrix OffsetFromNativeMatrix() override;
+  TransformationMatrix NativeFromOffsetMatrix() const override;
+  TransformationMatrix OffsetFromNativeMatrix() const override;
 
   // We override getPose to ensure that the viewer pose in viewer space returns
   // the identity pose instead of the result of multiplying inverse matrices.
-  XRPose* getPose(XRSpace* other_space) override;
+  XRPose* getPose(const XRSpace* other_space) const override;
 
   device::mojom::blink::XRReferenceSpaceType GetType() const;
 
-  XRReferenceSpace* getOffsetReferenceSpace(XRRigidTransform* transform);
+  XRReferenceSpace* getOffsetReferenceSpace(XRRigidTransform* transform) const;
 
   DEFINE_ATTRIBUTE_EVENT_LISTENER(reset, kReset)
 
@@ -60,16 +61,16 @@
 
  private:
   virtual XRReferenceSpace* cloneWithOriginOffset(
-      XRRigidTransform* origin_offset);
+      XRRigidTransform* origin_offset) const;
 
   // Updates the mojo_from_floor_ transform to match the one present in the
   // latest display parameters of a session.
-  void SetMojoFromFloor();
+  void SetMojoFromFloor() const;
 
-  uint32_t stage_parameters_id_ = 0;
+  mutable uint32_t stage_parameters_id_ = 0;
 
   // Floor from mojo (aka local-floor_from_mojo) transform.
-  std::unique_ptr<TransformationMatrix> mojo_from_floor_;
+  mutable std::unique_ptr<TransformationMatrix> mojo_from_floor_;
   Member<XRRigidTransform> origin_offset_;
   device::mojom::blink::XRReferenceSpaceType type_;
 };
diff --git a/third_party/blink/renderer/modules/xr/xr_space.cc b/third_party/blink/renderer/modules/xr/xr_space.cc
index a740dfe..8f0732f1 100644
--- a/third_party/blink/renderer/modules/xr/xr_space.cc
+++ b/third_party/blink/renderer/modules/xr/xr_space.cc
@@ -17,7 +17,7 @@
 XRSpace::~XRSpace() = default;
 
 absl::optional<TransformationMatrix> XRSpace::NativeFromViewer(
-    const absl::optional<TransformationMatrix>& mojo_from_viewer) {
+    const absl::optional<TransformationMatrix>& mojo_from_viewer) const {
   if (!mojo_from_viewer)
     return absl::nullopt;
 
@@ -31,17 +31,17 @@
   return native_from_mojo;
 }
 
-TransformationMatrix XRSpace::NativeFromOffsetMatrix() {
+TransformationMatrix XRSpace::NativeFromOffsetMatrix() const {
   TransformationMatrix identity;
   return identity;
 }
 
-TransformationMatrix XRSpace::OffsetFromNativeMatrix() {
+TransformationMatrix XRSpace::OffsetFromNativeMatrix() const {
   TransformationMatrix identity;
   return identity;
 }
 
-absl::optional<TransformationMatrix> XRSpace::MojoFromOffsetMatrix() {
+absl::optional<TransformationMatrix> XRSpace::MojoFromOffsetMatrix() const {
   auto maybe_mojo_from_native = MojoFromNative();
   if (!maybe_mojo_from_native) {
     return absl::nullopt;
@@ -53,7 +53,7 @@
   return maybe_mojo_from_native;
 }
 
-absl::optional<TransformationMatrix> XRSpace::NativeFromMojo() {
+absl::optional<TransformationMatrix> XRSpace::NativeFromMojo() const {
   absl::optional<TransformationMatrix> mojo_from_native = MojoFromNative();
   if (!mojo_from_native)
     return absl::nullopt;
@@ -66,7 +66,7 @@
   return session()->EmulatedPosition();
 }
 
-XRPose* XRSpace::getPose(XRSpace* other_space) {
+XRPose* XRSpace::getPose(const XRSpace* other_space) const {
   DVLOG(2) << __func__ << ": ToString()=" << ToString()
            << ", other_space->ToString()=" << other_space->ToString();
 
@@ -102,7 +102,7 @@
       EmulatedPosition() || other_space->EmulatedPosition());
 }
 
-absl::optional<TransformationMatrix> XRSpace::OffsetFromViewer() {
+absl::optional<TransformationMatrix> XRSpace::OffsetFromViewer() const {
   absl::optional<TransformationMatrix> native_from_viewer =
       NativeFromViewer(session()->GetMojoFrom(
           device::mojom::blink::XRReferenceSpaceType::kViewer));
diff --git a/third_party/blink/renderer/modules/xr/xr_space.h b/third_party/blink/renderer/modules/xr/xr_space.h
index bf3bc1814..d7a642b 100644
--- a/third_party/blink/renderer/modules/xr/xr_space.h
+++ b/third_party/blink/renderer/modules/xr/xr_space.h
@@ -38,12 +38,12 @@
   // Unless noted otherwise, all data returned over vr_service.mojom interfaces
   // is expressed in mojo space coordinates.
   // Returns nullopt if computing a transform is not possible.
-  virtual absl::optional<TransformationMatrix> MojoFromNative() = 0;
+  virtual absl::optional<TransformationMatrix> MojoFromNative() const = 0;
 
   // Convenience method to try to get the inverse of the above. This will return
   // the pose of the mojo origin in this space's native origin.
   // Returns nullopt if computing a transform is not possible.
-  absl::optional<TransformationMatrix> NativeFromMojo();
+  absl::optional<TransformationMatrix> NativeFromMojo() const;
 
   // Gets the viewer pose in the native coordinates of this space, corresponding
   // to a transform from viewer coordinates to this space's native coordinates.
@@ -54,22 +54,22 @@
   // instead of something near to, but not quite, identity.
   // Returns nullopt if computing a transform is not possible.
   virtual absl::optional<TransformationMatrix> NativeFromViewer(
-      const absl::optional<TransformationMatrix>& mojo_from_viewer);
+      const absl::optional<TransformationMatrix>& mojo_from_viewer) const;
 
   // Convenience method for calling NativeFromViewer with the current
   // MojoFromViewer of the session associated with this space. This also handles
   // the multiplication of OffsetFromNative onto the result of NativeFromViewer.
   // Returns nullopt if computing a transform is not possible.
-  absl::optional<TransformationMatrix> OffsetFromViewer();
+  absl::optional<TransformationMatrix> OffsetFromViewer() const;
 
   // Return origin offset matrix, aka native_origin_from_offset_space.
-  virtual TransformationMatrix NativeFromOffsetMatrix();
-  virtual TransformationMatrix OffsetFromNativeMatrix();
+  virtual TransformationMatrix NativeFromOffsetMatrix() const;
+  virtual TransformationMatrix OffsetFromNativeMatrix() const;
 
   // Returns transformation from offset space to mojo space. Convenience method,
   // returns MojoFromNative() * NativeFromOffsetMatrix() or nullopt if computing
   // a transform is not possible.
-  absl::optional<TransformationMatrix> MojoFromOffsetMatrix();
+  absl::optional<TransformationMatrix> MojoFromOffsetMatrix() const;
 
   // Returns true when invoked on a space that is deemed stationary, false
   // otherwise (this means that the space is considered dynamic). Stationary
@@ -87,7 +87,7 @@
   // Gets the pose of this space's origin in |other_space|. This is a transform
   // that maps from this space to the other's space, or in other words:
   // other_from_this.
-  virtual XRPose* getPose(XRSpace* other_space);
+  virtual XRPose* getPose(const XRSpace* other_space) const;
 
   XRSession* session() const { return session_; }
 
diff --git a/third_party/blink/renderer/modules/xr/xr_target_ray_space.cc b/third_party/blink/renderer/modules/xr/xr_target_ray_space.cc
index 372cff7..ccac469 100644
--- a/third_party/blink/renderer/modules/xr/xr_target_ray_space.cc
+++ b/third_party/blink/renderer/modules/xr/xr_target_ray_space.cc
@@ -16,7 +16,7 @@
 XRTargetRaySpace::XRTargetRaySpace(XRSession* session, XRInputSource* source)
     : XRSpace(session), input_source_(source) {}
 
-absl::optional<TransformationMatrix> XRTargetRaySpace::MojoFromNative() {
+absl::optional<TransformationMatrix> XRTargetRaySpace::MojoFromNative() const {
   auto mojo_from_viewer = session()->GetMojoFrom(
       device::mojom::blink::XRReferenceSpaceType::kViewer);
   switch (input_source_->TargetRayMode()) {
diff --git a/third_party/blink/renderer/modules/xr/xr_target_ray_space.h b/third_party/blink/renderer/modules/xr/xr_target_ray_space.h
index 5a212fa..c8553c1a 100644
--- a/third_party/blink/renderer/modules/xr/xr_target_ray_space.h
+++ b/third_party/blink/renderer/modules/xr/xr_target_ray_space.h
@@ -16,7 +16,7 @@
  public:
   XRTargetRaySpace(XRSession* session, XRInputSource* input_space);
 
-  absl::optional<TransformationMatrix> MojoFromNative() override;
+  absl::optional<TransformationMatrix> MojoFromNative() const override;
   bool EmulatedPosition() const override;
 
   device::mojom::blink::XRNativeOriginInformationPtr NativeOrigin()
diff --git a/third_party/blink/renderer/platform/graphics/gpu/OWNERS b/third_party/blink/renderer/platform/graphics/gpu/OWNERS
index 1bac549..7f3941f 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/OWNERS
+++ b/third_party/blink/renderer/platform/graphics/gpu/OWNERS
@@ -10,6 +10,7 @@
 # WebXR
 bajones@chromium.org
 klausw@chromium.org
+per-file xr_*=file://components/webxr/OWNERS
 
 # SharedGpuContext, ImageLayerBridge, etc.
 sunnyps@chromium.org
diff --git a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
index 0bdb559c..a892500 100644
--- a/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
+++ b/third_party/blink/web_tests/FlagExpectations/disable-layout-ng
@@ -13,6 +13,7 @@
 # Tests that fail in legacy but pass in NG
 
 # ====== New tests from wpt-importer added here ======
+crbug.com/626703 external/wpt/css/css-overflow/scrollbar-gutter-vertical-lr-002.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-writing-modes/inline-box-orthogonal-child-with-margins.html [ Failure ]
 crbug.com/626703 virtual/system-color-compute/external/wpt/css/css-color/oklch-008.html [ Failure ]
 crbug.com/626703 external/wpt/selection/textcontrols/onselectionchange-content-attribute.html [ Timeout ]
diff --git a/third_party/blink/web_tests/FlagExpectations/disable-site-isolation-trials b/third_party/blink/web_tests/FlagExpectations/disable-site-isolation-trials
index 87cb0563..1eb709b 100644
--- a/third_party/blink/web_tests/FlagExpectations/disable-site-isolation-trials
+++ b/third_party/blink/web_tests/FlagExpectations/disable-site-isolation-trials
@@ -52,4 +52,3 @@
 crbug.com/626703 external/wpt/infrastructure/channels/test_serialize.html [ Timeout ]
 crbug.com/626703 external/wpt/resource-timing/entries-for-network-errors.sub.https.html [ Timeout ]
 crbug.com/626703 virtual/plz-dedicated-worker/external/wpt/resource-timing/entries-for-network-errors.sub.https.html [ Timeout ]
-crbug.com/626703 wpt_internal/webid/get-argument-validation.https.html [ Timeout ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 2cffe2e..cd4f632 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -2812,6 +2812,40 @@
 crbug.com/626703 [ Fuchsia ] external/wpt/dom/historical.html [ Skip ]
 
 # ====== New tests from wpt-importer added here ======
+crbug.com/626703 [ Mac11-arm64 ] external/wpt/content-security-policy/inside-worker/dedicatedworker-script-src.html [ Timeout ]
+crbug.com/626703 external/wpt/css/css-color/color-mix-percents-01.html [ Failure ]
+crbug.com/626703 external/wpt/css/css-color/color-mix-percents-02.html [ Failure ]
+crbug.com/626703 [ Mac10.14 ] external/wpt/css/css-overflow/scrollbar-gutter-002.html [ Failure ]
+crbug.com/626703 [ Mac10.15 ] external/wpt/css/css-overflow/scrollbar-gutter-002.html [ Failure ]
+crbug.com/626703 [ Mac11 ] external/wpt/css/css-overflow/scrollbar-gutter-002.html [ Failure ]
+crbug.com/626703 [ Mac11-arm64 ] external/wpt/css/css-overflow/scrollbar-gutter-002.html [ Failure ]
+crbug.com/626703 [ Mac10.14 ] external/wpt/css/css-overflow/scrollbar-gutter-vertical-lr-002.html [ Failure ]
+crbug.com/626703 [ Mac10.15 ] external/wpt/css/css-overflow/scrollbar-gutter-vertical-lr-002.html [ Failure ]
+crbug.com/626703 [ Mac11 ] external/wpt/css/css-overflow/scrollbar-gutter-vertical-lr-002.html [ Failure ]
+crbug.com/626703 [ Mac11-arm64 ] external/wpt/css/css-overflow/scrollbar-gutter-vertical-lr-002.html [ Failure ]
+crbug.com/626703 [ Mac11 ] external/wpt/css/css-overflow/scrollbar-gutter-vertical-rl-002.html [ Failure Crash ]
+crbug.com/626703 [ Mac10.14 ] external/wpt/css/css-overflow/scrollbar-gutter-vertical-rl-002.html [ Failure ]
+crbug.com/626703 [ Mac10.15 ] external/wpt/css/css-overflow/scrollbar-gutter-vertical-rl-002.html [ Failure ]
+crbug.com/626703 [ Mac11-arm64 ] external/wpt/css/css-overflow/scrollbar-gutter-vertical-rl-002.html [ Failure ]
+crbug.com/626703 [ Mac11-arm64 ] external/wpt/css/css-ui/compute-kind-widget-fallback-props-revert-001.html [ Failure ]
+crbug.com/626703 [ Mac11-arm64 ] external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-background-color-001.html [ Failure ]
+crbug.com/626703 [ Mac11-arm64 ] external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-background-position-001.html [ Failure ]
+crbug.com/626703 [ Mac11-arm64 ] external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-border-block-start-color-001.html [ Failure ]
+crbug.com/626703 [ Mac11-arm64 ] external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-border-bottom-left-radius-001.html [ Failure ]
+crbug.com/626703 [ Mac11-arm64 ] external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-border-bottom-width-001.html [ Failure ]
+crbug.com/626703 [ Mac11-arm64 ] external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-border-end-start-radius-001.html [ Failure ]
+crbug.com/626703 [ Mac11-arm64 ] external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-border-image-source-001.html [ Failure ]
+crbug.com/626703 [ Mac11-arm64 ] external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-border-inline-end-width-001.html [ Failure ]
+crbug.com/626703 [ Mac11-arm64 ] external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-border-inline-start-style-001.html [ Failure ]
+crbug.com/626703 [ Mac11-arm64 ] external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-border-left-style-001.html [ Failure ]
+crbug.com/626703 [ Mac11-arm64 ] external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-border-top-right-radius-001.html [ Failure ]
+crbug.com/626703 [ Mac11-arm64 ] external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-border-top-style-001.html [ Failure ]
+crbug.com/626703 [ Mac11-arm64 ] external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-border-top-width-001.html [ Failure ]
+crbug.com/626703 [ Mac11-arm64 ] virtual/no-auto-wpt-origin-isolation/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-no-subdomain-child2-yes-subdomainport.sub.https.html [ Failure Crash ]
+crbug.com/626703 [ Mac11-arm64 ] virtual/no-auto-wpt-origin-isolation/external/wpt/html/browsers/origin/origin-keyed-agent-clusters/2-iframes/parent-no-child1-yes-subdomain-child2-no-subdomain.sub.https.html [ Failure Crash ]
+crbug.com/626703 [ Mac11-arm64 ] virtual/plz-dedicated-worker/external/wpt/content-security-policy/inside-worker/serviceworker-report-only.https.sub.html [ Timeout ]
+crbug.com/626703 virtual/system-color-compute/external/wpt/css/css-color/color-mix-percents-01.html [ Failure ]
+crbug.com/626703 virtual/system-color-compute/external/wpt/css/css-color/color-mix-percents-02.html [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/css/css-ui/compute-kind-widget-fallback-props-revert-001.html [ Failure ]
 crbug.com/626703 [ Mac10.12 ] external/wpt/css/css-ui/compute-kind-widget-fallback-props-revert-001.html [ Failure ]
 crbug.com/626703 [ Mac10.13 ] external/wpt/css/css-ui/compute-kind-widget-fallback-props-revert-001.html [ Failure ]
@@ -2931,7 +2965,7 @@
 crbug.com/626703 [ Mac10.15 ] external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-border-start-start-radius-001.html [ Failure ]
 crbug.com/626703 [ Mac11-arm64 ] external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-border-start-start-radius-001.html [ Failure ]
 crbug.com/626703 [ Win ] external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-border-start-start-radius-001.html [ Failure ]
-crbug.com/626703 [ Mac11 ] external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-border-start-start-radius-001.html [ Timeout Failure ]
+crbug.com/626703 [ Mac11 ] external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-border-start-start-radius-001.html [ Failure Timeout ]
 crbug.com/626703 external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-border-top-color-001.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-border-top-left-radius-001.html [ Failure ]
 crbug.com/626703 [ Linux ] external/wpt/css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-border-top-right-radius-001.html [ Failure ]
@@ -4904,6 +4938,7 @@
 crbug.com/614667 external/wpt/css/css-break/grid/grid-item-fragmentation-034.html [ Failure ]
 crbug.com/614667 external/wpt/css/css-break/grid/grid-item-fragmentation-037.html [ Failure ]
 crbug.com/614667 external/wpt/css/css-break/grid/grid-item-fragmentation-038.html [ Failure ]
+crbug.com/614667 external/wpt/css/css-break/grid/grid-item-fragmentation-039.html [ Failure ]
 crbug.com/1066629 external/wpt/css/css-break/hit-test-transformed.html [ Failure ]
 crbug.com/1058792 external/wpt/css/css-break/transform-007.html [ Failure ]
 crbug.com/1224888 external/wpt/css/css-break/transform-009.html [ Failure ]
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index e62a6f1..30b7bbc 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -978,11 +978,6 @@
     "args": ["--enable-fake-no-alloc-direct-call-for-testing"]
   },
   {
-    "prefix": "webid",
-    "bases": ["wpt_internal/webid"],
-    "args": ["--enable-features=WebID"]
-  },
-  {
     "prefix": "document-transition",
     "bases": ["wpt_internal/document-transition"],
     "args": ["--enable-blink-features=DocumentTransition",
diff --git a/third_party/blink/web_tests/android/ChromeWPTOverrideExpectations b/third_party/blink/web_tests/android/ChromeWPTOverrideExpectations
index fa5feb01..c33e20cc 100644
--- a/third_party/blink/web_tests/android/ChromeWPTOverrideExpectations
+++ b/third_party/blink/web_tests/android/ChromeWPTOverrideExpectations
@@ -396,7 +396,7 @@
 crbug.com/1050754 external/wpt/bluetooth/requestDevice/acceptAllDevices/device-with-name.https.window.html [ Failure ]
 crbug.com/1050754 external/wpt/bluetooth/requestDevice/acceptAllDevices/optional-services-missing.https.window.html [ Failure ]
 crbug.com/1050754 external/wpt/bluetooth/requestDevice/acceptAllDevices/optional-services-present.https.window.html [ Failure ]
-crbug.com/1050754 external/wpt/bluetooth/requestDevice/blocklisted-service-in-filter.https.html [ Failure ]
+crbug.com/1050754 external/wpt/bluetooth/requestDevice/blocklisted-service-in-filter.https.window.html [ Failure ]
 crbug.com/1050754 external/wpt/bluetooth/requestDevice/blocklisted-service-in-optionalServices.https.window.html [ Failure ]
 crbug.com/1050754 external/wpt/bluetooth/requestDevice/canonicalizeFilter/device-name-longer-than-29-bytes.https.window.html [ Failure ]
 crbug.com/1050754 external/wpt/bluetooth/requestDevice/canonicalizeFilter/empty-filter.https.window.html [ Failure ]
diff --git a/third_party/blink/web_tests/android/WebLayerWPTOverrideExpectations b/third_party/blink/web_tests/android/WebLayerWPTOverrideExpectations
index 7e3e98dd..6516bbfe 100644
--- a/third_party/blink/web_tests/android/WebLayerWPTOverrideExpectations
+++ b/third_party/blink/web_tests/android/WebLayerWPTOverrideExpectations
@@ -457,7 +457,7 @@
 crbug.com/1050754 external/wpt/bluetooth/requestDevice/acceptAllDevices/device-with-name.https.window.html [ Failure ]
 crbug.com/1050754 external/wpt/bluetooth/requestDevice/acceptAllDevices/optional-services-missing.https.window.html [ Failure ]
 crbug.com/1050754 external/wpt/bluetooth/requestDevice/acceptAllDevices/optional-services-present.https.window.html [ Failure ]
-crbug.com/1050754 external/wpt/bluetooth/requestDevice/blocklisted-service-in-filter.https.html [ Failure ]
+crbug.com/1050754 external/wpt/bluetooth/requestDevice/blocklisted-service-in-filter.https.window.html [ Failure ]
 crbug.com/1050754 external/wpt/bluetooth/requestDevice/blocklisted-service-in-optionalServices.https.window.html [ Failure ]
 crbug.com/1050754 external/wpt/bluetooth/requestDevice/canonicalizeFilter/data-prefix-and-mask-size.https.window.html [ Failure ]
 crbug.com/1050754 external/wpt/bluetooth/requestDevice/canonicalizeFilter/dataPrefix-buffer-is-detached.https.window.html [ Failure ]
diff --git a/third_party/blink/web_tests/android/WebviewWPTExpectations b/third_party/blink/web_tests/android/WebviewWPTExpectations
index 56fe9fa..070b3ab 100644
--- a/third_party/blink/web_tests/android/WebviewWPTExpectations
+++ b/third_party/blink/web_tests/android/WebviewWPTExpectations
@@ -355,7 +355,7 @@
 crbug.com/1050754 external/wpt/bluetooth/requestDevice/acceptAllDevices/device-with-name.https.window.html [ Failure ]
 crbug.com/1050754 external/wpt/bluetooth/requestDevice/acceptAllDevices/optional-services-missing.https.window.html [ Failure ]
 crbug.com/1050754 external/wpt/bluetooth/requestDevice/acceptAllDevices/optional-services-present.https.window.html [ Failure ]
-crbug.com/1050754 external/wpt/bluetooth/requestDevice/blocklisted-service-in-filter.https.html [ Failure ]
+crbug.com/1050754 external/wpt/bluetooth/requestDevice/blocklisted-service-in-filter.https.window.html [ Failure ]
 crbug.com/1050754 external/wpt/bluetooth/requestDevice/blocklisted-service-in-optionalServices.https.window.html [ Failure ]
 crbug.com/1050754 external/wpt/bluetooth/requestDevice/canonicalizeFilter/device-name-longer-than-29-bytes.https.window.html [ Failure ]
 crbug.com/1050754 external/wpt/bluetooth/requestDevice/canonicalizeFilter/empty-filter.https.window.html [ Failure ]
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index 87d65c7..ab6a072 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -1685,6 +1685,13 @@
         {}
        ]
       ],
+      "preserve3d-svg-foreign-object-hit-test.svg": [
+       "14c772cb2d24ae89cc2bce0e2ed2dad83d3ae6d9",
+       [
+        null,
+        {}
+       ]
+      ],
       "transform-marquee-resize-div-image-001.html": [
        "8bdbb984f3a1cb8a500a93faa4efa4a633b81826",
        [
@@ -82565,6 +82572,32 @@
        {}
       ]
      ],
+     "color-mix-percents-01.html": [
+      "425ef9a63621ef635fc3e800ee92a59dd32978d0",
+      [
+       null,
+       [
+        [
+         "/css/css-color/color-mix-percents-01-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "color-mix-percents-02.html": [
+      "5b1a4cfd9d92f311b7a6619017f29fb42c62f269",
+      [
+       null,
+       [
+        [
+         "/css/css-color/color-mix-percents-01-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "composited-filters-under-opacity.html": [
       "f613748af3ac5bb59c9fa207937ae6207d71062e",
       [
@@ -135907,6 +135940,19 @@
        {}
       ]
      ],
+     "overflow-clip-margin-008.html": [
+      "fabe669a49c69319c9fc689cc9f22af6b6fe41f9",
+      [
+       null,
+       [
+        [
+         "/css/css-overflow/overflow-clip-margin-008-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "overflow-clip-margin-invalidation.html": [
       "d9c87a34a53f17451b0d3ae8c2071971d1df3a94",
       [
@@ -136089,6 +136135,71 @@
        {}
       ]
      ],
+     "scrollbar-gutter-002.html": [
+      "129eb2c08590c98505e36f63da173d1c8a4bf98d",
+      [
+       null,
+       [
+        [
+         "/css/css-overflow/scrollbar-gutter-002-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "scrollbar-gutter-dynamic-001.html": [
+      "3dcb32048cd7a1b94eb7cd30be4a381a5f168eab",
+      [
+       null,
+       [
+        [
+         "/css/css-overflow/scrollbar-gutter-dynamic-001-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "scrollbar-gutter-rtl-002.html": [
+      "8d0376defdfc69ecb44fc2134c81573e553db2bb",
+      [
+       null,
+       [
+        [
+         "/css/css-overflow/scrollbar-gutter-rtl-002-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "scrollbar-gutter-vertical-lr-002.html": [
+      "9e5dcd299eba99e4ab6f077149a841011805c0a5",
+      [
+       null,
+       [
+        [
+         "/css/css-overflow/scrollbar-gutter-vertical-lr-002-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "scrollbar-gutter-vertical-rl-002.html": [
+      "d68d4e5d3cba0353da274f9c75c558c0a8324092",
+      [
+       null,
+       [
+        [
+         "/css/css-overflow/scrollbar-gutter-vertical-rl-002-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "text-overflow-ellipsis-001.html": [
       "41c11d581e982c7de2d51d38b18decd99e8a60b3",
       [
@@ -231585,7 +231696,7 @@
       []
      ],
      "fedcm-mock.js": [
-      "2fa351446c2601580c525f4ef75b01f2ba2a19c8",
+      "407ca8d7702adbf6d7dbc100323eb46267048631",
       []
      ],
      "federatedcredential-get.html": [
@@ -241348,6 +241459,10 @@
       "77ccec0d3adfc6bb9840f529ad5e574792c96cef",
       []
      ],
+     "color-mix-percents-01-ref.html": [
+      "2d9a1380b3889a446356725b75db485e55992cc1",
+      []
+     ],
      "color-resolving-hwb-expected.txt": [
       "4c702545c17052ddbdda669b4a8191116b9306f8",
       []
@@ -258742,6 +258857,10 @@
       "9f562d67fe4249d09c98062a74dcb2b656123056",
       []
      ],
+     "overflow-clip-margin-008-ref.html": [
+      "3af9e4e951d9d35eafc72fd7593be20a7c94dbde",
+      []
+     ],
      "overflow-clip-margin-invalidation-ref.html": [
       "1ec2a5ce0a21c8dd578b3fcfde702307e4e2a9a8",
       []
@@ -258986,10 +259105,26 @@
       "782ffab9da1c1cb55b4488e7fc8f913dd18857f3",
       []
      ],
+     "scrollbar-gutter-002-ref.html": [
+      "ae51682c75920743d4ae69fffe8b566a3758c7a4",
+      []
+     ],
      "scrollbar-gutter-dynamic-001-ref.html": [
       "5f2f7bff26420e949093a822d8995d9fcc3f1f51",
       []
      ],
+     "scrollbar-gutter-rtl-002-ref.html": [
+      "e6510a081353db996c4ead14ad6a167b90d48297",
+      []
+     ],
+     "scrollbar-gutter-vertical-lr-002-ref.html": [
+      "cb7c647da43c842e68c9ca8e9ca24cd9708324dd",
+      []
+     ],
+     "scrollbar-gutter-vertical-rl-002-ref.html": [
+      "7dbadf995ed89bc3032c4ca1b53e1cebecd6237e",
+      []
+     ],
      "text-overflow-ellipsis-editing-input-ref.html": [
       "3902072bc58b7bf62290edafbce1b735288af716",
       []
@@ -354116,6 +354251,13 @@
       {}
      ]
     ],
+    "fedcm-logout.https.html": [
+     "e4f83b0f90e1a684c254235e6222b299b8647cbc",
+     [
+      null,
+      {}
+     ]
+    ],
     "fedcm-revoke.https.html": [
      "c014724b8173e47e9d9ffb00a08537055c3f8003",
      [
@@ -354124,7 +354266,7 @@
      ]
     ],
     "fedcm.https.html": [
-     "71ff0ab70f49258f5e6d701b8e7034933ae1ae42",
+     "618f0ec3c24e5cca8512748d972357e03590d77e",
      [
       null,
       {}
@@ -559606,15 +559748,6 @@
       ]
      ]
     },
-    "css-overflow": {
-     "scrollbar-gutter-dynamic-001.html": [
-      "d6451ff995f5b522626e8e05eaed33d3e49e5f9b",
-      [
-       null,
-       {}
-      ]
-     ]
-    },
     "css-ruby": {
      "rbc-rtc-basic-001.html": [
       "e086181ab938b54ff40d2363d801380d1b9ada9b",
diff --git a/third_party/blink/web_tests/external/wpt/bluetooth/requestDevice/blocklisted-service-in-filter.https.html b/third_party/blink/web_tests/external/wpt/bluetooth/requestDevice/blocklisted-service-in-filter.https.html
deleted file mode 100644
index cb2f989..0000000
--- a/third_party/blink/web_tests/external/wpt/bluetooth/requestDevice/blocklisted-service-in-filter.https.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<!DOCTYPE html>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/resources/testdriver.js"></script>
-<script src="/resources/testdriver-vendor.js"></script>
-<script src="/bluetooth/resources/bluetooth-test.js"></script>
-<script src="/bluetooth/resources/bluetooth-fake-devices.js"></script>
-<script>
-'use strict';
-const test_desc = 'Reject with SecurityError if requesting a blocklisted ' +
-    'service.';
-const expected = new DOMException(
-    'requestDevice() called with a filter containing a blocklisted UUID. ' +
-        'https://goo.gl/4NeimX',
-    'SecurityError');
-
-bluetooth_test(
-    () =>
-        setUpPreconnectedDevice({knownServiceUUIDs: ['human_interface_device']})
-            .then(
-                () => assert_promise_rejects_with_message(
-                    requestDeviceWithTrustedClick(
-                        {filters: [{services: ['human_interface_device']}]}),
-                    expected, 'Requesting blocklisted service rejects.')),
-    test_desc);
-</script>
diff --git a/third_party/blink/web_tests/external/wpt/bluetooth/requestDevice/blocklisted-service-in-filter.https.window.js b/third_party/blink/web_tests/external/wpt/bluetooth/requestDevice/blocklisted-service-in-filter.https.window.js
new file mode 100644
index 0000000..48f3d13
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/bluetooth/requestDevice/blocklisted-service-in-filter.https.window.js
@@ -0,0 +1,23 @@
+// META: script=/resources/testharness.js
+// META: script=/resources/testharnessreport.js
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+// META: script=/bluetooth/resources/bluetooth-test.js
+// META: script=/bluetooth/resources/bluetooth-fake-devices.js
+'use strict';
+const test_desc = 'Reject with SecurityError if requesting a blocklisted ' +
+    'service.';
+const expected = new DOMException(
+    'requestDevice() called with a filter containing a blocklisted UUID. ' +
+        'https://goo.gl/4NeimX',
+    'SecurityError');
+
+bluetooth_test(async () => {
+  await assert_promise_rejects_with_message(
+      setUpPreconnectedFakeDevice({
+        fakeDeviceOptions: {knownServiceUUIDs: ['human_interface_device']},
+        requestDeviceOptions:
+            {filters: [{services: ['human_interface_device']}]}
+      }),
+      expected, 'Requesting blocklisted service rejects.');
+}, test_desc);
diff --git a/third_party/blink/web_tests/external/wpt/credential-management/support/README.md b/third_party/blink/web_tests/external/wpt/credential-management/support/README.md
index f58d4cd0..886faa8 100644
--- a/third_party/blink/web_tests/external/wpt/credential-management/support/README.md
+++ b/third_party/blink/web_tests/external/wpt/credential-management/support/README.md
@@ -31,3 +31,24 @@
 - function receive(): the main/only function that can be mocked
 - function expect(): the main/only function that enables us to mock it
 - enum State {kSuccess, kTimeout}: allows you to mock success/failures
+
+## FedCM Testing
+
+`fedcm-helper.js` exposes `fedcm_test` which is a specialized
+`promise_test` which comes pre-setup with the appropriate mocking infrastructure
+to emulate platform federated auth backend. The mock is passed to the test
+function as the second parameter.
+
+Example usage:
+```
+<script type="module">
+  import {fedcm_test} from './support/fedcm-helper.js';
+
+  fedcm_test(async (t, mock) => {
+    mock.returnIdToken("a_token");
+    assert_equals("a_token", await navigator.credentials.get(options));
+  }, "Successfully obtaining a token using mock.");
+</script>
+```
+
+The chromium implementation uses the MojoJS shim.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/grid/grid-item-fragmentation-039.html b/third_party/blink/web_tests/external/wpt/css/css-break/grid/grid-item-fragmentation-039.html
new file mode 100644
index 0000000..5201c49
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/grid/grid-item-fragmentation-039.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-grid-1/#pagination">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<p>Test passes if there is a filled green square and <strong>no red</strong>.</p>
+<div style="position: relative; width: 50px; columns: 1; column-gap: 0; background: red;">
+  <div style="display: grid;">
+    <div style="background: green; height: 100px; break-after: column;"></div>
+    <div style="background: green; height: 100px;"></div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-color/color-mix-percents-01-ref.html b/third_party/blink/web_tests/external/wpt/css/css-color/color-mix-percents-01-ref.html
new file mode 100644
index 0000000..2d9a1380
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-color/color-mix-percents-01-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Color 5: color-mix</title>
+<link rel="author" title="Chris Lilley" href="mailto:chris@w3.org">
+<style>
+    .test { background-color: rgb(68.4898% 36.015% 68.3102%); width: 14em; height: 14em; margin-top: 0; margin-bottom: 0;}
+</style>
+<body>
+    <p>Test passes if you see a purple square, and no red.</p>
+    <div class="test"></div>
+</body>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/css-color/color-mix-percents-01.html b/third_party/blink/web_tests/external/wpt/css/css-color/color-mix-percents-01.html
new file mode 100644
index 0000000..425ef9a6
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-color/color-mix-percents-01.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Color 5: color-mix</title>
+<link rel="author" title="Chris Lilley" href="mailto:chris@w3.org">
+<link rel="help" href="https://drafts.csswg.org/css-color-5/#color-mix">
+<link rel="help" href="https://drafts.csswg.org/css-color-4/#interpolation-space">
+<link rel="help" href="https://drafts.csswg.org/css-color-4/#hue-interpolation">
+<link rel="match" href="./color-mix-percents-01-ref.html">
+<meta name="assert" content="missing percentages, percent normalization">
+<style>
+    .test { background-color: red; width: 14em; height: 2em; margin-top: 0; margin-bottom: 0;}
+    .t1 { background-color: rgb(68.4898% 36.015% 68.3102%); }
+    .t2 { background-color: color-mix(in lch, purple 50%, plum 50%);}
+    .t3 { background-color: color-mix(in lch, purple 50%, plum);}
+    .t4 { background-color: color-mix(in lch, purple, plum 50%); }
+    .t5 { background-color: color-mix(in lch, purple, plum);}
+    .t6 { background-color: color-mix(in lch, plum, purple);}
+    .t7 { background-color: color-mix(in lch, purple 80%, plum 80%);}
+</style>
+<body>
+    <p>Test passes if you see a purple square, and no red.</p>
+    <div class="test t1"></div>
+    <div class="test t2"></div>
+    <div class="test t3"></div>
+    <div class="test t4"></div>
+    <div class="test t5"></div>
+    <div class="test t6"></div>
+    <div class="test t7"></div>
+</body>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/css-color/color-mix-percents-02.html b/third_party/blink/web_tests/external/wpt/css/css-color/color-mix-percents-02.html
new file mode 100644
index 0000000..5b1a4cf
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-color/color-mix-percents-02.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Color 5: color-mix</title>
+<link rel="author" title="Chris Lilley" href="mailto:chris@w3.org">
+<link rel="help" href="https://drafts.csswg.org/css-color-5/#color-mix">
+<link rel="help" href="https://drafts.csswg.org/css-color-4/#interpolation-space">
+<link rel="help" href="https://drafts.csswg.org/css-color-4/#hue-interpolation">
+<link rel="match" href="./color-mix-percents-01-ref.html">
+<meta name="assert" content="percent normalization for opaque mixes">
+<style>
+    .test { background-color: red; width: 14em; height: 2em; margin-top: 0; margin-bottom: 0;}
+    .t1 { background-color: rgb(68.4898% 36.015% 68.3102%); }
+    .t2 { background-color: color-mix(in lch, purple 50%, plum 50%);}
+    .t3 { background-color: color-mix(in lch, purple 55%, plum 55%);}
+    .t4 { background-color: color-mix(in lch, purple 70%, plum 70%); }
+    .t5 { background-color: color-mix(in lch, purple 95%, plum 95%);}
+    .t6 { background-color: color-mix(in lch, purple 125%, plum 125%);}
+    .t7 { background-color: color-mix(in lch, purple 9999%, plum 9999%);}
+</style>
+<body>
+    <p>Test passes if you see a purple square, and no red.</p>
+    <div class="test t1"></div>
+    <div class="test t2"></div>
+    <div class="test t3"></div>
+    <div class="test t4"></div>
+    <div class="test t5"></div>
+    <div class="test t6"></div>
+    <div class="test t7"></div>
+</body>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/css/css-multicol/nested-floated-multicol-with-monolithic-child-crash.html b/third_party/blink/web_tests/external/wpt/css/css-multicol/nested-floated-multicol-with-monolithic-child-crash.html
new file mode 100644
index 0000000..f48ae9ff
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-multicol/nested-floated-multicol-with-monolithic-child-crash.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1280624">
+<p>PASS if no freeze or crash.</p>
+<div style="columns:2; column-fill:auto; height:100px;">
+  <div style="margin-top:10px; float:right; columns:2; column-fill:auto; width:100%;">
+    <div style="height:100px; contain:size;"></div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/overflow-clip-margin-009-ref.html b/third_party/blink/web_tests/external/wpt/css/css-overflow/overflow-clip-margin-009-ref.html
new file mode 100644
index 0000000..efbf8985
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/overflow-clip-margin-009-ref.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<html class="reftest">
+<meta charset="utf-8">
+<title>Overflow-clip-margin can be inherited even if it has no effect on specified element</title>
+<link rel="help" href="https://www.w3.org/TR/css-overflow-3/#propdef-overflow-clip-margin">
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+<style>
+.container {
+  width: 100px;
+  height: 100px;
+  overflow-clip-margin: 20px;
+  overflow: clip;
+}
+.child {
+  width: 200px;
+  height: 200px;
+  background: lightblue;
+}
+</style>
+<div class=container>
+  <div class=child></div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/overflow-clip-margin-009.html b/third_party/blink/web_tests/external/wpt/css/css-overflow/overflow-clip-margin-009.html
new file mode 100644
index 0000000..6ba9a5a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/overflow-clip-margin-009.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<html class="reftest">
+<meta charset="utf-8">
+<title>Overflow-clip-margin can be inherited even if it has no effect on specified element</title>
+<link rel="help" href="https://www.w3.org/TR/css-overflow-3/#propdef-overflow-clip-margin">
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+<link rel="match" href="overflow-clip-margin-009-ref.html">
+<style>
+.prop {
+  overflow-clip-margin: 20px;
+}
+.container {
+  width: 100px;
+  height: 100px;
+  overflow-clip-margin: inherit;
+  overflow: clip;
+}
+.child {
+  width: 200px;
+  height: 200px;
+  background: lightblue;
+}
+</style>
+<div class=prop>
+  <div class=container>
+    <div class=child></div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-gutter-002-ref.html b/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-gutter-002-ref.html
new file mode 100644
index 0000000..ae51682c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-gutter-002-ref.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<html>
+  <meta charset="utf-8">
+  <title>CSS Overflow Reference: test scrollbar-gutter with horizontal left to right content</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+
+  <style>
+  .line {
+    display: flex;
+  }
+
+  .container {
+    writing-mode: horizontal-tb;
+    direction: ltr;
+
+    box-sizing: border-box;
+    block-size: 200px;
+    inline-size: 200px;
+    margin: 10px;
+    background: deepskyblue;
+    resize: both;
+  }
+
+  .content {
+    inline-size: 100%;
+    block-size: 200%;
+    background: lightsalmon;
+  }
+  </style>
+
+  <div class="line">
+    <div class="container" style="overflow-y: auto">
+      <div class="content"></div>
+    </div>
+
+    <div class="container" style="overflow-y: scroll" id="container_scroll_stable">
+      <div class="content" id="content_scroll_stable"></div>
+    </div>
+
+    <div class="container" style="overflow-y: hidden" id="container_hidden_stable">
+      <div class="content"></div>
+    </div>
+  </div>
+
+  <div class="line">
+    <div class="container" style="overflow-y: auto;" id="container_auto_stable_both_edges">
+      <div class="content"></div>
+    </div>
+
+    <div class="container" style="overflow-y: scroll" id="container_scroll_stable_both_edges">
+      <div class="content"></div>
+    </div>
+
+    <div class="container" style="overflow-y: hidden;" id="container_hidden_stable_both_edges">
+      <div class="content"></div>
+    </div>
+  </div>
+
+  <script>
+  let container_scroll_stable = document.getElementById("container_scroll_stable");
+  let content_scroll_stable = document.getElementById("content_scroll_stable");
+  let vScrollbarWidth = (container_scroll_stable.offsetWidth - content_scroll_stable.offsetWidth);
+  let vScrollbarWidthStr = vScrollbarWidth + "px";
+
+  // Simulate scrollbar-gutter via padding on containers.
+  document.getElementById("container_hidden_stable").style.paddingInlineEnd = vScrollbarWidthStr;
+  document.getElementById("container_auto_stable_both_edges").style.paddingInlineStart = vScrollbarWidthStr;
+  document.getElementById("container_scroll_stable_both_edges").style.paddingInlineStart = vScrollbarWidthStr;
+  document.getElementById("container_hidden_stable_both_edges").style.paddingInlineStart = vScrollbarWidthStr;
+  document.getElementById("container_hidden_stable_both_edges").style.paddingInlineEnd = vScrollbarWidthStr;
+  </script>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-gutter-002.html b/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-gutter-002.html
new file mode 100644
index 0000000..129eb2c0
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-gutter-002.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+  <meta charset="utf-8">
+  <title>CSS Overflow: test scrollbar-gutter with horizontal left to right content</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-overflow-4/#scrollbar-gutter-property">
+  <link rel="match" href="scrollbar-gutter-002-ref.html">
+
+  <style>
+  .line {
+    display: flex;
+  }
+
+  .container {
+    writing-mode: horizontal-tb;
+    direction: ltr;
+
+    block-size: 200px;
+    inline-size: 200px;
+    margin: 10px;
+    background: deepskyblue;
+    resize: both;
+  }
+
+  .content {
+    inline-size: 100%;
+    block-size: 200%;
+    background: lightsalmon;
+  }
+  </style>
+
+  <div class="line">
+    <div class="container" style="overflow-y: auto; scrollbar-gutter: stable">
+      <div class="content"></div>
+    </div>
+
+    <div class="container" style="overflow-y: scroll; scrollbar-gutter: stable">
+      <div class="content"></div>
+    </div>
+
+    <div class="container" style="overflow-y: hidden; scrollbar-gutter: stable">
+      <div class="content"></div>
+    </div>
+  </div>
+
+  <div class="line">
+    <div class="container" style="overflow-y: auto; scrollbar-gutter: stable both-edges">
+      <div class="content"></div>
+    </div>
+
+    <div class="container" style="overflow-y: scroll; scrollbar-gutter: stable both-edges">
+      <div class="content"></div>
+    </div>
+
+    <div class="container" style="overflow-y: hidden; scrollbar-gutter: stable both-edges">
+      <div class="content"></div>
+    </div>
+  </div>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-gutter-dynamic-001.html b/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-gutter-dynamic-001.html
index d6451ff..3dcb320 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-gutter-dynamic-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-gutter-dynamic-001.html
@@ -3,6 +3,8 @@
 <title>CSS Overflow: scrollbar-gutter changing dynamically</title>
 <link rel="help" href="https://drafts.csswg.org/css-overflow-4/#scrollbar-gutter-property">
 <link rel="stylesheet" href="/fonts/ahem.css">
+<link rel="match" href="scrollbar-gutter-dynamic-001-ref.html">
+
 <style>
   #scroller {
     font: 100px/1 Ahem;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-gutter-rtl-002-ref.html b/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-gutter-rtl-002-ref.html
new file mode 100644
index 0000000..e6510a0
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-gutter-rtl-002-ref.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<html>
+  <meta charset="utf-8">
+  <title>CSS Overflow Reference: test scrollbar-gutter with horizontal right to left content</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+
+  <style>
+  .line {
+    display: flex;
+  }
+
+  .container {
+    writing-mode: horizontal-tb;
+    direction: rtl;
+
+    box-sizing: border-box;
+    block-size: 200px;
+    inline-size: 200px;
+    margin: 10px;
+    background: deepskyblue;
+    resize: both;
+  }
+
+  .content {
+    inline-size: 100%;
+    block-size: 200%;
+    background: lightsalmon;
+  }
+  </style>
+
+  <div class="line">
+    <div class="container" style="overflow-y: auto">
+      <div class="content"></div>
+    </div>
+
+    <div class="container" style="overflow-y: scroll" id="container_scroll_stable">
+      <div class="content" id="content_scroll_stable"></div>
+    </div>
+
+    <div class="container" style="overflow-y: hidden" id="container_hidden_stable">
+      <div class="content"></div>
+    </div>
+  </div>
+
+  <div class="line">
+    <div class="container" style="overflow-y: auto;" id="container_auto_stable_both_edges">
+      <div class="content"></div>
+    </div>
+
+    <div class="container" style="overflow-y: scroll" id="container_scroll_stable_both_edges">
+      <div class="content"></div>
+    </div>
+
+    <div class="container" style="overflow-y: hidden;" id="container_hidden_stable_both_edges">
+      <div class="content"></div>
+    </div>
+  </div>
+
+  <script>
+  let container_scroll_stable = document.getElementById("container_scroll_stable");
+  let content_scroll_stable = document.getElementById("content_scroll_stable");
+  let vScrollbarWidth = (container_scroll_stable.offsetWidth - content_scroll_stable.offsetWidth);
+  let vScrollbarWidthStr = vScrollbarWidth + "px";
+
+  // Simulate scrollbar-gutter via padding on containers.
+  document.getElementById("container_hidden_stable").style.paddingInlineEnd = vScrollbarWidthStr;
+  document.getElementById("container_auto_stable_both_edges").style.paddingInlineStart = vScrollbarWidthStr;
+  document.getElementById("container_scroll_stable_both_edges").style.paddingInlineStart = vScrollbarWidthStr;
+  document.getElementById("container_hidden_stable_both_edges").style.paddingInlineStart = vScrollbarWidthStr;
+  document.getElementById("container_hidden_stable_both_edges").style.paddingInlineEnd = vScrollbarWidthStr;
+  </script>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-gutter-rtl-002.html b/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-gutter-rtl-002.html
new file mode 100644
index 0000000..8d0376d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-gutter-rtl-002.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+  <meta charset="utf-8">
+  <title>CSS Overflow: test scrollbar-gutter with horizontal right to left content</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-overflow-4/#scrollbar-gutter-property">
+  <link rel="match" href="scrollbar-gutter-rtl-002-ref.html">
+
+  <style>
+  .line {
+    display: flex;
+  }
+
+  .container {
+    writing-mode: horizontal-tb;
+    direction: rtl;
+
+    block-size: 200px;
+    inline-size: 200px;
+    margin: 10px;
+    background: deepskyblue;
+    resize: both;
+  }
+
+  .content {
+    inline-size: 100%;
+    block-size: 200%;
+    background: lightsalmon;
+  }
+  </style>
+
+  <div class="line">
+    <div class="container" style="overflow-y: auto; scrollbar-gutter: stable">
+      <div class="content"></div>
+    </div>
+
+    <div class="container" style="overflow-y: scroll; scrollbar-gutter: stable">
+      <div class="content"></div>
+    </div>
+
+    <div class="container" style="overflow-y: hidden; scrollbar-gutter: stable">
+      <div class="content"></div>
+    </div>
+  </div>
+
+  <div class="line">
+    <div class="container" style="overflow-y: auto; scrollbar-gutter: stable both-edges">
+      <div class="content"></div>
+    </div>
+
+    <div class="container" style="overflow-y: scroll; scrollbar-gutter: stable both-edges">
+      <div class="content"></div>
+    </div>
+
+    <div class="container" style="overflow-y: hidden; scrollbar-gutter: stable both-edges">
+      <div class="content"></div>
+    </div>
+  </div>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-gutter-vertical-lr-002-ref.html b/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-gutter-vertical-lr-002-ref.html
new file mode 100644
index 0000000..cb7c647
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-gutter-vertical-lr-002-ref.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html>
+  <meta charset="utf-8">
+  <title>CSS Overflow Reference: test scrollbar-gutter with vertical left to right content</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+
+  <style>
+  .line {
+    display: flex;
+  }
+
+  .container {
+    writing-mode: vertical-lr;
+
+    box-sizing: border-box;
+    block-size: 200px;
+    inline-size: 200px;
+    margin: 10px;
+    background: deepskyblue;
+    resize: both;
+  }
+
+  .content {
+    inline-size: 100%;
+    block-size: 200%;
+    background: lightsalmon;
+  }
+  </style>
+
+  <div class="line">
+    <div class="container" style="overflow-x: auto">
+      <div class="content"></div>
+    </div>
+
+    <div class="container" style="overflow-x: scroll" id="container_scroll_stable">
+      <div class="content" id="content_scroll_stable"></div>
+    </div>
+
+    <div class="container" style="overflow-x: hidden" id="container_hidden_stable">
+      <div class="content"></div>
+    </div>
+  </div>
+
+  <div class="line">
+    <div class="container" style="overflow-x: auto;" id="container_auto_stable_both_edges">
+      <div class="content"></div>
+    </div>
+
+    <div class="container" style="overflow-x: scroll" id="container_scroll_stable_both_edges">
+      <div class="content"></div>
+    </div>
+
+    <div class="container" style="overflow-x: hidden;" id="container_hidden_stable_both_edges">
+      <div class="content"></div>
+    </div>
+  </div>
+
+  <script>
+  let container_scroll_stable = document.getElementById("container_scroll_stable");
+  let content_scroll_stable = document.getElementById("content_scroll_stable");
+  let hScrollbarHeight = (container_scroll_stable.offsetHeight - content_scroll_stable.offsetHeight);
+  let hScrollbarHeightStr = hScrollbarHeight + "px";
+
+  // Simulate scrollbar-gutter via padding on containers.
+  document.getElementById("container_hidden_stable").style.paddingInlineEnd = hScrollbarHeightStr;
+  document.getElementById("container_auto_stable_both_edges").style.paddingInlineStart = hScrollbarHeightStr;
+  document.getElementById("container_scroll_stable_both_edges").style.paddingInlineStart = hScrollbarHeightStr;
+  document.getElementById("container_hidden_stable_both_edges").style.paddingInlineStart = hScrollbarHeightStr;
+  document.getElementById("container_hidden_stable_both_edges").style.paddingInlineEnd = hScrollbarHeightStr;
+  </script>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-gutter-vertical-lr-002.html b/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-gutter-vertical-lr-002.html
new file mode 100644
index 0000000..9e5dcd29
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-gutter-vertical-lr-002.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+  <meta charset="utf-8">
+  <title>CSS Overflow: test scrollbar-gutter with vertical left to right content</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-overflow-4/#scrollbar-gutter-property">
+  <link rel="match" href="scrollbar-gutter-vertical-lr-002-ref.html">
+
+  <style>
+  .line {
+    display: flex;
+  }
+
+  .container {
+    writing-mode: vertical-lr;
+
+    block-size: 200px;
+    inline-size: 200px;
+    margin: 10px;
+    background: deepskyblue;
+    resize: both;
+  }
+
+  .content {
+    inline-size: 100%;
+    block-size: 200%;
+    background: lightsalmon;
+  }
+  </style>
+
+  <div class="line">
+    <div class="container" style="overflow-x: auto; scrollbar-gutter: stable">
+      <div class="content"></div>
+    </div>
+
+    <div class="container" style="overflow-x: scroll; scrollbar-gutter: stable">
+      <div class="content"></div>
+    </div>
+
+    <div class="container" style="overflow-x: hidden; scrollbar-gutter: stable">
+      <div class="content"></div>
+    </div>
+  </div>
+
+  <div class="line">
+    <div class="container" style="overflow-x: auto; scrollbar-gutter: stable both-edges">
+      <div class="content"></div>
+    </div>
+
+    <div class="container" style="overflow-x: scroll; scrollbar-gutter: stable both-edges">
+      <div class="content"></div>
+    </div>
+
+    <div class="container" style="overflow-x: hidden; scrollbar-gutter: stable both-edges">
+      <div class="content"></div>
+    </div>
+  </div>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-gutter-vertical-rl-002-ref.html b/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-gutter-vertical-rl-002-ref.html
new file mode 100644
index 0000000..7dbadf9
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-gutter-vertical-rl-002-ref.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<html>
+  <meta charset="utf-8">
+  <title>CSS Overflow Reference: test scrollbar-gutter with vertical right to left content</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+
+  <style>
+  .line {
+    display: flex;
+  }
+
+  .container {
+    writing-mode: vertical-rl;
+    direction: ltr;
+
+    box-sizing: border-box;
+    block-size: 200px;
+    inline-size: 200px;
+    margin: 10px;
+    background: deepskyblue;
+    resize: both;
+  }
+
+  .content {
+    inline-size: 100%;
+    block-size: 200%;
+    background: lightsalmon;
+  }
+  </style>
+
+  <div class="line">
+    <div class="container" style="overflow-x: auto">
+      <div class="content"></div>
+    </div>
+
+    <div class="container" style="overflow-x: scroll" id="container_scroll_stable">
+      <div class="content" id="content_scroll_stable"></div>
+    </div>
+
+    <div class="container" style="overflow-x: hidden" id="container_hidden_stable">
+      <div class="content"></div>
+    </div>
+  </div>
+
+  <div class="line">
+    <div class="container" style="overflow-x: auto;" id="container_auto_stable_both_edges">
+      <div class="content"></div>
+    </div>
+
+    <div class="container" style="overflow-x: scroll" id="container_scroll_stable_both_edges">
+      <div class="content"></div>
+    </div>
+
+    <div class="container" style="overflow-x: hidden;" id="container_hidden_stable_both_edges">
+      <div class="content"></div>
+    </div>
+  </div>
+
+  <script>
+  let container_scroll_stable = document.getElementById("container_scroll_stable");
+  let content_scroll_stable = document.getElementById("content_scroll_stable");
+  let hScrollbarHeight = (container_scroll_stable.offsetHeight - content_scroll_stable.offsetHeight);
+  let hScrollbarHeightStr = hScrollbarHeight + "px";
+
+  // Simulate scrollbar-gutter via padding on containers.
+  document.getElementById("container_hidden_stable").style.paddingInlineEnd = hScrollbarHeightStr;
+  document.getElementById("container_auto_stable_both_edges").style.paddingInlineStart = hScrollbarHeightStr;
+  document.getElementById("container_scroll_stable_both_edges").style.paddingInlineStart = hScrollbarHeightStr;
+  document.getElementById("container_hidden_stable_both_edges").style.paddingInlineStart = hScrollbarHeightStr;
+  document.getElementById("container_hidden_stable_both_edges").style.paddingInlineEnd = hScrollbarHeightStr;
+  </script>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-gutter-vertical-rl-002.html b/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-gutter-vertical-rl-002.html
new file mode 100644
index 0000000..d68d4e5
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-gutter-vertical-rl-002.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+  <meta charset="utf-8">
+  <title>CSS Overflow: test scrollbar-gutter with vertical right to left content</title>
+  <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+  <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+  <link rel="help" href="https://drafts.csswg.org/css-overflow-4/#scrollbar-gutter-property">
+  <link rel="match" href="scrollbar-gutter-vertical-rl-002-ref.html">
+
+  <style>
+  .line {
+    display: flex;
+  }
+
+  .container {
+    writing-mode: vertical-rl;
+
+    block-size: 200px;
+    inline-size: 200px;
+    margin: 10px;
+    background: deepskyblue;
+    resize: both;
+  }
+
+  .content {
+    inline-size: 100%;
+    block-size: 200%;
+    background: lightsalmon;
+  }
+  </style>
+
+  <div class="line">
+    <div class="container" style="overflow-x: auto; scrollbar-gutter: stable">
+      <div class="content"></div>
+    </div>
+
+    <div class="container" style="overflow-x: scroll; scrollbar-gutter: stable">
+      <div class="content"></div>
+    </div>
+
+    <div class="container" style="overflow-x: hidden; scrollbar-gutter: stable">
+      <div class="content"></div>
+    </div>
+  </div>
+
+  <div class="line">
+    <div class="container" style="overflow-x: auto; scrollbar-gutter: stable both-edges">
+      <div class="content"></div>
+    </div>
+
+    <div class="container" style="overflow-x: scroll; scrollbar-gutter: stable both-edges">
+      <div class="content"></div>
+    </div>
+
+    <div class="container" style="overflow-x: hidden; scrollbar-gutter: stable both-edges">
+      <div class="content"></div>
+    </div>
+  </div>
+</html>
diff --git a/third_party/blink/web_tests/virtual/webid/README.md b/third_party/blink/web_tests/virtual/webid/README.md
deleted file mode 100644
index 4731317..0000000
--- a/third_party/blink/web_tests/virtual/webid/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Overview
-
-This suite runs the tests in wpt_internal/webid with `--enable-features=WebID`
diff --git a/third_party/blink/web_tests/wpt_internal/webid/README.md b/third_party/blink/web_tests/wpt_internal/webid/README.md
deleted file mode 100644
index 9c8d6e62..0000000
--- a/third_party/blink/web_tests/wpt_internal/webid/README.md
+++ /dev/null
@@ -1,29 +0,0 @@
-The `webid-helpers.js` exposes `webid_test` which is a specialized
-`promise_test` which comes pre-setup with the appropriate mocking infrastructure
-to emulate platform federated auth backend. The mock is passed to the test
-function as the second parameter.
-
-Example usage:
-```
-<script type="module">
-  import {webid_test} from './resources/webid-helper.js';
-
-  webid_test(async (t, mock) => {
-    mock.returnIdToken("a_token");
-    assert_equals("a_token", await navigator.id.get(options));
-  }, "Successfully obtaining a token using mock.");
-</script>
-```
-
-The mock interface, `MockFederatedAuthRequest`, is defined as:
-
-```
-class MockFederatedAuthRequest {
-  // Causes the subsequent `navigator.id.get()` to resolve with the token.
-  returnIdToken(string token);
-  // Causes the subsequent `navigator.id.get()` to reject with the error.
-  returnError(string error);
-};
-```
-
-The chromium implementation uses the MojoJS shim.
diff --git a/third_party/blink/web_tests/wpt_internal/webid/insecure-context.html b/third_party/blink/web_tests/wpt_internal/webid/insecure-context.html
deleted file mode 100644
index cd07351..0000000
--- a/third_party/blink/web_tests/wpt_internal/webid/insecure-context.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>WebID requires secure context</title>
-<link rel="author" title="Majid Valipour" href="mailto:majidvp@chromium.org">
-<link rel="help" href="https://wicg.github.io/WebID">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script>
-test(t => {
-  assert_false(isSecureContext);
-  assert_false('id' in navigator);
-}, "navigator.id requires secure context.");
-</script>
diff --git a/third_party/blink/web_tests/wpt_internal/webid/resources/webid-helper.js b/third_party/blink/web_tests/wpt_internal/webid/resources/webid-helper.js
deleted file mode 100644
index ecf6795..0000000
--- a/third_party/blink/web_tests/wpt_internal/webid/resources/webid-helper.js
+++ /dev/null
@@ -1,18 +0,0 @@
-// The testing infra for WebID is loaded using mojo js shim. To enable these
-// tests the browser must be run with these options:
-//
-//   --enable-blink-features=MojoJS,MojoJSTest
-
-import { MockFederatedAuthRequest } from './webid-mock.js';
-
-export function webid_test(test_func, name, properties) {
-  promise_test(async (t) => {
-    assert_implements(navigator.id, 'missing navigator.id');
-    const mock = new MockFederatedAuthRequest();
-    try {
-      await test_func(t, mock);
-    } finally {
-      await mock.reset();
-    }
-  }, name, properties);
-}
diff --git a/third_party/blink/web_tests/wpt_internal/webid/resources/webid-mock.js b/third_party/blink/web_tests/wpt_internal/webid/resources/webid-mock.js
deleted file mode 100644
index 4aa9af4..0000000
--- a/third_party/blink/web_tests/wpt_internal/webid/resources/webid-mock.js
+++ /dev/null
@@ -1,75 +0,0 @@
-import { RequestMode, RequestIdTokenStatus, LogoutStatus, RevokeStatus, FederatedAuthRequest, FederatedAuthRequestReceiver } from '/gen/third_party/blink/public/mojom/webid/federated_auth_request.mojom.m.js';
-
-function toMojoIdTokenStatus(status) {
-  switch(status) {
-    case "Success": return RequestIdTokenStatus.kSuccess;
-    case "ApprovalDeclined": return RequestIdTokenStatus.kApprovalDeclined;
-    case "ErrorTooManyRequests": return RequestIdTokenStatus.kErrorTooManyRequests;
-    case "ErrorWebIdNotSupportedByProvider": return RequestIdTokenStatus.kErrorWebIdNotSupportedByProvider;
-    case "ErrorFetchingWellKnown": return RequestIdTokenStatus.kErrorFetchingWellKnown;
-    case "ErrorInvalidWellKnown": return RequestIdTokenStatus.kErrorInvalidWellKnown;
-    case "ErrorFetchingSignin": return RequestIdTokenStatus.kErrorFetchingSignin;
-    case "ErrorInvalidSigninResponse": return RequestIdTokenStatus.kErrorInvalidSigninResponse;
-    case "ErrorInvalidAccountsResponse": return RequestIdTokenStatus.kErrorInvalidAccountsResponse;
-    case "ErrorInvalidTokenResponse": return RequestIdTokenStatus.kErrorInvalidTokenResponse;
-    case "Error": return RequestIdTokenStatus.kError;
-    default: throw new Error(`Invalid status: ${status}`);
-  }
-}
-
-// A mock service for responding to federated auth requests.
-export class MockFederatedAuthRequest {
-  constructor() {
-    this.receiver_ = new FederatedAuthRequestReceiver(this);
-    this.interceptor_ = new MojoInterfaceInterceptor(FederatedAuthRequest.$interfaceName);
-    this.interceptor_.oninterfacerequest = e => {
-        this.receiver_.$.bindHandle(e.handle);
-    }
-    this.interceptor_.start();
-    this.idToken_ = null;
-    this.status_ = RequestIdTokenStatus.kError;
-    this.logoutStatus_ = LogoutStatus.kError;
-  }
-
-  // Causes the subsequent `navigator.id.get()` to resolve with the token.
-  returnIdToken(token) {
-    this.status_ = RequestIdTokenStatus.kSuccess;
-    this.idToken_ = token;
-  }
-
-  // Causes the subsequent `navigator.id.get()` to reject with the error.
-  returnError(error) {
-    if (error == "Success")
-      throw new Error("Success is not a valid error");
-    this.status_ = toMojoIdTokenStatus(error);
-    this.idToken_ = null;
-  }
-
-  // Implements
-  //   RequestIdToken(url.mojom.Url provider, string id_request, RequestMode mode) => (RequestIdTokenStatus status, string? id_token);
-  async requestIdToken(provider, idRequest, mode) {
-    return Promise.resolve({
-      status: this.status_,
-      idToken: this.idToken_
-    });
-  }
-
-  async logout(logout_endpoints) {
-    return Promise.resolve({
-      status: this.logoutStatus_
-    });
-  }
-
-  async reset() {
-    this.idToken_ = null;
-    this.status_ = RequestIdTokenStatus.kError;
-    this.logoutStatus_ = LogoutStatus.kError;
-    this.receiver_.$.close();
-    this.interceptor_.stop();
-
-    // Clean up and reset mock stubs asynchronously, so that the blink side
-    // closes its proxies and notifies JS sensor objects before new test is
-    // started.
-    await new Promise(resolve => { setTimeout(resolve, 0); });
-  }
-}
diff --git a/third_party/crashpad/README.chromium b/third_party/crashpad/README.chromium
index 2cf1f73..fe056c5 100644
--- a/third_party/crashpad/README.chromium
+++ b/third_party/crashpad/README.chromium
@@ -2,7 +2,7 @@
 Short Name: crashpad
 URL: https://crashpad.chromium.org/
 Version: unknown
-Revision: 3b8a7cf594da2e3454fb230f98a60808837da217
+Revision: 5cc0d543d0e5dadba03a6bf7dd8161fb78ab07b5
 License: Apache 2.0
 License File: crashpad/LICENSE
 Security Critical: yes
@@ -36,6 +36,7 @@
 
 Local Modifications:
  - codereview.settings has been excluded.
+ - infra/ has been excluded.
  - MultiprocessExec.MultiprocessExec is disabled when OS_POSIX and
    BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) are defined. crbug.com/1153544
    (crashpad/test/BUILD.gn, crashpad/test/multiprocess_exec_test.cc)
diff --git a/third_party/crashpad/crashpad/DEPS b/third_party/crashpad/crashpad/DEPS
index c1edb80c..e13bea8 100644
--- a/third_party/crashpad/crashpad/DEPS
+++ b/third_party/crashpad/crashpad/DEPS
@@ -33,13 +33,13 @@
   },
   'crashpad/third_party/googletest/googletest':
       Var('chromium_git') + '/external/github.com/google/googletest@' +
-      '11da093e0477185dbd78abaaa9f99db15be498d0',
+      '5bcd8e3bb929714e031a542d303f818e5a5af45d',
   'crashpad/third_party/lss/lss':
       Var('chromium_git') + '/linux-syscall-support.git@' +
       '7bde79cc274d06451bf65ae82c012a5d3e476b5a',
   'crashpad/third_party/mini_chromium/mini_chromium':
       Var('chromium_git') + '/chromium/mini_chromium@' +
-      '0e22eed71eec97dacbe80822a14c5cd0b580d793',
+      '502930381b23c5fa3911c8b82ec3e4ba6ceb3658',
   'crashpad/third_party/libfuzzer/src':
       Var('chromium_git') + '/chromium/llvm-project/compiler-rt/lib/fuzzer.git@' +
       'fda403cf93ecb8792cb1d061564d89a6553ca020',
@@ -106,7 +106,7 @@
     'packages': [
       {
         'package': 'chrome_internal/third_party/sdk/windows',
-        'version': 'uploaded:2018-06-13'
+        'version': 'uploaded:2021-04-28'
       },
     ],
     'condition': 'checkout_win and pull_win_toolchain',
diff --git a/third_party/crashpad/crashpad/client/crashpad_client_linux_test.cc b/third_party/crashpad/crashpad/client/crashpad_client_linux_test.cc
index a3ddc31..ddf15390 100644
--- a/third_party/crashpad/crashpad/client/crashpad_client_linux_test.cc
+++ b/third_party/crashpad/crashpad/client/crashpad_client_linux_test.cc
@@ -26,6 +26,7 @@
 #include "client/annotation.h"
 #include "client/annotation_list.h"
 #include "client/crash_report_database.h"
+#include "client/crashpad_info.h"
 #include "client/simulate_crash.h"
 #include "gtest/gtest.h"
 #include "snapshot/annotation_snapshot.h"
@@ -73,12 +74,13 @@
   bool set_first_chance_handler;
   bool crash_non_main_thread;
   bool client_uses_signals;
+  bool gather_indirectly_referenced_memory;
   CrashType crash_type;
 };
 
 class StartHandlerForSelfTest
     : public testing::TestWithParam<
-          std::tuple<bool, bool, bool, bool, CrashType>> {
+          std::tuple<bool, bool, bool, bool, bool, CrashType>> {
  public:
   StartHandlerForSelfTest() = default;
 
@@ -92,6 +94,7 @@
              options_.set_first_chance_handler,
              options_.crash_non_main_thread,
              options_.client_uses_signals,
+             options_.gather_indirectly_referenced_memory,
              options_.crash_type) = GetParam();
   }
 
@@ -147,7 +150,8 @@
             0);
 }
 
-void ValidateExtraMemory(const ProcessSnapshotMinidump& minidump) {
+void ValidateExtraMemory(const StartHandlerForSelfTestOptions& options,
+                         const ProcessSnapshotMinidump& minidump) {
   // Verify that if we have an exception, then the code around the instruction
   // pointer is included in the extra memory.
   const ExceptionSnapshot* exception = minidump.Exception();
@@ -164,10 +168,11 @@
       break;
     }
   }
-  EXPECT_TRUE(pc_found);
+  EXPECT_EQ(pc_found, options.gather_indirectly_referenced_memory);
 }
 
-void ValidateDump(const CrashReportDatabase::UploadReport* report) {
+void ValidateDump(const StartHandlerForSelfTestOptions& options,
+                  const CrashReportDatabase::UploadReport* report) {
   ProcessSnapshotMinidump minidump_snapshot;
   ASSERT_TRUE(minidump_snapshot.Initialize(report->Reader()));
 
@@ -184,7 +189,7 @@
 #endif
   ValidateAttachment(report);
 
-  ValidateExtraMemory(minidump_snapshot);
+  ValidateExtraMemory(options, minidump_snapshot);
 
   for (const ModuleSnapshot* module : minidump_snapshot.Modules()) {
     for (const AnnotationSnapshot& annotation : module->AnnotationObjects()) {
@@ -330,6 +335,11 @@
         client_handler, SA_ONSTACK, &old_actions));
   }
 
+  if (options.gather_indirectly_referenced_memory) {
+    CrashpadInfo::GetCrashpadInfo()->set_gather_indirectly_referenced_memory(
+        TriState::kEnabled, 1024 * 1024 * 4);
+  }
+
   base::FilePath handler_path = TestPaths::Executable().DirName().Append(
       FILE_PATH_LITERAL("crashpad_handler"));
 
@@ -442,7 +452,7 @@
     std::unique_ptr<const CrashReportDatabase::UploadReport> report;
     ASSERT_EQ(database->GetReportForUploading(reports[0].uuid, &report),
               CrashReportDatabase::kNoError);
-    ValidateDump(report.get());
+    ValidateDump(options_, report.get());
   }
 
   StartHandlerForSelfTestOptions options_;
@@ -471,6 +481,7 @@
                      testing::Bool(),
                      testing::Bool(),
                      testing::Bool(),
+                     testing::Bool(),
                      testing::Values(CrashType::kSimulated,
                                      CrashType::kBuiltinTrap,
                                      CrashType::kInfiniteRecursion)));
diff --git a/third_party/crashpad/crashpad/client/crashpad_info_note.S b/third_party/crashpad/crashpad/client/crashpad_info_note.S
index b13d864..ef3d68c 100644
--- a/third_party/crashpad/crashpad/client/crashpad_info_note.S
+++ b/third_party/crashpad/crashpad/client/crashpad_info_note.S
@@ -17,6 +17,7 @@
 // that symbol to be in the dynamic symbol table.
 
 #include "util/misc/elf_note_types.h"
+#include "util/misc/arm64_bti_note.S"
 
 // namespace crashpad {
 // CrashpadInfo g_crashpad_info;
diff --git a/third_party/crashpad/crashpad/infra/config/PRESUBMIT.py b/third_party/crashpad/crashpad/infra/config/PRESUBMIT.py
deleted file mode 100644
index 3961942..0000000
--- a/third_party/crashpad/crashpad/infra/config/PRESUBMIT.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# Copyright 2018 The Crashpad Authors. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-USE_PYTHON3 = True
-PRESUBMIT_VERSION = '2.0.0'
-
-
-def CheckChangedLUCIConfigs(input_api, output_api):
-    return input_api.canned_checks.CheckChangedLUCIConfigs(
-        input_api, output_api)
-
-
-def CheckLucicfgGenOutputMain(input_api, output_api):
-    return input_api.RunTests(
-        input_api.canned_checks.CheckLucicfgGenOutput(input_api, output_api,
-                                                      'main.star'))
diff --git a/third_party/crashpad/crashpad/infra/config/generated/commit-queue.cfg b/third_party/crashpad/crashpad/infra/config/generated/commit-queue.cfg
deleted file mode 100644
index 95b31824..0000000
--- a/third_party/crashpad/crashpad/infra/config/generated/commit-queue.cfg
+++ /dev/null
@@ -1,80 +0,0 @@
-# Auto-generated by lucicfg.
-# Do not modify manually.
-#
-# For the schema of this file, see Config message:
-#   https://luci-config.appspot.com/schemas/projects:commit-queue.cfg
-
-cq_status_host: "chromium-cq-status.appspot.com"
-submit_options {
-  max_burst: 4
-  burst_delay {
-    seconds: 480
-  }
-}
-config_groups {
-  name: "crashpad"
-  gerrit {
-    url: "https://chromium-review.googlesource.com"
-    projects {
-      name: "crashpad/crashpad"
-      ref_regexp: "refs/heads/.+"
-    }
-  }
-  verifiers {
-    gerrit_cq_ability {
-      committer_list: "project-crashpad-tryjob-access"
-      dry_run_access_list: "project-crashpad-tryjob-access"
-    }
-    tryjob {
-      builders {
-        name: "crashpad/try/crashpad_fuchsia_arm64_dbg"
-      }
-      builders {
-        name: "crashpad/try/crashpad_fuchsia_arm64_rel"
-      }
-      builders {
-        name: "crashpad/try/crashpad_fuchsia_x64_dbg"
-      }
-      builders {
-        name: "crashpad/try/crashpad_fuchsia_x64_rel"
-      }
-      builders {
-        name: "crashpad/try/crashpad_ios_arm64_dbg"
-      }
-      builders {
-        name: "crashpad/try/crashpad_ios_arm64_rel"
-      }
-      builders {
-        name: "crashpad/try/crashpad_ios_x64_dbg"
-      }
-      builders {
-        name: "crashpad/try/crashpad_ios_x64_rel"
-      }
-      builders {
-        name: "crashpad/try/crashpad_linux_x64_dbg"
-      }
-      builders {
-        name: "crashpad/try/crashpad_linux_x64_rel"
-      }
-      builders {
-        name: "crashpad/try/crashpad_mac_x64_dbg"
-      }
-      builders {
-        name: "crashpad/try/crashpad_mac_x64_rel"
-      }
-      builders {
-        name: "crashpad/try/crashpad_win_x64_dbg"
-      }
-      builders {
-        name: "crashpad/try/crashpad_win_x64_rel"
-      }
-      retry_config {
-        single_quota: 1
-        global_quota: 2
-        failure_weight: 1
-        transient_failure_weight: 1
-        timeout_weight: 2
-      }
-    }
-  }
-}
diff --git a/third_party/crashpad/crashpad/infra/config/generated/cr-buildbucket.cfg b/third_party/crashpad/crashpad/infra/config/generated/cr-buildbucket.cfg
deleted file mode 100644
index d34621c..0000000
--- a/third_party/crashpad/crashpad/infra/config/generated/cr-buildbucket.cfg
+++ /dev/null
@@ -1,972 +0,0 @@
-# Auto-generated by lucicfg.
-# Do not modify manually.
-#
-# For the schema of this file, see BuildbucketCfg message:
-#   https://luci-config.appspot.com/schemas/projects:buildbucket.cfg
-
-buckets {
-  name: "ci"
-  acls {
-    role: WRITER
-    group: "project-crashpad-admins"
-  }
-  acls {
-    group: "all"
-  }
-  acls {
-    role: SCHEDULER
-    identity: "user:luci-scheduler@appspot.gserviceaccount.com"
-  }
-  swarming {
-    builders {
-      name: "crashpad_fuchsia_arm64_dbg"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04"
-      dimensions: "pool:luci.flex.ci"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/main"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "$gatekeeper": {'
-        '    "group": "client.crashpad"'
-        '  },'
-        '  "$kitchen": {'
-        '    "devshell": true,'
-        '    "git_auth": true'
-        '  },'
-        '  "config": "Debug",'
-        '  "recipe": "crashpad/build",'
-        '  "target_cpu": "arm64",'
-        '  "target_os": "fuchsia"'
-        '}'
-      execution_timeout_secs: 10800
-      build_numbers: YES
-      service_account: "crashpad-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-    builders {
-      name: "crashpad_fuchsia_arm64_rel"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04"
-      dimensions: "pool:luci.flex.ci"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/main"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "$gatekeeper": {'
-        '    "group": "client.crashpad"'
-        '  },'
-        '  "$kitchen": {'
-        '    "devshell": true,'
-        '    "git_auth": true'
-        '  },'
-        '  "config": "Release",'
-        '  "recipe": "crashpad/build",'
-        '  "target_cpu": "arm64",'
-        '  "target_os": "fuchsia"'
-        '}'
-      execution_timeout_secs: 10800
-      build_numbers: YES
-      service_account: "crashpad-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-    builders {
-      name: "crashpad_fuchsia_x64_dbg"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04"
-      dimensions: "pool:luci.flex.ci"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/main"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "$gatekeeper": {'
-        '    "group": "client.crashpad"'
-        '  },'
-        '  "$kitchen": {'
-        '    "devshell": true,'
-        '    "git_auth": true'
-        '  },'
-        '  "config": "Debug",'
-        '  "recipe": "crashpad/build",'
-        '  "target_os": "fuchsia"'
-        '}'
-      execution_timeout_secs: 10800
-      build_numbers: YES
-      service_account: "crashpad-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-    builders {
-      name: "crashpad_fuchsia_x64_rel"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04"
-      dimensions: "pool:luci.flex.ci"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/main"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "$gatekeeper": {'
-        '    "group": "client.crashpad"'
-        '  },'
-        '  "$kitchen": {'
-        '    "devshell": true,'
-        '    "git_auth": true'
-        '  },'
-        '  "config": "Release",'
-        '  "recipe": "crashpad/build",'
-        '  "target_os": "fuchsia"'
-        '}'
-      execution_timeout_secs: 10800
-      build_numbers: YES
-      service_account: "crashpad-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-    builders {
-      name: "crashpad_ios_arm64_dbg"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-10.15"
-      dimensions: "pool:luci.flex.ci"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/main"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "$gatekeeper": {'
-        '    "group": "client.crashpad"'
-        '  },'
-        '  "$kitchen": {'
-        '    "devshell": true,'
-        '    "git_auth": true'
-        '  },'
-        '  "config": "Debug",'
-        '  "recipe": "crashpad/build",'
-        '  "target_cpu": "arm64",'
-        '  "target_os": "ios"'
-        '}'
-      execution_timeout_secs: 10800
-      caches {
-        name: "osx_sdk_ios"
-        path: "osx_sdk"
-      }
-      build_numbers: YES
-      service_account: "crashpad-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-    builders {
-      name: "crashpad_ios_arm64_rel"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-10.15"
-      dimensions: "pool:luci.flex.ci"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/main"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "$gatekeeper": {'
-        '    "group": "client.crashpad"'
-        '  },'
-        '  "$kitchen": {'
-        '    "devshell": true,'
-        '    "git_auth": true'
-        '  },'
-        '  "config": "Release",'
-        '  "recipe": "crashpad/build",'
-        '  "target_cpu": "arm64",'
-        '  "target_os": "ios"'
-        '}'
-      execution_timeout_secs: 10800
-      caches {
-        name: "osx_sdk_ios"
-        path: "osx_sdk"
-      }
-      build_numbers: YES
-      service_account: "crashpad-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-    builders {
-      name: "crashpad_ios_x64_dbg"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-10.15"
-      dimensions: "pool:luci.flex.ci"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/main"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "$gatekeeper": {'
-        '    "group": "client.crashpad"'
-        '  },'
-        '  "$kitchen": {'
-        '    "devshell": true,'
-        '    "git_auth": true'
-        '  },'
-        '  "config": "Debug",'
-        '  "recipe": "crashpad/build",'
-        '  "target_os": "ios"'
-        '}'
-      execution_timeout_secs: 10800
-      caches {
-        name: "osx_sdk_ios"
-        path: "osx_sdk"
-      }
-      build_numbers: YES
-      service_account: "crashpad-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-    builders {
-      name: "crashpad_ios_x64_rel"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-10.15"
-      dimensions: "pool:luci.flex.ci"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/main"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "$gatekeeper": {'
-        '    "group": "client.crashpad"'
-        '  },'
-        '  "$kitchen": {'
-        '    "devshell": true,'
-        '    "git_auth": true'
-        '  },'
-        '  "config": "Release",'
-        '  "recipe": "crashpad/build",'
-        '  "target_os": "ios"'
-        '}'
-      execution_timeout_secs: 10800
-      caches {
-        name: "osx_sdk_ios"
-        path: "osx_sdk"
-      }
-      build_numbers: YES
-      service_account: "crashpad-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-    builders {
-      name: "crashpad_linux_x64_dbg"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04"
-      dimensions: "pool:luci.flex.ci"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/main"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "$gatekeeper": {'
-        '    "group": "client.crashpad"'
-        '  },'
-        '  "$kitchen": {'
-        '    "devshell": true,'
-        '    "git_auth": true'
-        '  },'
-        '  "config": "Debug",'
-        '  "recipe": "crashpad/build",'
-        '  "target_os": "linux"'
-        '}'
-      execution_timeout_secs: 10800
-      build_numbers: YES
-      service_account: "crashpad-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-    builders {
-      name: "crashpad_linux_x64_rel"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04"
-      dimensions: "pool:luci.flex.ci"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/main"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "$gatekeeper": {'
-        '    "group": "client.crashpad"'
-        '  },'
-        '  "$kitchen": {'
-        '    "devshell": true,'
-        '    "git_auth": true'
-        '  },'
-        '  "config": "Release",'
-        '  "recipe": "crashpad/build",'
-        '  "target_os": "linux"'
-        '}'
-      execution_timeout_secs: 10800
-      build_numbers: YES
-      service_account: "crashpad-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-    builders {
-      name: "crashpad_mac_x64_dbg"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-10.15"
-      dimensions: "pool:luci.flex.ci"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/main"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "$gatekeeper": {'
-        '    "group": "client.crashpad"'
-        '  },'
-        '  "$kitchen": {'
-        '    "devshell": true,'
-        '    "git_auth": true'
-        '  },'
-        '  "config": "Debug",'
-        '  "recipe": "crashpad/build",'
-        '  "target_os": "mac"'
-        '}'
-      execution_timeout_secs: 10800
-      caches {
-        name: "osx_sdk_mac"
-        path: "osx_sdk"
-      }
-      build_numbers: YES
-      service_account: "crashpad-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-    builders {
-      name: "crashpad_mac_x64_rel"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-10.15"
-      dimensions: "pool:luci.flex.ci"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/main"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "$gatekeeper": {'
-        '    "group": "client.crashpad"'
-        '  },'
-        '  "$kitchen": {'
-        '    "devshell": true,'
-        '    "git_auth": true'
-        '  },'
-        '  "config": "Release",'
-        '  "recipe": "crashpad/build",'
-        '  "target_os": "mac"'
-        '}'
-      execution_timeout_secs: 10800
-      caches {
-        name: "osx_sdk_mac"
-        path: "osx_sdk"
-      }
-      build_numbers: YES
-      service_account: "crashpad-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-    builders {
-      name: "crashpad_win_x64_dbg"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Windows-10"
-      dimensions: "pool:luci.flex.ci"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/main"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "$gatekeeper": {'
-        '    "group": "client.crashpad"'
-        '  },'
-        '  "$kitchen": {'
-        '    "devshell": true,'
-        '    "git_auth": true'
-        '  },'
-        '  "config": "Debug",'
-        '  "recipe": "crashpad/build",'
-        '  "target_os": "win"'
-        '}'
-      execution_timeout_secs: 10800
-      build_numbers: YES
-      service_account: "crashpad-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-    builders {
-      name: "crashpad_win_x64_rel"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Windows-10"
-      dimensions: "pool:luci.flex.ci"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/main"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "$gatekeeper": {'
-        '    "group": "client.crashpad"'
-        '  },'
-        '  "$kitchen": {'
-        '    "devshell": true,'
-        '    "git_auth": true'
-        '  },'
-        '  "config": "Release",'
-        '  "recipe": "crashpad/build",'
-        '  "target_os": "win"'
-        '}'
-      execution_timeout_secs: 10800
-      build_numbers: YES
-      service_account: "crashpad-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-  }
-}
-buckets {
-  name: "try"
-  acls {
-    role: WRITER
-    group: "project-crashpad-admins"
-  }
-  acls {
-    role: WRITER
-    group: "service-account-crashpad-cq"
-  }
-  acls {
-    group: "all"
-  }
-  acls {
-    role: SCHEDULER
-    group: "project-crashpad-tryjob-access"
-  }
-  acls {
-    role: SCHEDULER
-    group: "service-account-cq"
-  }
-  swarming {
-    builders {
-      name: "crashpad_fuchsia_arm64_dbg"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04"
-      dimensions: "pool:luci.flex.try"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/main"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "$kitchen": {'
-        '    "devshell": true,'
-        '    "git_auth": true'
-        '  },'
-        '  "config": "Debug",'
-        '  "recipe": "crashpad/build",'
-        '  "target_cpu": "arm64",'
-        '  "target_os": "fuchsia"'
-        '}'
-      execution_timeout_secs: 10800
-      build_numbers: YES
-      service_account: "crashpad-try-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-    builders {
-      name: "crashpad_fuchsia_arm64_rel"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04"
-      dimensions: "pool:luci.flex.try"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/main"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "$kitchen": {'
-        '    "devshell": true,'
-        '    "git_auth": true'
-        '  },'
-        '  "config": "Release",'
-        '  "recipe": "crashpad/build",'
-        '  "target_cpu": "arm64",'
-        '  "target_os": "fuchsia"'
-        '}'
-      execution_timeout_secs: 10800
-      build_numbers: YES
-      service_account: "crashpad-try-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-    builders {
-      name: "crashpad_fuchsia_x64_dbg"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04"
-      dimensions: "pool:luci.flex.try"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/main"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "$kitchen": {'
-        '    "devshell": true,'
-        '    "git_auth": true'
-        '  },'
-        '  "config": "Debug",'
-        '  "recipe": "crashpad/build",'
-        '  "target_os": "fuchsia"'
-        '}'
-      execution_timeout_secs: 10800
-      build_numbers: YES
-      service_account: "crashpad-try-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-    builders {
-      name: "crashpad_fuchsia_x64_rel"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04"
-      dimensions: "pool:luci.flex.try"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/main"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "$kitchen": {'
-        '    "devshell": true,'
-        '    "git_auth": true'
-        '  },'
-        '  "config": "Release",'
-        '  "recipe": "crashpad/build",'
-        '  "target_os": "fuchsia"'
-        '}'
-      execution_timeout_secs: 10800
-      build_numbers: YES
-      service_account: "crashpad-try-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-    builders {
-      name: "crashpad_ios_arm64_dbg"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-10.15"
-      dimensions: "pool:luci.flex.try"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/main"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "$kitchen": {'
-        '    "devshell": true,'
-        '    "git_auth": true'
-        '  },'
-        '  "config": "Debug",'
-        '  "recipe": "crashpad/build",'
-        '  "target_cpu": "arm64",'
-        '  "target_os": "ios"'
-        '}'
-      execution_timeout_secs: 10800
-      caches {
-        name: "osx_sdk_ios"
-        path: "osx_sdk"
-      }
-      build_numbers: YES
-      service_account: "crashpad-try-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-    builders {
-      name: "crashpad_ios_arm64_rel"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-10.15"
-      dimensions: "pool:luci.flex.try"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/main"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "$kitchen": {'
-        '    "devshell": true,'
-        '    "git_auth": true'
-        '  },'
-        '  "config": "Release",'
-        '  "recipe": "crashpad/build",'
-        '  "target_cpu": "arm64",'
-        '  "target_os": "ios"'
-        '}'
-      execution_timeout_secs: 10800
-      caches {
-        name: "osx_sdk_ios"
-        path: "osx_sdk"
-      }
-      build_numbers: YES
-      service_account: "crashpad-try-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-    builders {
-      name: "crashpad_ios_x64_dbg"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-10.15"
-      dimensions: "pool:luci.flex.try"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/main"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "$kitchen": {'
-        '    "devshell": true,'
-        '    "git_auth": true'
-        '  },'
-        '  "config": "Debug",'
-        '  "recipe": "crashpad/build",'
-        '  "target_os": "ios"'
-        '}'
-      execution_timeout_secs: 10800
-      caches {
-        name: "osx_sdk_ios"
-        path: "osx_sdk"
-      }
-      build_numbers: YES
-      service_account: "crashpad-try-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-    builders {
-      name: "crashpad_ios_x64_rel"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-10.15"
-      dimensions: "pool:luci.flex.try"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/main"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "$kitchen": {'
-        '    "devshell": true,'
-        '    "git_auth": true'
-        '  },'
-        '  "config": "Release",'
-        '  "recipe": "crashpad/build",'
-        '  "target_os": "ios"'
-        '}'
-      execution_timeout_secs: 10800
-      caches {
-        name: "osx_sdk_ios"
-        path: "osx_sdk"
-      }
-      build_numbers: YES
-      service_account: "crashpad-try-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-    builders {
-      name: "crashpad_linux_x64_dbg"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04"
-      dimensions: "pool:luci.flex.try"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/main"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "$kitchen": {'
-        '    "devshell": true,'
-        '    "git_auth": true'
-        '  },'
-        '  "config": "Debug",'
-        '  "recipe": "crashpad/build",'
-        '  "target_os": "linux"'
-        '}'
-      execution_timeout_secs: 10800
-      build_numbers: YES
-      service_account: "crashpad-try-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-    builders {
-      name: "crashpad_linux_x64_rel"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-16.04"
-      dimensions: "pool:luci.flex.try"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/main"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "$kitchen": {'
-        '    "devshell": true,'
-        '    "git_auth": true'
-        '  },'
-        '  "config": "Release",'
-        '  "recipe": "crashpad/build",'
-        '  "target_os": "linux"'
-        '}'
-      execution_timeout_secs: 10800
-      build_numbers: YES
-      service_account: "crashpad-try-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-    builders {
-      name: "crashpad_mac_x64_dbg"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-10.15"
-      dimensions: "pool:luci.flex.try"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/main"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "$kitchen": {'
-        '    "devshell": true,'
-        '    "git_auth": true'
-        '  },'
-        '  "config": "Debug",'
-        '  "recipe": "crashpad/build",'
-        '  "target_os": "mac"'
-        '}'
-      execution_timeout_secs: 10800
-      caches {
-        name: "osx_sdk_mac"
-        path: "osx_sdk"
-      }
-      build_numbers: YES
-      service_account: "crashpad-try-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-    builders {
-      name: "crashpad_mac_x64_rel"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-10.15"
-      dimensions: "pool:luci.flex.try"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/main"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "$kitchen": {'
-        '    "devshell": true,'
-        '    "git_auth": true'
-        '  },'
-        '  "config": "Release",'
-        '  "recipe": "crashpad/build",'
-        '  "target_os": "mac"'
-        '}'
-      execution_timeout_secs: 10800
-      caches {
-        name: "osx_sdk_mac"
-        path: "osx_sdk"
-      }
-      build_numbers: YES
-      service_account: "crashpad-try-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-    builders {
-      name: "crashpad_win_x64_dbg"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Windows-10"
-      dimensions: "pool:luci.flex.try"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/main"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "$kitchen": {'
-        '    "devshell": true,'
-        '    "git_auth": true'
-        '  },'
-        '  "config": "Debug",'
-        '  "recipe": "crashpad/build",'
-        '  "target_os": "win"'
-        '}'
-      execution_timeout_secs: 10800
-      build_numbers: YES
-      service_account: "crashpad-try-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-    builders {
-      name: "crashpad_win_x64_rel"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "cores:8"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Windows-10"
-      dimensions: "pool:luci.flex.try"
-      exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/main"
-        cmd: "luciexe"
-      }
-      properties:
-        '{'
-        '  "$kitchen": {'
-        '    "devshell": true,'
-        '    "git_auth": true'
-        '  },'
-        '  "config": "Release",'
-        '  "recipe": "crashpad/build",'
-        '  "target_os": "win"'
-        '}'
-      execution_timeout_secs: 10800
-      build_numbers: YES
-      service_account: "crashpad-try-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-    }
-  }
-}
diff --git a/third_party/crashpad/crashpad/infra/config/generated/luci-logdog.cfg b/third_party/crashpad/crashpad/infra/config/generated/luci-logdog.cfg
deleted file mode 100644
index adc75be..0000000
--- a/third_party/crashpad/crashpad/infra/config/generated/luci-logdog.cfg
+++ /dev/null
@@ -1,9 +0,0 @@
-# Auto-generated by lucicfg.
-# Do not modify manually.
-#
-# For the schema of this file, see ProjectConfig message:
-#   https://luci-config.appspot.com/schemas/projects:luci-logdog.cfg
-
-reader_auth_groups: "all"
-writer_auth_groups: "luci-logdog-chromium-writers"
-archive_gs_bucket: "chromium-luci-logdog"
diff --git a/third_party/crashpad/crashpad/infra/config/generated/luci-milo.cfg b/third_party/crashpad/crashpad/infra/config/generated/luci-milo.cfg
deleted file mode 100644
index 6c891b1..0000000
--- a/third_party/crashpad/crashpad/infra/config/generated/luci-milo.cfg
+++ /dev/null
@@ -1,131 +0,0 @@
-# Auto-generated by lucicfg.
-# Do not modify manually.
-#
-# For the schema of this file, see Project message:
-#   https://luci-config.appspot.com/schemas/projects:luci-milo.cfg
-
-consoles {
-  id: "main"
-  name: "Crashpad Main Console"
-  repo_url: "https://chromium.googlesource.com/crashpad/crashpad"
-  refs: "regexp:refs/heads/main"
-  manifest_name: "REVISION"
-  builders {
-    name: "buildbucket/luci.crashpad.ci/crashpad_fuchsia_arm64_dbg"
-    category: "fuchsia|arm64"
-    short_name: "dbg"
-  }
-  builders {
-    name: "buildbucket/luci.crashpad.ci/crashpad_fuchsia_arm64_rel"
-    category: "fuchsia|arm64"
-    short_name: "rel"
-  }
-  builders {
-    name: "buildbucket/luci.crashpad.ci/crashpad_fuchsia_x64_dbg"
-    category: "fuchsia|x64"
-    short_name: "dbg"
-  }
-  builders {
-    name: "buildbucket/luci.crashpad.ci/crashpad_fuchsia_x64_rel"
-    category: "fuchsia|x64"
-    short_name: "rel"
-  }
-  builders {
-    name: "buildbucket/luci.crashpad.ci/crashpad_ios_arm64_dbg"
-    category: "ios|arm64"
-    short_name: "dbg"
-  }
-  builders {
-    name: "buildbucket/luci.crashpad.ci/crashpad_ios_arm64_rel"
-    category: "ios|arm64"
-    short_name: "rel"
-  }
-  builders {
-    name: "buildbucket/luci.crashpad.ci/crashpad_ios_x64_dbg"
-    category: "ios|x64"
-    short_name: "dbg"
-  }
-  builders {
-    name: "buildbucket/luci.crashpad.ci/crashpad_ios_x64_rel"
-    category: "ios|x64"
-    short_name: "rel"
-  }
-  builders {
-    name: "buildbucket/luci.crashpad.ci/crashpad_linux_x64_dbg"
-    category: "linux|x64"
-    short_name: "dbg"
-  }
-  builders {
-    name: "buildbucket/luci.crashpad.ci/crashpad_linux_x64_rel"
-    category: "linux|x64"
-    short_name: "rel"
-  }
-  builders {
-    name: "buildbucket/luci.crashpad.ci/crashpad_mac_x64_dbg"
-    category: "mac|x64"
-    short_name: "dbg"
-  }
-  builders {
-    name: "buildbucket/luci.crashpad.ci/crashpad_mac_x64_rel"
-    category: "mac|x64"
-    short_name: "rel"
-  }
-  builders {
-    name: "buildbucket/luci.crashpad.ci/crashpad_win_x64_dbg"
-    category: "win|x64"
-    short_name: "dbg"
-  }
-  builders {
-    name: "buildbucket/luci.crashpad.ci/crashpad_win_x64_rel"
-    category: "win|x64"
-    short_name: "rel"
-  }
-}
-consoles {
-  id: "try"
-  name: "Crashpad Try Builders"
-  builders {
-    name: "buildbucket/luci.crashpad.try/crashpad_fuchsia_arm64_dbg"
-  }
-  builders {
-    name: "buildbucket/luci.crashpad.try/crashpad_fuchsia_arm64_rel"
-  }
-  builders {
-    name: "buildbucket/luci.crashpad.try/crashpad_fuchsia_x64_dbg"
-  }
-  builders {
-    name: "buildbucket/luci.crashpad.try/crashpad_fuchsia_x64_rel"
-  }
-  builders {
-    name: "buildbucket/luci.crashpad.try/crashpad_ios_arm64_dbg"
-  }
-  builders {
-    name: "buildbucket/luci.crashpad.try/crashpad_ios_arm64_rel"
-  }
-  builders {
-    name: "buildbucket/luci.crashpad.try/crashpad_ios_x64_dbg"
-  }
-  builders {
-    name: "buildbucket/luci.crashpad.try/crashpad_ios_x64_rel"
-  }
-  builders {
-    name: "buildbucket/luci.crashpad.try/crashpad_linux_x64_dbg"
-  }
-  builders {
-    name: "buildbucket/luci.crashpad.try/crashpad_linux_x64_rel"
-  }
-  builders {
-    name: "buildbucket/luci.crashpad.try/crashpad_mac_x64_dbg"
-  }
-  builders {
-    name: "buildbucket/luci.crashpad.try/crashpad_mac_x64_rel"
-  }
-  builders {
-    name: "buildbucket/luci.crashpad.try/crashpad_win_x64_dbg"
-  }
-  builders {
-    name: "buildbucket/luci.crashpad.try/crashpad_win_x64_rel"
-  }
-  builder_view_only: true
-}
-logo_url: "https://storage.googleapis.com/chrome-infra-public/logo/crashpad-logo.svg"
diff --git a/third_party/crashpad/crashpad/infra/config/generated/luci-scheduler.cfg b/third_party/crashpad/crashpad/infra/config/generated/luci-scheduler.cfg
deleted file mode 100644
index 6b3d04d1..0000000
--- a/third_party/crashpad/crashpad/infra/config/generated/luci-scheduler.cfg
+++ /dev/null
@@ -1,179 +0,0 @@
-# Auto-generated by lucicfg.
-# Do not modify manually.
-#
-# For the schema of this file, see ProjectConfig message:
-#   https://luci-config.appspot.com/schemas/projects:luci-scheduler.cfg
-
-job {
-  id: "crashpad_fuchsia_arm64_dbg"
-  realm: "ci"
-  acl_sets: "ci"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.crashpad.ci"
-    builder: "crashpad_fuchsia_arm64_dbg"
-  }
-}
-job {
-  id: "crashpad_fuchsia_arm64_rel"
-  realm: "ci"
-  acl_sets: "ci"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.crashpad.ci"
-    builder: "crashpad_fuchsia_arm64_rel"
-  }
-}
-job {
-  id: "crashpad_fuchsia_x64_dbg"
-  realm: "ci"
-  acl_sets: "ci"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.crashpad.ci"
-    builder: "crashpad_fuchsia_x64_dbg"
-  }
-}
-job {
-  id: "crashpad_fuchsia_x64_rel"
-  realm: "ci"
-  acl_sets: "ci"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.crashpad.ci"
-    builder: "crashpad_fuchsia_x64_rel"
-  }
-}
-job {
-  id: "crashpad_ios_arm64_dbg"
-  realm: "ci"
-  acl_sets: "ci"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.crashpad.ci"
-    builder: "crashpad_ios_arm64_dbg"
-  }
-}
-job {
-  id: "crashpad_ios_arm64_rel"
-  realm: "ci"
-  acl_sets: "ci"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.crashpad.ci"
-    builder: "crashpad_ios_arm64_rel"
-  }
-}
-job {
-  id: "crashpad_ios_x64_dbg"
-  realm: "ci"
-  acl_sets: "ci"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.crashpad.ci"
-    builder: "crashpad_ios_x64_dbg"
-  }
-}
-job {
-  id: "crashpad_ios_x64_rel"
-  realm: "ci"
-  acl_sets: "ci"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.crashpad.ci"
-    builder: "crashpad_ios_x64_rel"
-  }
-}
-job {
-  id: "crashpad_linux_x64_dbg"
-  realm: "ci"
-  acl_sets: "ci"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.crashpad.ci"
-    builder: "crashpad_linux_x64_dbg"
-  }
-}
-job {
-  id: "crashpad_linux_x64_rel"
-  realm: "ci"
-  acl_sets: "ci"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.crashpad.ci"
-    builder: "crashpad_linux_x64_rel"
-  }
-}
-job {
-  id: "crashpad_mac_x64_dbg"
-  realm: "ci"
-  acl_sets: "ci"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.crashpad.ci"
-    builder: "crashpad_mac_x64_dbg"
-  }
-}
-job {
-  id: "crashpad_mac_x64_rel"
-  realm: "ci"
-  acl_sets: "ci"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.crashpad.ci"
-    builder: "crashpad_mac_x64_rel"
-  }
-}
-job {
-  id: "crashpad_win_x64_dbg"
-  realm: "ci"
-  acl_sets: "ci"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.crashpad.ci"
-    builder: "crashpad_win_x64_dbg"
-  }
-}
-job {
-  id: "crashpad_win_x64_rel"
-  realm: "ci"
-  acl_sets: "ci"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "luci.crashpad.ci"
-    builder: "crashpad_win_x64_rel"
-  }
-}
-trigger {
-  id: "master-gitiles-trigger"
-  realm: "ci"
-  acl_sets: "ci"
-  triggers: "crashpad_fuchsia_arm64_dbg"
-  triggers: "crashpad_fuchsia_arm64_rel"
-  triggers: "crashpad_fuchsia_x64_dbg"
-  triggers: "crashpad_fuchsia_x64_rel"
-  triggers: "crashpad_ios_arm64_dbg"
-  triggers: "crashpad_ios_arm64_rel"
-  triggers: "crashpad_ios_x64_dbg"
-  triggers: "crashpad_ios_x64_rel"
-  triggers: "crashpad_linux_x64_dbg"
-  triggers: "crashpad_linux_x64_rel"
-  triggers: "crashpad_mac_x64_dbg"
-  triggers: "crashpad_mac_x64_rel"
-  triggers: "crashpad_win_x64_dbg"
-  triggers: "crashpad_win_x64_rel"
-  gitiles {
-    repo: "https://chromium.googlesource.com/crashpad/crashpad"
-    refs: "regexp:refs/heads/main"
-  }
-}
-acl_sets {
-  name: "ci"
-  acls {
-    role: OWNER
-    granted_to: "group:project-crashpad-admins"
-  }
-  acls {
-    granted_to: "group:all"
-  }
-}
diff --git a/third_party/crashpad/crashpad/infra/config/generated/project.cfg b/third_party/crashpad/crashpad/infra/config/generated/project.cfg
deleted file mode 100644
index 9dcccbfa..0000000
--- a/third_party/crashpad/crashpad/infra/config/generated/project.cfg
+++ /dev/null
@@ -1,14 +0,0 @@
-# Auto-generated by lucicfg.
-# Do not modify manually.
-#
-# For the schema of this file, see ProjectCfg message:
-#   https://luci-config.appspot.com/schemas/projects:project.cfg
-
-name: "crashpad"
-access: "group:all"
-lucicfg {
-  version: "1.30.1"
-  package_dir: ".."
-  config_dir: "generated"
-  entry_point: "main.star"
-}
diff --git a/third_party/crashpad/crashpad/infra/config/generated/realms.cfg b/third_party/crashpad/crashpad/infra/config/generated/realms.cfg
deleted file mode 100644
index 8dc05f6b..0000000
--- a/third_party/crashpad/crashpad/infra/config/generated/realms.cfg
+++ /dev/null
@@ -1,65 +0,0 @@
-# Auto-generated by lucicfg.
-# Do not modify manually.
-#
-# For the schema of this file, see RealmsCfg message:
-#   https://luci-config.appspot.com/schemas/projects:realms.cfg
-
-realms {
-  name: "@root"
-  bindings {
-    role: "role/buildbucket.reader"
-    principals: "group:all"
-  }
-  bindings {
-    role: "role/configs.reader"
-    principals: "group:all"
-  }
-  bindings {
-    role: "role/logdog.reader"
-    principals: "group:all"
-  }
-  bindings {
-    role: "role/logdog.writer"
-    principals: "group:luci-logdog-chromium-writers"
-  }
-  bindings {
-    role: "role/scheduler.owner"
-    principals: "group:project-crashpad-admins"
-  }
-  bindings {
-    role: "role/scheduler.reader"
-    principals: "group:all"
-  }
-}
-realms {
-  name: "ci"
-  bindings {
-    role: "role/buildbucket.builderServiceAccount"
-    principals: "user:crashpad-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-  }
-  bindings {
-    role: "role/buildbucket.owner"
-    principals: "group:project-crashpad-admins"
-  }
-  bindings {
-    role: "role/buildbucket.triggerer"
-    principals: "user:luci-scheduler@appspot.gserviceaccount.com"
-  }
-}
-realms {
-  name: "try"
-  bindings {
-    role: "role/buildbucket.builderServiceAccount"
-    principals: "user:crashpad-try-builder@chops-service-accounts.iam.gserviceaccount.com"
-  }
-  bindings {
-    role: "role/buildbucket.owner"
-    principals: "group:project-crashpad-admins"
-    principals: "group:service-account-crashpad-cq"
-  }
-  bindings {
-    role: "role/buildbucket.triggerer"
-    principals: "group:project-crashpad-tryjob-access"
-    principals: "group:service-account-cq"
-  }
-}
diff --git a/third_party/crashpad/crashpad/infra/config/main.star b/third_party/crashpad/crashpad/infra/config/main.star
deleted file mode 100755
index f51ef23..0000000
--- a/third_party/crashpad/crashpad/infra/config/main.star
+++ /dev/null
@@ -1,261 +0,0 @@
-#!/usr/bin/env lucicfg
-# Copyright 2021 The Crashpad Authors. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-lucicfg.check_version("1.28.0", "Please update depot_tools")
-
-# Enable LUCI Realms support and Launch 100% of Swarming tasks for builds in
-# "realms-aware mode".
-lucicfg.enable_experiment("crbug.com/1085650")
-
-REPO_URL = "https://chromium.googlesource.com/crashpad/crashpad"
-REVIEW_URL = "https://chromium-review.googlesource.com/crashpad/crashpad"
-
-luci.project(
-    name = "crashpad",
-    buildbucket = "cr-buildbucket.appspot.com",
-    swarming = "chromium-swarm.appspot.com",
-    acls = [
-        acl.entry(
-            roles = [
-                acl.LOGDOG_READER,
-                acl.PROJECT_CONFIGS_READER,
-                acl.SCHEDULER_READER,
-                acl.BUILDBUCKET_READER,
-            ],
-            groups = "all",
-        ),
-        acl.entry(
-            roles = acl.LOGDOG_WRITER,
-            groups = "luci-logdog-chromium-writers",
-        ),
-        acl.entry(
-            roles = acl.SCHEDULER_OWNER,
-            groups = "project-crashpad-admins",
-        ),
-    ],
-    logdog = "luci-logdog.appspot.com",
-    milo = "luci-milo.appspot.com",
-    scheduler = "luci-scheduler.appspot.com",
-)
-
-luci.cq(
-    status_host = "chromium-cq-status.appspot.com",
-    submit_max_burst = 4,
-    submit_burst_delay = 8 * time.minute,
-)
-
-luci.cq_group(
-    name = "crashpad",
-    watch = cq.refset(repo = REVIEW_URL, refs = ["refs/heads/.+"]),
-    retry_config = cq.retry_config(
-        single_quota = 1,
-        global_quota = 2,
-        failure_weight = 1,
-        transient_failure_weight = 1,
-        timeout_weight = 2,
-    ),
-    acls = [
-        acl.entry(
-            roles = acl.CQ_COMMITTER,
-            groups = "project-crashpad-tryjob-access",
-        ),
-        acl.entry(
-            roles = acl.CQ_DRY_RUNNER,
-            groups = "project-crashpad-tryjob-access",
-        ),
-    ],
-)
-
-luci.gitiles_poller(
-    name = "master-gitiles-trigger",
-    bucket = "ci",
-    repo = REPO_URL,
-)
-
-luci.logdog(
-    gs_bucket = "chromium-luci-logdog",
-)
-
-luci.milo(
-    logo = "https://storage.googleapis.com/chrome-infra-public/logo/crashpad-logo.svg",
-)
-
-luci.console_view(
-    name = "main",
-    repo = REPO_URL,
-    title = "Crashpad Main Console",
-)
-
-luci.list_view(
-    name = "try",
-    title = "Crashpad Try Builders",
-)
-
-luci.bucket(
-    name = "ci",
-    acls = [
-        acl.entry(
-            acl.BUILDBUCKET_OWNER,
-            groups = "project-crashpad-admins",
-        ),
-        acl.entry(
-            acl.BUILDBUCKET_TRIGGERER,
-            users = "luci-scheduler@appspot.gserviceaccount.com",
-        ),
-    ],
-)
-
-luci.bucket(
-    name = "try",
-    acls = [
-        acl.entry(
-            acl.BUILDBUCKET_OWNER,
-            groups = [
-                "service-account-crashpad-cq",
-                "project-crashpad-admins",
-            ],
-        ),
-        acl.entry(
-            acl.BUILDBUCKET_TRIGGERER,
-            groups = "service-account-cq",
-        ),
-        acl.entry(
-            acl.BUILDBUCKET_TRIGGERER,
-            groups = "project-crashpad-tryjob-access",
-        ),
-    ],
-)
-
-def crashpad_recipe():
-    return luci.recipe(
-        name = "crashpad/build",
-        cipd_package = "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",
-        use_python3=True,
-    )
-
-def crashpad_caches(platform):
-    if platform == "ios":
-        return [swarming.cache("osx_sdk", name = "osx_sdk_ios")]
-    elif platform == "mac":
-        return [swarming.cache("osx_sdk", name = "osx_sdk_mac")]
-
-def crashpad_dimensions(platform, bucket):
-    dimensions = {}
-    dimensions["cpu"] = "x86-64"
-    dimensions["pool"] = "luci.flex." + bucket
-
-    if platform == "fuchsia":
-        dimensions["os"] = "Ubuntu-16.04"
-    elif platform == "ios":
-        dimensions["os"] = "Mac-10.15"
-    elif platform == "linux":
-        dimensions["os"] = "Ubuntu-16.04"
-    elif platform == "mac":
-        dimensions["os"] = "Mac-10.15"
-    elif platform == "win":
-        dimensions["os"] = "Windows-10"
-
-    if platform == "fuchsia" or platform == "linux" or platform == "win":
-        dimensions["cores"] = "8"
-
-    return dimensions
-
-def crashpad_properties(platform, cpu, config, bucket):
-    properties = {}
-    properties["target_os"] = platform
-    properties["$kitchen"] = {
-        "devshell": True,
-        "git_auth": True,
-    }
-
-    if cpu != "x64":
-        properties["target_cpu"] = cpu
-
-    if bucket == "ci":
-        properties["$gatekeeper"] = {
-            "group": "client.crashpad",
-        }
-
-    if config == "dbg":
-        properties["config"] = "Debug"
-    elif config == "rel":
-        properties["config"] = "Release"
-
-    return properties
-
-def crashpad_builder(platform, cpu, config, bucket):
-    name = "_".join(["crashpad", platform, cpu, config])
-    triggered_by = None
-
-    if bucket == "ci":
-        luci.console_view_entry(
-            builder = "ci/" + name,
-            console_view = "main",
-            short_name = config,
-            category = platform + "|" + cpu,
-        )
-        triggered_by = ["master-gitiles-trigger"]
-    elif bucket == "try":
-        luci.list_view_entry(
-            builder = "try/" + name,
-            list_view = "try",
-        )
-        luci.cq_tryjob_verifier(
-            "try/" + name,
-            cq_group = "crashpad",
-        )
-
-    return luci.builder(
-        name = name,
-        bucket = bucket,
-        executable = crashpad_recipe(),
-        build_numbers = True,
-        caches = crashpad_caches(platform),
-        dimensions = crashpad_dimensions(platform, bucket),
-        execution_timeout = 3 * time.hour,
-        properties = crashpad_properties(platform, cpu, config, bucket),
-        service_account = "crashpad-" + bucket + "-builder@chops-service-accounts.iam.gserviceaccount.com",
-        triggered_by = triggered_by,
-    )
-
-crashpad_builder("fuchsia", "arm64", "dbg", "ci")
-crashpad_builder("fuchsia", "arm64", "rel", "ci")
-crashpad_builder("fuchsia", "x64", "dbg", "ci")
-crashpad_builder("fuchsia", "x64", "rel", "ci")
-crashpad_builder("ios", "arm64", "dbg", "ci")
-crashpad_builder("ios", "arm64", "rel", "ci")
-crashpad_builder("ios", "x64", "dbg", "ci")
-crashpad_builder("ios", "x64", "rel", "ci")
-crashpad_builder("linux", "x64", "dbg", "ci")
-crashpad_builder("linux", "x64", "rel", "ci")
-crashpad_builder("mac", "x64", "dbg", "ci")
-crashpad_builder("mac", "x64", "rel", "ci")
-crashpad_builder("win", "x64", "dbg", "ci")
-crashpad_builder("win", "x64", "rel", "ci")
-
-crashpad_builder("fuchsia", "arm64", "dbg", "try")
-crashpad_builder("fuchsia", "arm64", "rel", "try")
-crashpad_builder("fuchsia", "x64", "dbg", "try")
-crashpad_builder("fuchsia", "x64", "rel", "try")
-crashpad_builder("ios", "arm64", "dbg", "try")
-crashpad_builder("ios", "arm64", "rel", "try")
-crashpad_builder("ios", "x64", "dbg", "try")
-crashpad_builder("ios", "x64", "rel", "try")
-crashpad_builder("linux", "x64", "dbg", "try")
-crashpad_builder("linux", "x64", "rel", "try")
-crashpad_builder("mac", "x64", "dbg", "try")
-crashpad_builder("mac", "x64", "rel", "try")
-crashpad_builder("win", "x64", "dbg", "try")
-crashpad_builder("win", "x64", "rel", "try")
diff --git a/third_party/crashpad/crashpad/snapshot/crashpad_info_client_options_test.cc b/third_party/crashpad/crashpad/snapshot/crashpad_info_client_options_test.cc
index f5baa5bc..4476e21 100644
--- a/third_party/crashpad/crashpad/snapshot/crashpad_info_client_options_test.cc
+++ b/third_party/crashpad/crashpad/snapshot/crashpad_info_client_options_test.cc
@@ -144,7 +144,7 @@
     EXPECT_EQ(options.crashpad_handler_behavior, TriState::kUnset);
     EXPECT_EQ(options.system_crash_reporter_forwarding, TriState::kUnset);
     EXPECT_EQ(options.gather_indirectly_referenced_memory, TriState::kEnabled);
-    EXPECT_EQ(options.indirectly_referenced_memory_cap, 1234u);
+    EXPECT_LE(options.indirectly_referenced_memory_cap, 1234u);
   }
 }
 
diff --git a/third_party/crashpad/crashpad/snapshot/crashpad_info_size_test_note.S b/third_party/crashpad/crashpad/snapshot/crashpad_info_size_test_note.S
index 16b5d49..8b1a0bd 100644
--- a/third_party/crashpad/crashpad/snapshot/crashpad_info_size_test_note.S
+++ b/third_party/crashpad/crashpad/snapshot/crashpad_info_size_test_note.S
@@ -17,6 +17,7 @@
 // that symbol to be in the dynamic symbol table.
 
 #include "util/misc/elf_note_types.h"
+#include "util/misc/arm64_bti_note.S"
 
 // namespace crashpad {
 // CrashpadInfo g_test_crashpad_info;
diff --git a/third_party/crashpad/crashpad/snapshot/elf/elf_image_reader_test_note.S b/third_party/crashpad/crashpad/snapshot/elf/elf_image_reader_test_note.S
index 9ab0338..08f1829 100644
--- a/third_party/crashpad/crashpad/snapshot/elf/elf_image_reader_test_note.S
+++ b/third_party/crashpad/crashpad/snapshot/elf/elf_image_reader_test_note.S
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "util/misc/elf_note_types.h"
+#include "util/misc/arm64_bti_note.S"
 
 #define NOTE_ALIGN 4
   .section .note.crashpad.test,"a",%note
diff --git a/third_party/crashpad/crashpad/snapshot/fuchsia/process_snapshot_fuchsia_test.cc b/third_party/crashpad/crashpad/snapshot/fuchsia/process_snapshot_fuchsia_test.cc
index 496d97a8..9389868 100644
--- a/third_party/crashpad/crashpad/snapshot/fuchsia/process_snapshot_fuchsia_test.cc
+++ b/third_party/crashpad/crashpad/snapshot/fuchsia/process_snapshot_fuchsia_test.cc
@@ -56,7 +56,7 @@
   // correctly.
   for (const auto& t : kTestMappingPermAndSizes) {
     zx_handle_t vmo = ZX_HANDLE_INVALID;
-    const size_t size = t.pages * PAGE_SIZE;
+    const size_t size = t.pages * zx_system_get_page_size();
     zx_status_t status = zx_vmo_create(size, 0, &vmo);
     ZX_CHECK(status == ZX_OK, status) << "zx_vmo_create";
     status = zx_vmo_replace_as_executable(vmo, ZX_HANDLE_INVALID, &vmo);
@@ -126,7 +126,7 @@
       const auto& t = kTestMappingPermAndSizes[i];
       EXPECT_TRUE(HasSingleMatchingMapping(process_snapshot.MemoryMap(),
                                            test_addresses[i],
-                                           t.pages * PAGE_SIZE,
+                                           t.pages * zx_system_get_page_size(),
                                            t.minidump_perm))
           << base::StringPrintf(
                  "index %zu, zircon_perm 0x%x, minidump_perm 0x%x",
diff --git a/third_party/crashpad/crashpad/snapshot/linux/capture_memory_delegate_linux.cc b/third_party/crashpad/crashpad/snapshot/linux/capture_memory_delegate_linux.cc
index 4f15874..918e572 100644
--- a/third_party/crashpad/crashpad/snapshot/linux/capture_memory_delegate_linux.cc
+++ b/third_party/crashpad/crashpad/snapshot/linux/capture_memory_delegate_linux.cc
@@ -57,19 +57,17 @@
     return;
   if (range.size() == 0)
     return;
-  if (budget_remaining_ && *budget_remaining_ == 0)
+  if (!budget_remaining_ || *budget_remaining_ == 0)
     return;
   snapshots_->push_back(std::make_unique<internal::MemorySnapshotGeneric>());
   internal::MemorySnapshotGeneric* snapshot = snapshots_->back().get();
   snapshot->Initialize(process_reader_->Memory(), range.base(), range.size());
-  if (budget_remaining_) {
-    if (!base::IsValueInRangeForNumericType<int64_t>(range.size())) {
-      *budget_remaining_ = 0;
-    } else {
-      int64_t temp = *budget_remaining_;
-      temp -= range.size();
-      *budget_remaining_ = base::saturated_cast<uint32_t>(temp);
-    }
+  if (!base::IsValueInRangeForNumericType<int64_t>(range.size())) {
+    *budget_remaining_ = 0;
+  } else {
+    int64_t temp = *budget_remaining_;
+    temp -= range.size();
+    *budget_remaining_ = base::saturated_cast<uint32_t>(temp);
   }
 }
 
diff --git a/third_party/crashpad/crashpad/snapshot/linux/exception_snapshot_linux.cc b/third_party/crashpad/crashpad/snapshot/linux/exception_snapshot_linux.cc
index 42d0eb0..6726d048 100644
--- a/third_party/crashpad/crashpad/snapshot/linux/exception_snapshot_linux.cc
+++ b/third_party/crashpad/crashpad/snapshot/linux/exception_snapshot_linux.cc
@@ -326,10 +326,12 @@
 
 #endif  // ARCH_CPU_X86_FAMILY
 
-bool ExceptionSnapshotLinux::Initialize(ProcessReaderLinux* process_reader,
-                                        LinuxVMAddress siginfo_address,
-                                        LinuxVMAddress context_address,
-                                        pid_t thread_id) {
+bool ExceptionSnapshotLinux::Initialize(
+    ProcessReaderLinux* process_reader,
+    LinuxVMAddress siginfo_address,
+    LinuxVMAddress context_address,
+    pid_t thread_id,
+    uint32_t* gather_indirectly_referenced_memory_cap) {
   INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
 
   thread_id_ = thread_id;
@@ -359,7 +361,10 @@
   }
 
   CaptureMemoryDelegateLinux capture_memory_delegate(
-      process_reader, thread, &extra_memory_, nullptr);
+      process_reader,
+      thread,
+      &extra_memory_,
+      gather_indirectly_referenced_memory_cap);
   CaptureMemory::PointedToByContext(context_, &capture_memory_delegate);
 
   INITIALIZATION_STATE_SET_VALID(initialized_);
diff --git a/third_party/crashpad/crashpad/snapshot/linux/exception_snapshot_linux.h b/third_party/crashpad/crashpad/snapshot/linux/exception_snapshot_linux.h
index 1719f0b..05f6004 100644
--- a/third_party/crashpad/crashpad/snapshot/linux/exception_snapshot_linux.h
+++ b/third_party/crashpad/crashpad/snapshot/linux/exception_snapshot_linux.h
@@ -59,7 +59,8 @@
   bool Initialize(ProcessReaderLinux* process_reader,
                   LinuxVMAddress siginfo_address,
                   LinuxVMAddress context_address,
-                  pid_t thread_id);
+                  pid_t thread_id,
+                  uint32_t* gather_indirectly_referenced_memory_cap);
 
   // ExceptionSnapshot:
 
diff --git a/third_party/crashpad/crashpad/snapshot/linux/exception_snapshot_linux_test.cc b/third_party/crashpad/crashpad/snapshot/linux/exception_snapshot_linux_test.cc
index 51d3fd3..91be497 100644
--- a/third_party/crashpad/crashpad/snapshot/linux/exception_snapshot_linux_test.cc
+++ b/third_party/crashpad/crashpad/snapshot/linux/exception_snapshot_linux_test.cc
@@ -320,7 +320,8 @@
   ASSERT_TRUE(exception.Initialize(&process_reader,
                                    FromPointerCast<LinuxVMAddress>(&siginfo),
                                    FromPointerCast<LinuxVMAddress>(&context),
-                                   gettid()));
+                                   gettid(),
+                                   nullptr));
   EXPECT_EQ(exception.Exception(), static_cast<uint32_t>(siginfo.si_signo));
   EXPECT_EQ(exception.ExceptionInfo(), static_cast<uint32_t>(siginfo.si_code));
   EXPECT_EQ(exception.ExceptionAddress(),
@@ -393,7 +394,8 @@
     ASSERT_TRUE(exception.Initialize(&process_reader,
                                      FromPointerCast<LinuxVMAddress>(siginfo),
                                      FromPointerCast<LinuxVMAddress>(context),
-                                     gettid()));
+                                     gettid(),
+                                     nullptr));
 
     EXPECT_EQ(exception.Exception(), static_cast<uint32_t>(kSigno));
 
@@ -464,7 +466,8 @@
     ASSERT_TRUE(exception.Initialize(&process_reader,
                                      FromPointerCast<LinuxVMAddress>(siginfo),
                                      FromPointerCast<LinuxVMAddress>(context),
-                                     gettid()));
+                                     gettid(),
+                                     nullptr));
 
     EXPECT_EQ(exception.Exception(), static_cast<uint32_t>(kSigno));
 
diff --git a/third_party/crashpad/crashpad/snapshot/linux/process_snapshot_linux.cc b/third_party/crashpad/crashpad/snapshot/linux/process_snapshot_linux.cc
index 62331347..a730b29 100644
--- a/third_party/crashpad/crashpad/snapshot/linux/process_snapshot_linux.cc
+++ b/third_party/crashpad/crashpad/snapshot/linux/process_snapshot_linux.cc
@@ -42,10 +42,9 @@
   client_id_.InitializeToZero();
   system_.Initialize(&process_reader_, &snapshot_time_);
 
-  GetCrashpadOptionsInternal((&options_));
-
-  InitializeThreads();
   InitializeModules();
+  GetCrashpadOptionsInternal((&options_));
+  InitializeThreads();
   InitializeAnnotations();
 
   INITIALIZATION_STATE_SET_VALID(initialized_);
@@ -83,11 +82,17 @@
     info.thread_id = exception_thread_id;
   }
 
+  uint32_t* budget_remaining_pointer =
+      options_.gather_indirectly_referenced_memory == TriState::kEnabled
+          ? &options_.indirectly_referenced_memory_cap
+          : nullptr;
+
   exception_.reset(new internal::ExceptionSnapshotLinux());
   if (!exception_->Initialize(&process_reader_,
                               info.siginfo_address,
                               info.context_address,
-                              info.thread_id)) {
+                              info.thread_id,
+                              budget_remaining_pointer)) {
     exception_.reset();
     return false;
   }
@@ -269,11 +274,11 @@
 void ProcessSnapshotLinux::InitializeThreads() {
   const std::vector<ProcessReaderLinux::Thread>& process_reader_threads =
       process_reader_.Threads();
-  uint32_t* budget_remaining_pointer = nullptr;
-  uint32_t budget_remaining = options_.indirectly_referenced_memory_cap;
-  if (options_.gather_indirectly_referenced_memory == TriState::kEnabled) {
-    budget_remaining_pointer = &budget_remaining;
-  }
+  uint32_t* budget_remaining_pointer =
+      options_.gather_indirectly_referenced_memory == TriState::kEnabled
+          ? &options_.indirectly_referenced_memory_cap
+          : nullptr;
+
   for (const ProcessReaderLinux::Thread& process_reader_thread :
        process_reader_threads) {
     auto thread = std::make_unique<internal::ThreadSnapshotLinux>();
diff --git a/third_party/crashpad/crashpad/snapshot/win/capture_memory_delegate_win.cc b/third_party/crashpad/crashpad/snapshot/win/capture_memory_delegate_win.cc
index ee5e5d64..43067ca 100644
--- a/third_party/crashpad/crashpad/snapshot/win/capture_memory_delegate_win.cc
+++ b/third_party/crashpad/crashpad/snapshot/win/capture_memory_delegate_win.cc
@@ -55,19 +55,17 @@
     return;
   if (range.size() == 0)
     return;
-  if (budget_remaining_ && *budget_remaining_ == 0)
+  if (!budget_remaining_ || *budget_remaining_ == 0)
     return;
   snapshots_->push_back(std::make_unique<internal::MemorySnapshotGeneric>());
   internal::MemorySnapshotGeneric* snapshot = snapshots_->back().get();
   snapshot->Initialize(process_reader_->Memory(), range.base(), range.size());
-  if (budget_remaining_) {
-    if (!base::IsValueInRangeForNumericType<int64_t>(range.size())) {
-      *budget_remaining_ = 0;
-    } else {
-      int64_t temp = *budget_remaining_;
-      temp -= range.size();
-      *budget_remaining_ = base::saturated_cast<uint32_t>(temp);
-    }
+  if (!base::IsValueInRangeForNumericType<int64_t>(range.size())) {
+    *budget_remaining_ = 0;
+  } else {
+    int64_t temp = *budget_remaining_;
+    temp -= range.size();
+    *budget_remaining_ = base::saturated_cast<uint32_t>(temp);
   }
 }
 
diff --git a/third_party/crashpad/crashpad/snapshot/win/exception_snapshot_win.cc b/third_party/crashpad/crashpad/snapshot/win/exception_snapshot_win.cc
index 5759652..5b306cd 100644
--- a/third_party/crashpad/crashpad/snapshot/win/exception_snapshot_win.cc
+++ b/third_party/crashpad/crashpad/snapshot/win/exception_snapshot_win.cc
@@ -84,7 +84,8 @@
 bool ExceptionSnapshotWin::Initialize(
     ProcessReaderWin* process_reader,
     DWORD thread_id,
-    WinVMAddress exception_pointers_address) {
+    WinVMAddress exception_pointers_address,
+    uint32_t* gather_indirectly_referenced_memory_cap) {
   INITIALIZATION_STATE_SET_INITIALIZING(initialized_);
 
   const ProcessReaderWin::Thread* thread = nullptr;
@@ -132,7 +133,10 @@
 #endif
 
   CaptureMemoryDelegateWin capture_memory_delegate(
-      process_reader, *thread, &extra_memory_, nullptr);
+      process_reader,
+      *thread,
+      &extra_memory_,
+      gather_indirectly_referenced_memory_cap);
   CaptureMemory::PointedToByContext(context_, &capture_memory_delegate);
 
   INITIALIZATION_STATE_SET_VALID(initialized_);
diff --git a/third_party/crashpad/crashpad/snapshot/win/exception_snapshot_win.h b/third_party/crashpad/crashpad/snapshot/win/exception_snapshot_win.h
index 9167a9ef..b8fa735 100644
--- a/third_party/crashpad/crashpad/snapshot/win/exception_snapshot_win.h
+++ b/third_party/crashpad/crashpad/snapshot/win/exception_snapshot_win.h
@@ -72,7 +72,8 @@
   //!     an appropriate message logged.
   bool Initialize(ProcessReaderWin* process_reader,
                   DWORD thread_id,
-                  WinVMAddress exception_pointers);
+                  WinVMAddress exception_pointers,
+                  uint32_t* gather_indirectly_referenced_memory_cap);
 
   // ExceptionSnapshot:
 
diff --git a/third_party/crashpad/crashpad/snapshot/win/process_snapshot_win.cc b/third_party/crashpad/crashpad/snapshot/win/process_snapshot_win.cc
index b60ea38..fee843c 100644
--- a/third_party/crashpad/crashpad/snapshot/win/process_snapshot_win.cc
+++ b/third_party/crashpad/crashpad/snapshot/win/process_snapshot_win.cc
@@ -63,24 +63,6 @@
   if (!process_reader_.Initialize(process, suspension_state))
     return false;
 
-  if (exception_information_address != 0) {
-    ExceptionInformation exception_information = {};
-    if (!process_reader_.Memory()->Read(exception_information_address,
-                                        sizeof(exception_information),
-                                        &exception_information)) {
-      LOG(WARNING) << "ReadMemory ExceptionInformation failed";
-      return false;
-    }
-
-    exception_.reset(new internal::ExceptionSnapshotWin());
-    if (!exception_->Initialize(&process_reader_,
-                                exception_information.thread_id,
-                                exception_information.exception_pointers)) {
-      exception_.reset();
-      return false;
-    }
-  }
-
   client_id_.InitializeToZero();
   system_.Initialize(&process_reader_);
 
@@ -96,10 +78,31 @@
   InitializeUnloadedModules();
 
   GetCrashpadOptionsInternal(&options_);
+  uint32_t* budget_remaining_pointer =
+      options_.gather_indirectly_referenced_memory == TriState::kEnabled
+          ? &options_.indirectly_referenced_memory_cap
+          : nullptr;
 
-  InitializeThreads(
-      options_.gather_indirectly_referenced_memory == TriState::kEnabled,
-      options_.indirectly_referenced_memory_cap);
+  if (exception_information_address != 0) {
+    ExceptionInformation exception_information = {};
+    if (!process_reader_.Memory()->Read(exception_information_address,
+                                        sizeof(exception_information),
+                                        &exception_information)) {
+      LOG(WARNING) << "ReadMemory ExceptionInformation failed";
+      return false;
+    }
+
+    exception_.reset(new internal::ExceptionSnapshotWin());
+    if (!exception_->Initialize(&process_reader_,
+                                exception_information.thread_id,
+                                exception_information.exception_pointers,
+                                budget_remaining_pointer)) {
+      exception_.reset();
+      return false;
+    }
+  }
+
+  InitializeThreads(budget_remaining_pointer);
 
   for (const MEMORY_BASIC_INFORMATION64& mbi :
        process_reader_.GetProcessInfo().MemoryInfo()) {
@@ -239,15 +242,9 @@
   return process_reader_.Memory();
 }
 
-void ProcessSnapshotWin::InitializeThreads(
-    bool gather_indirectly_referenced_memory,
-    uint32_t indirectly_referenced_memory_cap) {
+void ProcessSnapshotWin::InitializeThreads(uint32_t* budget_remaining_pointer) {
   const std::vector<ProcessReaderWin::Thread>& process_reader_threads =
       process_reader_.Threads();
-  uint32_t* budget_remaining_pointer = nullptr;
-  uint32_t budget_remaining = indirectly_referenced_memory_cap;
-  if (gather_indirectly_referenced_memory)
-    budget_remaining_pointer = &budget_remaining;
   for (const ProcessReaderWin::Thread& process_reader_thread :
        process_reader_threads) {
     auto thread = std::make_unique<internal::ThreadSnapshotWin>();
diff --git a/third_party/crashpad/crashpad/snapshot/win/process_snapshot_win.h b/third_party/crashpad/crashpad/snapshot/win/process_snapshot_win.h
index 7a8ae2fd..0021070 100644
--- a/third_party/crashpad/crashpad/snapshot/win/process_snapshot_win.h
+++ b/third_party/crashpad/crashpad/snapshot/win/process_snapshot_win.h
@@ -137,8 +137,7 @@
 
  private:
   // Initializes threads_ on behalf of Initialize().
-  void InitializeThreads(bool gather_indirectly_referenced_memory,
-                         uint32_t indirectly_referenced_memory_cap);
+  void InitializeThreads(uint32_t* indirectly_referenced_memory_cap);
 
   // Initializes modules_ on behalf of Initialize().
   void InitializeModules();
diff --git a/third_party/crashpad/crashpad/util/misc/arm64_bti_note.S b/third_party/crashpad/crashpad/util/misc/arm64_bti_note.S
new file mode 100644
index 0000000..987493b
--- /dev/null
+++ b/third_party/crashpad/crashpad/util/misc/arm64_bti_note.S
@@ -0,0 +1,46 @@
+// Copyright 2021 The Crashpad Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef CRASHPAD_UTIL_MISC_ARM64_BTI_NOTE_S
+#define CRASHPAD_UTIL_MISC_ARM64_BTI_NOTE_S
+
+/* Support macros for the Armv8.5-A Branch Target Identification feature which
+ * requires emitting a .note.gnu.property section with the appropriate
+ * architecture-dependent feature bits set.
+ * Read more: "ELF for the Arm® 64-bit Architecture"
+ */
+#if defined(__ARM_FEATURE_BTI_DEFAULT) && (__ARM_FEATURE_BTI_DEFAULT == 1)
+#define GNU_PROPERTY_AARCH64_BTI (1 << 0)  // Has BTI
+  .pushsection .note.gnu.property, "a"
+  .balign 4
+  .long 0x4  /* size of field "GNU" */
+  .long 0x10 /* note descriptor size */
+  .long 0x5  /* type of note descriptor: NT_GNU_PROPERTY_TYPE_0 */
+  .asciz "GNU"
+  .long 0xc0000000 /* GNU_PROPERTY_AARCH64_FEATURE_1_AND */
+  .long 0x4
+  .long GNU_PROPERTY_AARCH64_BTI
+  .long 0x0
+  .popsection
+#define CRASHPAD_AARCH64_VALID_JUMP_CALL_TARGET bti jc
+#define CRASHPAD_AARCH64_VALID_CALL_TARGET      bti c
+#define CRASHPAD_AARCH64_VALID_JUMP_TARGET      bti j
+#undef GNU_PROPERTY_AARCH64_BTI
+#else
+#define CRASHPAD_AARCH64_VALID_JUMP_CALL_TARGET
+#define CRASHPAD_AARCH64_VALID_CALL_TARGET
+#define CRASHPAD_AARCH64_VALID_JUMP_TARGET
+#endif
+
+#endif /* CRASHPAD_UTIL_MISC_ARM64_BTI_NOTE_S */
diff --git a/third_party/crashpad/crashpad/util/misc/capture_context_linux.S b/third_party/crashpad/crashpad/util/misc/capture_context_linux.S
index 52215ee..0ee561f 100644
--- a/third_party/crashpad/crashpad/util/misc/capture_context_linux.S
+++ b/third_party/crashpad/crashpad/util/misc/capture_context_linux.S
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "util/misc/arm64_bti_note.S"
+
 // namespace crashpad {
 // void CaptureContext(ucontext_t* context);
 // }  // namespace crashpad
@@ -38,6 +40,7 @@
 
 CAPTURECONTEXT_SYMBOL:
 CAPTURECONTEXT_SYMBOL2:
+  CRASHPAD_AARCH64_VALID_CALL_TARGET
 
 #if defined(__i386__)
 
diff --git a/third_party/crashpad/update.py b/third_party/crashpad/update.py
index f99640f8c..6758d6b9 100755
--- a/third_party/crashpad/update.py
+++ b/third_party/crashpad/update.py
@@ -76,7 +76,7 @@
         dest='readme_path')
     parser.add_argument(
         '--exclude',
-        default=['codereview.settings'],
+        default=['codereview.settings', 'infra'],
         action='append',
         help='Files to exclude from the imported copy',
         metavar='PATH')
@@ -137,7 +137,7 @@
          'filter-branch',
          '--force',
          '--index-filter',
-         'git rm --cached --ignore-unmatch ' +
+         'git rm -r --cached --ignore-unmatch ' +
              ' '.join(pipes.quote(path) for path in parsed.exclude),
          revision_old + '..UPDATE_TO'],
         cwd=toplevel,
diff --git a/third_party/distributed_point_functions/DEPS b/third_party/distributed_point_functions/DEPS
index fa43b60..fa1f248 100644
--- a/third_party/distributed_point_functions/DEPS
+++ b/third_party/distributed_point_functions/DEPS
@@ -1,7 +1,6 @@
 include_rules = [
   "+absl",
   "+benchmark",
-  "+dcf",
   "+dpf",
   "+google/protobuf",
 ]
diff --git a/third_party/distributed_point_functions/README.chromium b/third_party/distributed_point_functions/README.chromium
index e1212d0..3fced6a 100644
--- a/third_party/distributed_point_functions/README.chromium
+++ b/third_party/distributed_point_functions/README.chromium
@@ -14,10 +14,11 @@
 functions, based on the paper by Boneh et al.
 
 Local Modifications:
-The directory code/ is an unchanged copy of the source code other than the
-addition of a .clang-format file to disable automatic code formatting. Parts of
-code/dpf/distributed_point_function_test.cc are also adapted for fuzzing in
-fuzz/dpf_fuzzer.cc.
+The directory code/ is a copy of the source code, modified in two ways. First,
+all top-level directories other than dpf/ have been removed as they are unused.
+Second, a .clang-format file has been added to disable automatic code
+formatting. Parts of code/dpf/distributed_point_function_test.cc are also
+adapted for fuzzing in fuzz/dpf_fuzzer.cc.
 
 The source code pulled in depends on "glog/logging.h" which is not accessible
 from chromium. As a workaround, we create a simple glog/logging.h that includes
diff --git a/third_party/distributed_point_functions/code/.bazelci/presubmit.yml b/third_party/distributed_point_functions/code/.bazelci/presubmit.yml
deleted file mode 100644
index 02fccaf..0000000
--- a/third_party/distributed_point_functions/code/.bazelci/presubmit.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright 2021 Google LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-tasks:
-  ubuntu2004:
-    build_targets:
-      - "//..."
-    test_flags:
-      - "--test_tag_filters=-benchmark"
-    test_targets:
-      - "//..."
diff --git a/third_party/distributed_point_functions/code/dcf/BUILD b/third_party/distributed_point_functions/code/dcf/BUILD
deleted file mode 100644
index a62f03f..0000000
--- a/third_party/distributed_point_functions/code/dcf/BUILD
+++ /dev/null
@@ -1,48 +0,0 @@
-load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
-load("@rules_cc//cc:defs.bzl", "cc_library")
-load("@rules_proto//proto:defs.bzl", "proto_library")
-
-package(
-    default_visibility = ["//visibility:public"],
-)
-
-licenses(["notice"])
-
-cc_library(
-    name = "distributed_comparison_function",
-    srcs = ["distributed_comparison_function.cc"],
-    hdrs = ["distributed_comparison_function.h"],
-    deps = [
-        ":distributed_comparison_function_cc_proto",
-        "//dpf:distributed_point_function",
-        "//dpf:distributed_point_function_cc_proto",
-        "//dpf:status_macros",
-        "@com_google_absl//absl/meta:type_traits",
-        "@com_google_absl//absl/status:statusor",
-    ],
-)
-
-proto_library(
-    name = "distributed_comparison_function_proto",
-    srcs = ["distributed_comparison_function.proto"],
-    deps = [
-        "//dpf:distributed_point_function_proto",
-    ],
-)
-
-cc_proto_library(
-    name = "distributed_comparison_function_cc_proto",
-    deps = [":distributed_comparison_function_proto"],
-)
-
-cc_test(
-    name = "distributed_comparison_function_test",
-    srcs = ["distributed_comparison_function_test.cc"],
-    deps = [
-        ":distributed_comparison_function",
-        "//dpf/internal:status_matchers",
-        "@com_github_google_googletest//:gtest_main",
-        "@com_google_absl//absl/random",
-        "@com_google_absl//absl/utility",
-    ],
-)
diff --git a/third_party/distributed_point_functions/code/dcf/distributed_comparison_function.cc b/third_party/distributed_point_functions/code/dcf/distributed_comparison_function.cc
deleted file mode 100644
index 20db2bb..0000000
--- a/third_party/distributed_point_functions/code/dcf/distributed_comparison_function.cc
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright 2021 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "dcf/distributed_comparison_function.h"
-
-#include "dpf/status_macros.h"
-
-namespace distributed_point_functions {
-
-namespace {
-
-void SetToZero(Value& value) {
-  if (value.value_case() == Value::kInteger) {
-    value.mutable_integer()->set_value_uint64(0);
-  } else if (value.value_case() == Value::kIntModN) {
-    value.mutable_int_mod_n()->set_value_uint64(0);
-  } else if (value.value_case() == Value::kTuple) {
-    for (int i = 0; i < value.tuple().elements_size(); ++i) {
-      SetToZero(*(value.mutable_tuple()->mutable_elements(i)));
-    }
-  }
-}
-
-}  // namespace
-
-DistributedComparisonFunction::DistributedComparisonFunction(
-    DcfParameters parameters, std::unique_ptr<DistributedPointFunction> dpf)
-    : parameters_(std::move(parameters)), dpf_(std::move(dpf)) {}
-
-absl::StatusOr<std::unique_ptr<DistributedComparisonFunction>>
-DistributedComparisonFunction::Create(const DcfParameters& parameters) {
-  // A DCF with a single-element domain doesn't make sense.
-  if (parameters.parameters().log_domain_size() < 1) {
-    return absl::InvalidArgumentError("A DCF must have log_domain_size >= 1");
-  }
-
-  // We don't support the legacy element_bitsize field in DCFs.
-  if (!parameters.parameters().has_value_type()) {
-    return absl::InvalidArgumentError(
-        "parameters.value_type must be set for "
-        "DistributedComparisonFunction::Create");
-  }
-
-  // Create parameter vector for the incremental DPF.
-  std::vector<DpfParameters> dpf_parameters(
-      parameters.parameters().log_domain_size());
-  for (int i = 0; i < static_cast<int>(dpf_parameters.size()); ++i) {
-    dpf_parameters[i].set_log_domain_size(i);
-    *(dpf_parameters[i].mutable_value_type()) =
-        parameters.parameters().value_type();
-  }
-
-  // Check that parameters are valid. We can use the DPF proto validator
-  // directly.
-  DPF_RETURN_IF_ERROR(
-      dpf_internal::ProtoValidator::ValidateParameters(dpf_parameters));
-
-  // Create incremental DPF.
-  DPF_ASSIGN_OR_RETURN(
-      std::unique_ptr<DistributedPointFunction> dpf,
-      DistributedPointFunction::CreateIncremental(dpf_parameters));
-
-  return absl::WrapUnique(
-      new DistributedComparisonFunction(parameters, std::move(dpf)));
-}
-
-absl::StatusOr<std::pair<DcfKey, DcfKey>>
-DistributedComparisonFunction::GenerateKeys(absl::uint128 alpha,
-                                            const Value& beta) {
-  const int log_domain_size = parameters_.parameters().log_domain_size();
-  std::vector<Value> dpf_values(log_domain_size, beta);
-  for (int i = 0; i < log_domain_size; ++i) {
-    // beta_i = 0 if alpha_i == 0, and beta otherwise.
-    bool current_bit =
-        (alpha & (absl::uint128{1} << (log_domain_size - i - 1))) != 0;
-    if (!current_bit) {
-      SetToZero(dpf_values[i]);
-    }
-  }
-
-  std::pair<DcfKey, DcfKey> result;
-  DPF_ASSIGN_OR_RETURN(
-      std::tie(*(result.first.mutable_key()), *(result.second.mutable_key())),
-      dpf_->GenerateKeysIncremental(
-          alpha >> 1,  // We can ignore the last bit of alpha, since it is
-                       // encoded in dpf_values.back().
-          dpf_values));
-  return result;
-}
-
-}  // namespace distributed_point_functions
diff --git a/third_party/distributed_point_functions/code/dcf/distributed_comparison_function.h b/third_party/distributed_point_functions/code/dcf/distributed_comparison_function.h
deleted file mode 100644
index 36527e3..0000000
--- a/third_party/distributed_point_functions/code/dcf/distributed_comparison_function.h
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright 2021 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef DISTRIBUTED_POINT_FUNCTIONS_DCF_DISTRIBUTED_COMPARISON_FUNCTION_H_
-#define DISTRIBUTED_POINT_FUNCTIONS_DCF_DISTRIBUTED_COMPARISON_FUNCTION_H_
-
-#include <memory>
-
-#include "absl/meta/type_traits.h"
-#include "absl/status/statusor.h"
-#include "dcf/distributed_comparison_function.pb.h"
-#include "dpf/distributed_point_function.h"
-#include "dpf/distributed_point_function.pb.h"
-
-namespace distributed_point_functions {
-
-class DistributedComparisonFunction {
- public:
-  static absl::StatusOr<std::unique_ptr<DistributedComparisonFunction>> Create(
-      const DcfParameters& parameters);
-
-  // Creates keys for a DCF that evaluates to shares of `beta` on any input x <
-  // `alpha`, and shares of 0 otherwise.
-  //
-  // Returns INVALID_ARGUMENT if `alpha` or `beta` do not match the
-  // DcfParameters passed at construction.
-  //
-  // Overload for explicit Value proto.
-  absl::StatusOr<std::pair<DcfKey, DcfKey>> GenerateKeys(absl::uint128 alpha,
-                                                         const Value& beta);
-
-  // Template for automatic conversion to Value proto. Disabled if the argument
-  // is convertible to `absl::uint128` or `Value` to make overloading
-  // unambiguous.
-  template <typename T, typename = absl::enable_if_t<
-                            !std::is_convertible<T, Value>::value &&
-                            is_supported_type_v<T>>>
-  absl::StatusOr<std::pair<DcfKey, DcfKey>> GenerateKeys(absl::uint128 alpha,
-                                                         const T& beta) {
-    absl::StatusOr<Value> value = dpf_->ToValue(beta);
-    if (!value.ok()) {
-      return value.status();
-    }
-    return GenerateKeys(alpha, *value);
-  }
-
-  // Evaluates a DcfKey at the given point `x`.
-  //
-  // Returns INVALID_ARGUMENT if `key` or `x` do not match the parameters passed
-  // at construction.
-  template <typename T>
-  absl::StatusOr<T> Evaluate(const DcfKey& key, absl::uint128 x);
-
-  // DistributedComparisonFunction is neither copyable nor movable.
-  DistributedComparisonFunction(const DistributedComparisonFunction&) = delete;
-  DistributedComparisonFunction& operator=(
-      const DistributedComparisonFunction&) = delete;
-
- private:
-  DistributedComparisonFunction(DcfParameters parameters,
-                                std::unique_ptr<DistributedPointFunction> dpf);
-
-  const DcfParameters parameters_;
-  const std::unique_ptr<DistributedPointFunction> dpf_;
-};
-
-// Implementation details.
-
-template <typename T>
-absl::StatusOr<T> DistributedComparisonFunction::Evaluate(const DcfKey& key,
-                                                          absl::uint128 x) {
-  const int log_domain_size = parameters_.parameters().log_domain_size();
-  T result{};
-
-  absl::StatusOr<EvaluationContext> ctx =
-      dpf_->CreateEvaluationContext(key.key());
-  if (!ctx.ok()) {
-    return ctx.status();
-  }
-
-  int previous_bit = 0;
-  for (int i = 0; i < log_domain_size; ++i) {
-    absl::StatusOr<std::vector<T>> expansion_i;
-    if (i == 0) {
-      expansion_i = dpf_->EvaluateNext<T>({}, *ctx);
-    } else {
-      absl::uint128 prefix = 0;
-      if (log_domain_size < 128) {
-        prefix = x >> (log_domain_size - i + 1);
-      }
-      expansion_i =
-          dpf_->EvaluateNext<T>(absl::MakeConstSpan(&prefix, 1), *ctx);
-    }
-    if (!expansion_i.ok()) {
-      return expansion_i.status();
-    }
-
-    int current_bit = static_cast<int>(
-        (x & (absl::uint128{1} << (log_domain_size - i - 1))) != 0);
-    // We only add the current value along the path if the current bit of x is
-    // 0.
-    if (current_bit == 0) {
-      result += (*expansion_i)[previous_bit];
-    }
-    previous_bit = current_bit;
-  }
-  return result;
-}
-
-}  // namespace distributed_point_functions
-
-#endif  // DISTRIBUTED_POINT_FUNCTIONS_DCF_DISTRIBUTED_COMPARISON_FUNCTION_H_
diff --git a/third_party/distributed_point_functions/code/dcf/distributed_comparison_function.proto b/third_party/distributed_point_functions/code/dcf/distributed_comparison_function.proto
deleted file mode 100644
index dd69043..0000000
--- a/third_party/distributed_point_functions/code/dcf/distributed_comparison_function.proto
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2021 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-syntax = "proto3";
-
-package distributed_point_functions;
-
-import "dpf/distributed_point_function.proto";
-
-// For faster allocations of sub-messages.
-option cc_enable_arenas = true;
-
-// The parameters for a DCF have the same form as for a DPF.
-message DcfParameters {
-  DpfParameters parameters = 1;
-}
-
-// A DCF key is just a special DpfKey.
-message DcfKey {
-  DpfKey key = 1;
-}
\ No newline at end of file
diff --git a/third_party/distributed_point_functions/code/dcf/distributed_comparison_function_test.cc b/third_party/distributed_point_functions/code/dcf/distributed_comparison_function_test.cc
deleted file mode 100644
index 7d12894cb..0000000
--- a/third_party/distributed_point_functions/code/dcf/distributed_comparison_function_test.cc
+++ /dev/null
@@ -1,180 +0,0 @@
-// Copyright 2021 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "dcf/distributed_comparison_function.h"
-
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-
-#include "absl/random/random.h"
-#include "absl/utility/utility.h"
-#include "dpf/internal/status_matchers.h"
-
-namespace distributed_point_functions {
-
-namespace {
-
-// Helper function that recursively sets all elements of a tuple to 42.
-template <typename T0>
-static void SetTo42(T0& x) {
-  x = T0(42);
-}
-template <typename T0, typename... Tn>
-static void SetTo42(T0& x0, Tn&... xn) {
-  SetTo42(x0);
-  SetTo42(xn...);
-}
-template <typename... Tn>
-static void SetTo42(Tuple<Tn...>& x) {
-  absl::apply([](auto&... in) { SetTo42(in...); }, x.value());
-}
-
-TEST(DcfTest, CreateFailsWithZeroLogDomainSize) {
-  DcfParameters parameters;
-  parameters.mutable_parameters()
-      ->mutable_value_type()
-      ->mutable_integer()
-      ->set_bitsize(32);
-
-  parameters.mutable_parameters()->set_log_domain_size(0);
-
-  EXPECT_THAT(DistributedComparisonFunction::Create(parameters),
-              dpf_internal::StatusIs(absl::StatusCode::kInvalidArgument,
-                                     "A DCF must have log_domain_size >= 1"));
-}
-
-template <typename T, int log_domain_size>
-class DcfTestParameters {
- public:
-  using ValueType = T;
-  static constexpr int kLogDomainSize = log_domain_size;
-};
-
-template <typename T>
-struct DcfTest : public testing::Test {
-  void SetUp() {
-    DcfParameters parameters;
-    parameters.mutable_parameters()->set_log_domain_size(T::kLogDomainSize);
-    *(parameters.mutable_parameters()->mutable_value_type()) =
-        ToValueType<typename T::ValueType>();
-
-    DPF_ASSERT_OK_AND_ASSIGN(dcf_,
-                             DistributedComparisonFunction::Create(parameters));
-  }
-
-  std::unique_ptr<DistributedComparisonFunction> dcf_;
-};
-
-using MyIntModN = IntModN<uint32_t, 4294967291u>;  // 2**32 - 5.
-using DcfTestTypes = ::testing::Types<
-    DcfTestParameters<uint32_t, 1>, DcfTestParameters<uint32_t, 2>,
-    DcfTestParameters<uint32_t, 5>, DcfTestParameters<absl::uint128, 5>,
-    DcfTestParameters<Tuple<uint32_t, uint32_t>, 5>,
-    DcfTestParameters<Tuple<uint32_t, absl::uint128>, 5>,
-    DcfTestParameters<Tuple<MyIntModN, MyIntModN>, 5> >;
-
-TYPED_TEST_SUITE(DcfTest, DcfTestTypes);
-
-TYPED_TEST(DcfTest, CreateWorks) {
-  EXPECT_THAT(this->dcf_, testing::Ne(nullptr));
-}
-
-TYPED_TEST(DcfTest, GenEval) {
-  using ValueType = typename TypeParam::ValueType;
-  const absl::uint128 domain_size = absl::uint128{1}
-                                    << TypeParam::kLogDomainSize;
-  ValueType beta;
-  SetTo42(beta);
-  for (absl::uint128 alpha = 0; alpha < domain_size; ++alpha) {
-    // Generate keys.
-    DcfKey key_0, key_1;
-    DPF_ASSERT_OK_AND_ASSIGN(std::tie(key_0, key_1),
-                             this->dcf_->GenerateKeys(alpha, beta));
-
-    // Evaluate on every point in the domain.
-    for (absl::uint128 x = 0; x < domain_size; ++x) {
-      DPF_ASSERT_OK_AND_ASSIGN(
-          ValueType result_0,
-          this->dcf_->template Evaluate<ValueType>(key_0, x));
-      DPF_ASSERT_OK_AND_ASSIGN(
-          ValueType result_1,
-          this->dcf_->template Evaluate<ValueType>(key_1, x));
-      if (x < alpha) {
-        EXPECT_EQ(ValueType(result_0 + result_1), beta)
-            << "x=" << x << ", alpha=" << alpha;
-      } else {
-        EXPECT_EQ(ValueType(result_0 + result_1), ValueType{})
-            << "x=" << x << ", alpha=" << alpha;
-      }
-    }
-  }
-}
-
-TEST(DcfTest, WorksCorrectlyOnUint64TWithLargeDomain) {
-  using ValueType = uint64_t;
-  const absl::uint128 domain_size = absl::uint128{1} << 64;
-  ValueType beta;
-  SetTo42(beta);
-  absl::uint128 alpha = 50;
-
-  DcfParameters parameters;
-  parameters.mutable_parameters()->set_log_domain_size(64);
-  *(parameters.mutable_parameters()->mutable_value_type()) =
-      ToValueType<uint64_t>();
-
-  DPF_ASSERT_OK_AND_ASSIGN(auto dcf,
-                           DistributedComparisonFunction::Create(parameters));
-
-  // Generate keys.
-  DcfKey key_0, key_1;
-  DPF_ASSERT_OK_AND_ASSIGN(std::tie(key_0, key_1),
-                           dcf->GenerateKeys(alpha, beta));
-
-  // Evaluate on every point in the domain smaller than alpha.
-  for (absl::uint128 x = 0; x < alpha; ++x) {
-    DPF_ASSERT_OK_AND_ASSIGN(ValueType result_0,
-                             dcf->template Evaluate<ValueType>(key_0, x));
-    DPF_ASSERT_OK_AND_ASSIGN(ValueType result_1,
-                             dcf->template Evaluate<ValueType>(key_1, x));
-    EXPECT_EQ(ValueType(result_0 + result_1), beta)
-        << "x=" << x << ", alpha=" << alpha;
-  }
-
-  // Evaluate on 100 random points in the domain.
-  absl::BitGen rng;
-  absl::uniform_int_distribution<uint64_t> dist;
-  const int kNumEvaluationPoints = 100;
-  std::vector<absl::uint128> evaluation_points(kNumEvaluationPoints);
-  for (int i = 0; i < kNumEvaluationPoints - 1; ++i) {
-    evaluation_points[i] =
-        absl::MakeUint128(dist(rng), dist(rng)) % domain_size;
-    DPF_ASSERT_OK_AND_ASSIGN(
-        uint64_t result_0,
-        dcf->template Evaluate<ValueType>(key_0, evaluation_points[i]));
-    DPF_ASSERT_OK_AND_ASSIGN(
-        ValueType result_1,
-        dcf->template Evaluate<ValueType>(key_1, evaluation_points[i]));
-    if (evaluation_points[i] < alpha) {
-      EXPECT_EQ(ValueType(result_0 + result_1), beta)
-          << "x=" << evaluation_points[i] << ", alpha=" << alpha;
-    } else {
-      EXPECT_EQ(ValueType(result_0 + result_1), ValueType{})
-          << "x=" << evaluation_points[i] << ", alpha=" << alpha;
-    }
-  }
-}
-
-}  // namespace
-
-}  // namespace distributed_point_functions
diff --git a/third_party/distributed_point_functions/code/dcf/fss_gates/BUILD b/third_party/distributed_point_functions/code/dcf/fss_gates/BUILD
deleted file mode 100644
index b62be84b..0000000
--- a/third_party/distributed_point_functions/code/dcf/fss_gates/BUILD
+++ /dev/null
@@ -1,62 +0,0 @@
-# This package contains implementation of various Function Secret Sharing (FSS)
-# gates as specified in https://eprint.iacr.org/2020/1392. The implementation
-# uses the Distributed Comparison Function (as implemented in
-# distributed_comparison_function.cc) as a central component.
-
-load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
-load("@rules_cc//cc:defs.bzl", "cc_library")
-load("@rules_proto//proto:defs.bzl", "proto_library")
-
-package(
-    default_visibility = ["//visibility:public"],
-)
-
-licenses(["notice"])
-
-# Multiple Interval Containment
-
-cc_library(
-    name = "multiple_interval_containment",
-    srcs = ["multiple_interval_containment.cc"],
-    hdrs = ["multiple_interval_containment.h"],
-    deps = [
-        ":multiple_interval_containment_cc_proto",
-        "//dcf:distributed_comparison_function",
-        "//dcf/fss_gates/prng:basic_rng",
-        "//dpf:distributed_point_function_cc_proto",
-        "//dpf:status_macros",
-        "@com_google_absl//absl/numeric:int128",
-        "@com_google_absl//absl/status",
-        "@com_google_absl//absl/status:statusor",
-    ],
-)
-
-proto_library(
-    name = "multiple_interval_containment_proto",
-    srcs = ["multiple_interval_containment.proto"],
-    deps = [
-        "//dcf:distributed_comparison_function_proto",
-        "//dpf:distributed_point_function_proto",
-    ],
-)
-
-cc_proto_library(
-    name = "multiple_interval_containment_cc_proto",
-    deps = [":multiple_interval_containment_proto"],
-)
-
-cc_test(
-    name = "multiple_interval_containment_test",
-    srcs = ["multiple_interval_containment_test.cc"],
-    deps = [
-        ":multiple_interval_containment",
-        ":multiple_interval_containment_cc_proto",
-        "//dcf/fss_gates/prng:basic_rng",
-        "//dpf:distributed_point_function_cc_proto",
-        "//dpf:status_macros",
-        "//dpf/internal:status_matchers",
-        "//dpf/internal:value_type_helpers",
-        "@com_github_google_googletest//:gtest_main",
-        "@com_google_absl//absl/numeric:int128",
-    ],
-)
diff --git a/third_party/distributed_point_functions/code/dcf/fss_gates/multiple_interval_containment.cc b/third_party/distributed_point_functions/code/dcf/fss_gates/multiple_interval_containment.cc
deleted file mode 100644
index e47b436a..0000000
--- a/third_party/distributed_point_functions/code/dcf/fss_gates/multiple_interval_containment.cc
+++ /dev/null
@@ -1,278 +0,0 @@
-// Copyright 2021 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "dcf/fss_gates/multiple_interval_containment.h"
-
-#include <utility>
-
-#include "absl/numeric/int128.h"
-#include "absl/status/status.h"
-#include "absl/status/statusor.h"
-#include "dcf/distributed_comparison_function.h"
-#include "dcf/fss_gates/multiple_interval_containment.pb.h"
-#include "dcf/fss_gates/prng/basic_rng.h"
-#include "dpf/distributed_point_function.pb.h"
-#include "dpf/status_macros.h"
-
-namespace distributed_point_functions {
-namespace fss_gates {
-
-absl::StatusOr<std::unique_ptr<MultipleIntervalContainmentGate>>
-MultipleIntervalContainmentGate::Create(const MicParameters& mic_parameters) {
-  // Declaring the parameters for a Distributed Comparison Function (DCF).
-  DcfParameters dcf_parameters;
-
-  // Return error if log_group_size is not between 0 and 127.
-  if (mic_parameters.log_group_size() < 0 ||
-      mic_parameters.log_group_size() > 127) {
-    return absl::InvalidArgumentError(
-        "log_group_size should be in > 0 and < 128");
-  }
-
-  // Setting N = 2 ^ log_group_size.
-  absl::uint128 N = absl::uint128(1) << mic_parameters.log_group_size();
-
-  for (int i = 0; i < mic_parameters.intervals_size(); i++) {
-    // Return error if some interval is empty.
-    if (!mic_parameters.intervals(i).has_lower_bound() ||
-        !mic_parameters.intervals(i).has_upper_bound()) {
-      return absl::InvalidArgumentError("Intervals should be non-empty");
-    }
-
-    absl::uint128 p = absl::MakeUint128(
-        mic_parameters.intervals(i).lower_bound().value_uint128().high(),
-        mic_parameters.intervals(i).lower_bound().value_uint128().low());
-
-    absl::uint128 q = absl::MakeUint128(
-        mic_parameters.intervals(i).upper_bound().value_uint128().high(),
-        mic_parameters.intervals(i).upper_bound().value_uint128().low());
-
-    // Return error if the intervals are not valid group elements.
-    if (p < 0 || p >= N) {
-      return absl::InvalidArgumentError(
-          "Interval bounds should be between 0 and 2^log_group_size");
-    }
-
-    // Return error if the intervals are not valid group elements.
-    if (q < 0 || q >= N) {
-      return absl::InvalidArgumentError(
-          "Interval bounds should be between 0 and 2^log_group_size");
-    }
-
-    // Return error if lower bound of an interval is less that its
-    // upper bound.
-    if (p > q) {
-      return absl::InvalidArgumentError(
-          "Interval upper bounds should be >= lower bound");
-    }
-  }
-
-  // Setting the `log_domain_size` of the DCF to be same as the
-  // `log_group_size` of the Multiple Interval Containment Gate.
-  dcf_parameters.mutable_parameters()->set_log_domain_size(
-      mic_parameters.log_group_size());
-
-  // Setting the output ValueType of the DCF so that it can store 128 bit
-  // integers.
-  *(dcf_parameters.mutable_parameters()->mutable_value_type()) =
-      ToValueType<absl::uint128>();
-
-  // Creating a DCF with appropriate parameters.
-  DPF_ASSIGN_OR_RETURN(std::unique_ptr<DistributedComparisonFunction> dcf,
-                       DistributedComparisonFunction::Create(dcf_parameters));
-
-  return absl::WrapUnique(
-      new MultipleIntervalContainmentGate(mic_parameters, std::move(dcf)));
-}
-
-MultipleIntervalContainmentGate::MultipleIntervalContainmentGate(
-    MicParameters mic_parameters,
-    std::unique_ptr<DistributedComparisonFunction> dcf)
-    : mic_parameters_(std::move(mic_parameters)), dcf_(std::move(dcf)) {}
-
-absl::StatusOr<std::pair<MicKey, MicKey>> MultipleIntervalContainmentGate::Gen(
-    absl::uint128 r_in, std::vector<absl::uint128> r_out) {
-  if (r_out.size() != mic_parameters_.intervals_size()) {
-    return absl::InvalidArgumentError(
-        "Count of output masks should be equal to the number of intervals");
-  }
-
-  // Setting N = 2 ^ log_group_size.
-  absl::uint128 N = absl::uint128(1) << mic_parameters_.log_group_size();
-
-  // Checking whether r_in is a group element.
-  if (r_in < 0 || r_in >= N) {
-    return absl::InvalidArgumentError(
-        "Input mask should be between 0 and 2^log_group_size");
-  }
-
-  // Checking whether each element of r_out is a group element.
-  for (int i = 0; i < r_out.size(); i++) {
-    if (r_out[i] < 0 || r_out[i] >= N) {
-      return absl::InvalidArgumentError(
-          "Output mask should be between 0 and 2^log_group_size");
-    }
-  }
-
-  // The following code is commented using their Line numbering in
-  // https://eprint.iacr.org/2020/1392 Fig. 14 Gen procedure.
-
-  // Line 1
-  absl::uint128 gamma = (N - 1 + r_in) % N;
-
-  // Line 2
-
-  DcfKey key_0, key_1;
-
-  absl::uint128 alpha = gamma;
-  absl::uint128 beta = 1;
-
-  DPF_ASSIGN_OR_RETURN(std::tie(key_0, key_1),
-                       this->dcf_->GenerateKeys(alpha, beta));
-
-  MicKey k0, k1;
-
-  // Part of Line 7
-  *(k0.mutable_dcfkey()) = key_0;
-  *(k1.mutable_dcfkey()) = key_1;
-
-  // Line 3
-  for (int i = 0; i < mic_parameters_.intervals_size(); i++) {
-    absl::uint128 p = absl::MakeUint128(
-        mic_parameters_.intervals(i).lower_bound().value_uint128().high(),
-        mic_parameters_.intervals(i).lower_bound().value_uint128().low());
-
-    absl::uint128 q = absl::MakeUint128(
-        mic_parameters_.intervals(i).upper_bound().value_uint128().high(),
-        mic_parameters_.intervals(i).upper_bound().value_uint128().low());
-
-    // Line 4
-    absl::uint128 q_prime = (q + 1) % N;
-
-    absl::uint128 alpha_p = (p + r_in) % N;
-
-    absl::uint128 alpha_q = (q + r_in) % N;
-
-    absl::uint128 alpha_q_prime = (q + 1 + r_in) % N;
-
-    // Line 5 - This computes the correction term for the output of the gate,
-    // and the logic and proof of its correctness is described in
-    // https://eprint.iacr.org/2020/1392 Lemma 1, Lemma 2 and Theorem 3.
-    absl::uint128 z =
-        r_out[i] + (alpha_p > alpha_q ? 1 : 0) + (alpha_p > p ? -1 : 0) +
-        (alpha_q_prime > q_prime ? 1 : 0) + (alpha_q == (N - 1) ? 1 : 0);
-    z = z % N;
-
-    const absl::string_view kSampleSeed = absl::string_view();
-    DPF_ASSIGN_OR_RETURN(
-        auto rng, distributed_point_functions::BasicRng::Create(kSampleSeed));
-    DPF_ASSIGN_OR_RETURN(absl::uint128 z_0, rng->Rand128());
-
-    z_0 = z_0 % N;
-
-    absl::uint128 z_1 = (z - z_0) % N;
-
-    // Part of Line 7
-    Value_Integer* k0_output_mask_share = k0.add_output_mask_share();
-
-    k0_output_mask_share->mutable_value_uint128()->set_high(
-        absl::Uint128High64(z_0));
-    k0_output_mask_share->mutable_value_uint128()->set_low(
-        absl::Uint128Low64(z_0));
-
-    Value_Integer* k1_output_mask_share = k1.add_output_mask_share();
-
-    k1_output_mask_share->mutable_value_uint128()->set_high(
-        absl::Uint128High64(z_1));
-    k1_output_mask_share->mutable_value_uint128()->set_low(
-        absl::Uint128Low64(z_1));
-  }
-
-  // Line 8
-  return std::pair<MicKey, MicKey>(k0, k1);
-}
-
-absl::StatusOr<std::vector<absl::uint128>>
-MultipleIntervalContainmentGate::Eval(MicKey k, absl::uint128 x) {
-  // Setting N = 2 ^ log_group_size
-  absl::uint128 N = absl::uint128(1) << mic_parameters_.log_group_size();
-
-  // Checking whether x is a group element
-  if (x < 0 || x >= N) {
-    return absl::InvalidArgumentError(
-        "Masked input should be between 0 and 2^log_group_size");
-  }
-
-  std::vector<absl::uint128> res;
-
-  // The following code is commented using their Line numbering in
-  // https://eprint.iacr.org/2020/1392 Fig. 14 Eval procedure.
-
-  // Line 2
-  for (int i = 0; i < mic_parameters_.intervals_size(); i++) {
-    absl::uint128 p = absl::MakeUint128(
-        mic_parameters_.intervals(i).lower_bound().value_uint128().high(),
-        mic_parameters_.intervals(i).lower_bound().value_uint128().low());
-
-    absl::uint128 q = absl::MakeUint128(
-        mic_parameters_.intervals(i).upper_bound().value_uint128().high(),
-        mic_parameters_.intervals(i).upper_bound().value_uint128().low());
-
-    // Line 3
-
-    absl::uint128 q_prime = (q + 1) % N;
-
-    // Line 4
-    absl::uint128 x_p = (x + N - 1 - p) % N;
-
-    absl::uint128 x_q_prime = (x + N - 1 - q_prime) % N;
-
-    // Line 5
-
-    absl::uint128 s_p;
-
-    DPF_ASSIGN_OR_RETURN(s_p, dcf_->Evaluate<absl::uint128>(k.dcfkey(), x_p));
-
-    s_p = s_p % N;
-
-    // Line 6
-
-    absl::uint128 s_q_prime;
-
-    DPF_ASSIGN_OR_RETURN(s_q_prime,
-                         dcf_->Evaluate<absl::uint128>(k.dcfkey(), x_q_prime));
-
-    s_q_prime = s_q_prime % N;
-
-    // Line 7
-
-    absl::uint128 y;
-
-    absl::uint128 z =
-        absl::MakeUint128(k.output_mask_share(i).value_uint128().high(),
-                          k.output_mask_share(i).value_uint128().low());
-
-    y = (k.dcfkey().key().party() ? ((x > p ? 1 : 0) - (x > q_prime ? 1 : 0))
-                                  : 0) -
-        s_p + s_q_prime + z;
-
-    res.push_back(y % N);
-  }
-
-  // Line 9
-  return res;
-}
-
-}  // namespace fss_gates
-}  // namespace distributed_point_functions
diff --git a/third_party/distributed_point_functions/code/dcf/fss_gates/multiple_interval_containment.h b/third_party/distributed_point_functions/code/dcf/fss_gates/multiple_interval_containment.h
deleted file mode 100644
index cb8c203..0000000
--- a/third_party/distributed_point_functions/code/dcf/fss_gates/multiple_interval_containment.h
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright 2021 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef DISTRIBUTED_POINT_FUNCTIONS_DCF_FSS_GATES_MULTIPLE_INTERVAL_CONTAINMENT_H_
-#define DISTRIBUTED_POINT_FUNCTIONS_DCF_FSS_GATES_MULTIPLE_INTERVAL_CONTAINMENT_H_
-#include <vector>
-
-#include "absl/numeric/int128.h"
-#include "absl/status/statusor.h"
-#include "dcf/distributed_comparison_function.h"
-#include "dcf/fss_gates/multiple_interval_containment.pb.h"
-#include "dpf/status_macros.h"
-
-namespace distributed_point_functions {
-namespace fss_gates {
-
-// Implements the Multiple Interval Containment gate as specified in
-// https://eprint.iacr.org/2020/1392 (Fig. 14). Such a gate is specified by
-// input and output group Z_{2 ^ n} where n is the bit length and a sequence
-// of `m` public intervals {p_i, q_i}_{i \in [m]}. The Key generation procedure
-// produces two keys, k_0 and k_1, corresponding to Party 0 and Party 1
-// respectively. Evaluating each key on any point `x` in the input group results
-// in an additive secret share of m values {y_i}_{i \in [m]} where y_i = `1`, if
-// `p_i <= x <= q_i`, and 0 otherwise.
-
-class MultipleIntervalContainmentGate {
- public:
-  // Factory method : creates and returns a MultipleIntervalContainmentGate
-  // initialized with appropriate parameters.
-  static absl::StatusOr<std::unique_ptr<MultipleIntervalContainmentGate>>
-  Create(const MicParameters& mic_parameters);
-
-  // MultipleIntervalContainmentGate is neither copyable nor movable.
-  MultipleIntervalContainmentGate(const MultipleIntervalContainmentGate&) =
-      delete;
-  MultipleIntervalContainmentGate& operator=(
-      const MultipleIntervalContainmentGate&) = delete;
-
-  // This method generates Multiple Interval Containment Gate a pair of keys
-  // using `r_in` and `r_out` as the input mask and output masks respectively.
-  // The implementation of this method is identical to Gen procedure specified
-  // in https://eprint.iacr.org/2020/1392 (Fig. 14). Note that although the
-  // datatype of r_in and r_out is absl::uint128, but they will be interpreted
-  // as an element in the input and output group Z_{2 ^ n} respectively. This
-  // reinterpretion in the group is achieved simply by taking mod of r_in and
-  // r_out with the size of group i.e. 2^{log_group_size}.
-
-  // This method expects that the size of r_out vector be exactly same as the
-  // number of public intervals in the MicParameters. This is because for
-  // each interval in MIC, we will need an independent output mask to hide
-  // the actual cleartext output of the MIC on that interval. This method
-  // will return InvalidArgumentError.
-
-  // Returns INVALID_ARGUMENT if the size of r_out vector does not match the
-  // number of intervals specified during construction.
-  absl::StatusOr<std::pair<MicKey, MicKey>> Gen(
-      absl::uint128 r_in, std::vector<absl::uint128> r_out);
-
-  // This method evaluates the Multiple Interval Containment Gate key k
-  // on input domain point `x`. The output is returned as a 128 bit string
-  // and needs to be interpreted as an element in the output group Z_{2 ^ n}.
-  absl::StatusOr<std::vector<absl::uint128>> Eval(MicKey k, absl::uint128 x);
-
- private:
-  // Parameters needed for specifying a Multiple Interval Containment Gate.
-  const MicParameters mic_parameters_;
-
-  // Private constructor, called by `Create`
-  MultipleIntervalContainmentGate(
-      MicParameters mic_parameters,
-      std::unique_ptr<DistributedComparisonFunction> dcf);
-
-  // Pointer to a Distributed Comparison Function which will be internally
-  // invoked by Gen and Eval.
-  std::unique_ptr<DistributedComparisonFunction> dcf_;
-};
-
-}  // namespace fss_gates
-}  // namespace distributed_point_functions
-
-#endif  // DISTRIBUTED_POINT_FUNCTIONS_DCF_FSS_GATES_MULTIPLE_INTERVAL_CONTAINMENT_H_
diff --git a/third_party/distributed_point_functions/code/dcf/fss_gates/multiple_interval_containment.proto b/third_party/distributed_point_functions/code/dcf/fss_gates/multiple_interval_containment.proto
deleted file mode 100644
index 54551fd..0000000
--- a/third_party/distributed_point_functions/code/dcf/fss_gates/multiple_interval_containment.proto
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2021 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-syntax = "proto3";
-
-package distributed_point_functions.fss_gates;
-
-import "dcf/distributed_comparison_function.proto";
-import "dpf/distributed_point_function.proto";
-
-// Represents an interval on the group G = Z_N.
-message Interval {
-  // Represents the lower limit of the interval. This corresponds to `p_i`
-  // used in https://eprint.iacr.org/2020/1392 (Fig. 14).
-  Value.Integer lower_bound = 1;
-
-  // Represents the upper limit of the interval. This corresponds to `q_i`
-  // used in https://eprint.iacr.org/2020/1392 (Fig. 14).
-  Value.Integer upper_bound = 2;
-}
-
-message MicParameters {
-  // Represents the bit length of the input to the Multiple Interval Containment
-  // gate. This corresponds to `n` used in https://eprint.iacr.org/2020/1392
-  // (Fig. 14). Here we assume that if `n` is the input bit-length, then the
-  // input and output group of the gate is implicitly Z_N where N = 2^n, and
-  // hence the variable name "log_group_size". Maximum allowed log_group_size
-  // is 127 and minimum value should be at least the number of bits required to
-  // store each interval boundary.
-  int32 log_group_size = 1;
-
-  // Represents a sequence of public intervals. This corresponds to `{p_i, q_i}`
-  // used in https://eprint.iacr.org/2020/1392 (Fig. 14).
-  repeated Interval intervals = 2;
-}
-
-// Represents a key for Multiple Interval Containment gate. This corresponds to
-//`k_b` used in https://eprint.iacr.org/2020/1392 (Fig. 14). The key implicitly
-// corresponds to the MicParameters used to generate this key.
-message MicKey {
-  // Represents a Distributed Comparison Function Key. This corresponds to
-  //`k_b^(N - 1)` used in https://eprint.iacr.org/2020/1392 (Fig. 14).
-  DcfKey dcfkey = 1;
-
-  // Represents output mask shares corresponding to each of the m different
-  // intervals. This corresponds to `{z_i,b}_i` used in
-  // https://eprint.iacr.org/2020/1392 (Fig. 14).
-  repeated Value.Integer output_mask_share = 2;
-}
diff --git a/third_party/distributed_point_functions/code/dcf/fss_gates/multiple_interval_containment_test.cc b/third_party/distributed_point_functions/code/dcf/fss_gates/multiple_interval_containment_test.cc
deleted file mode 100644
index b24904a..0000000
--- a/third_party/distributed_point_functions/code/dcf/fss_gates/multiple_interval_containment_test.cc
+++ /dev/null
@@ -1,543 +0,0 @@
-// Copyright 2021 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "dcf/fss_gates/multiple_interval_containment.h"
-
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-
-#include <cstdint>
-#include <vector>
-
-#include "absl/numeric/int128.h"
-#include "dcf/fss_gates/multiple_interval_containment.pb.h"
-#include "dcf/fss_gates/prng/basic_rng.h"
-#include "dpf/distributed_point_function.pb.h"
-#include "dpf/internal/status_matchers.h"
-#include "dpf/internal/value_type_helpers.h"
-#include "dpf/status_macros.h"
-
-namespace distributed_point_functions {
-namespace fss_gates {
-namespace {
-
-using ::testing::Test;
-
-TEST(MICTest, GenAndEvalSucceedsForSmallGroup) {
-  MicParameters mic_parameters;
-  const int group_size = 64;
-  const uint64_t interval_count = 5;
-
-  // Setting input and output group to be Z_{2^64}
-  mic_parameters.set_log_group_size(group_size);
-
-  // Setting up the lower bound and upper bounds for intervals
-  std::vector<absl::uint128> ps{10, 23, 45, 66, 15};
-  std::vector<absl::uint128> qs{45, 30, 100, 250, 15};
-
-  for (int i = 0; i < interval_count; ++i) {
-    Interval* interval = mic_parameters.add_intervals();
-
-    interval->mutable_lower_bound()->mutable_value_uint128()->set_low(
-        absl::Uint128Low64(ps[i]));
-
-    interval->mutable_upper_bound()->mutable_value_uint128()->set_low(
-        absl::Uint128Low64(qs[i]));
-  }
-
-  // Creating a MIC gate
-  DPF_ASSERT_OK_AND_ASSIGN(
-      std::unique_ptr<MultipleIntervalContainmentGate> MicGate,
-      MultipleIntervalContainmentGate::Create(mic_parameters));
-
-  MicKey key_0, key_1;
-
-  // Initializing the input and output masks uniformly at random;
-  const absl::string_view kSampleSeed = absl::string_view();
-  DPF_ASSERT_OK_AND_ASSIGN(
-      auto rng, distributed_point_functions::BasicRng::Create(kSampleSeed));
-
-  absl::uint128 N = absl::uint128(1) << mic_parameters.log_group_size();
-
-  DPF_ASSERT_OK_AND_ASSIGN(absl::uint128 r_in, rng->Rand64());
-  r_in = r_in % N;
-
-  std::vector<absl::uint128> r_outs;
-
-  for (int i = 0; i < interval_count; ++i) {
-    DPF_ASSERT_OK_AND_ASSIGN(absl::uint128 r_out, rng->Rand64());
-    r_out = r_out % N;
-    r_outs.push_back(r_out);
-  }
-
-  // Generating MIC gate keys
-  DPF_ASSERT_OK_AND_ASSIGN(std::tie(key_0, key_1), MicGate->Gen(r_in, r_outs));
-
-  // Inside this loop we will test the Evaluation of the MIC gate on
-  // input values ranging between [0, 400)
-  for (uint64_t i = 0; i < 400; i++) {
-    std::vector<absl::uint128> res_0, res_1;
-
-    // Evaluating MIC gate key_0 on masked input i + r_in
-    DPF_ASSERT_OK_AND_ASSIGN(res_0, MicGate->Eval(key_0, (i + r_in) % N));
-
-    // Evaluating MIC gate key_1 on masked input i + r_in
-    DPF_ASSERT_OK_AND_ASSIGN(res_1, MicGate->Eval(key_1, (i + r_in) % N));
-
-    // Reconstructing the actual output of the MIC gate by adding together
-    // the secret shared output res_0 and res_1, and then subtracting out
-    // the output mask r_out
-    for (int j = 0; j < interval_count; j++) {
-      absl::uint128 result = (res_0[j] + res_1[j] - r_outs[j]) % N;
-
-      // If the input i lies inside the j^th interval, then the expected
-      // output of MIC gate is 1, and 0 otherwise
-      if (i >= ps[j] && i <= qs[j]) {
-        EXPECT_EQ(result, 1);
-      } else {
-        EXPECT_EQ(result, 0);
-      }
-    }
-  }
-}
-
-TEST(MICTest, GenAndEvalSucceedsForLargeGroup) {
-  MicParameters mic_parameters;
-  const int group_size = 127;
-  const uint64_t interval_count = 3;
-  const absl::uint128 two_power_127 = absl::uint128(1) << 127;
-  const absl::uint128 two_power_126 = absl::uint128(1) << 126;
-
-  // Setting input and output group to be Z_{2^127}
-  mic_parameters.set_log_group_size(group_size);
-
-  // Setting up the lower bound and upper bounds for intervals
-
-  std::vector<absl::uint128> ps{two_power_126, two_power_127 - 1,
-                                two_power_127 - 3};
-  std::vector<absl::uint128> qs{two_power_126 + 3, two_power_127 - 1,
-                                two_power_127 - 2};
-
-  std::vector<absl::uint128> x{two_power_126 - 1, two_power_126,
-                               two_power_126 + 1, two_power_126 + 2,
-                               two_power_126 + 3, two_power_126 + 4,
-                               two_power_127 - 4, two_power_127 - 3,
-                               two_power_127 - 2, two_power_127 - 1};
-
-  for (int i = 0; i < interval_count; ++i) {
-    Interval* interval = mic_parameters.add_intervals();
-
-    interval->mutable_lower_bound()->mutable_value_uint128()->set_high(
-        absl::Uint128High64(ps[i]));
-    interval->mutable_lower_bound()->mutable_value_uint128()->set_low(
-        absl::Uint128Low64(ps[i]));
-
-    interval->mutable_upper_bound()->mutable_value_uint128()->set_high(
-        absl::Uint128High64(qs[i]));
-    interval->mutable_upper_bound()->mutable_value_uint128()->set_low(
-        absl::Uint128Low64(qs[i]));
-  }
-
-  // Creating a MIC gate
-  DPF_ASSERT_OK_AND_ASSIGN(
-      std::unique_ptr<MultipleIntervalContainmentGate> MicGate,
-      MultipleIntervalContainmentGate::Create(mic_parameters));
-
-  MicKey key_0, key_1;
-
-  absl::uint128 N = absl::uint128(1) << mic_parameters.log_group_size();
-
-  // Initializing the input and output masks uniformly at random;
-  const absl::string_view kSampleSeed = absl::string_view();
-  DPF_ASSERT_OK_AND_ASSIGN(
-      auto rng, distributed_point_functions::BasicRng::Create(kSampleSeed));
-
-  DPF_ASSERT_OK_AND_ASSIGN(absl::uint128 r_in, rng->Rand128());
-
-  r_in = r_in % N;
-
-  std::vector<absl::uint128> r_outs;
-
-  for (int i = 0; i < interval_count; ++i) {
-    DPF_ASSERT_OK_AND_ASSIGN(absl::uint128 r_out, rng->Rand128());
-    r_out = r_out % N;
-    r_outs.push_back(r_out);
-  }
-
-  // Generating MIC gate keys
-  DPF_ASSERT_OK_AND_ASSIGN(std::tie(key_0, key_1), MicGate->Gen(r_in, r_outs));
-
-  // Inside this loop we will test the Evaluation of the MIC gate on
-  // input values in the vicinity of interval boundaries which are hardcoded
-  // in the vector x.
-  for (uint64_t i = 0; i < x.size(); i++) {
-    std::vector<absl::uint128> res_0, res_1;
-
-    // Evaluating MIC gate key_0 on masked input
-    DPF_ASSERT_OK_AND_ASSIGN(res_0, MicGate->Eval(key_0, (x[i] + r_in) % N));
-
-    // Evaluating MIC gate key_1 on masked input
-    DPF_ASSERT_OK_AND_ASSIGN(res_1, MicGate->Eval(key_1, (x[i] + r_in) % N));
-
-    // Reconstructing the actual output of the MIC gate by adding together
-    // the secret shared output res_0 and res_1, and then subtracting out
-    // the output mask r_out
-    for (int j = 0; j < interval_count; j++) {
-      absl::uint128 result = (res_0[j] + res_1[j] - r_outs[j]) % N;
-
-      // If the input lies inside the j^th interval, then the expected
-      // output of MIC gate is 1, and 0 otherwise
-      if (x[i] >= ps[j] && x[i] <= qs[j]) {
-        EXPECT_EQ(result, 1);
-      } else {
-        EXPECT_EQ(result, 0);
-      }
-    }
-  }
-}
-
-TEST(MICTest, CreateFailsWith128bitGroup) {
-  MicParameters mic_parameters;
-  const int group_size = 128;
-  const uint64_t interval_count = 5;
-
-  // Setting input and output group to be Z_{2^128}
-  mic_parameters.set_log_group_size(group_size);
-
-  // Setting up the lower bound and upper bounds for intervals
-  std::vector<absl::uint128> ps{10, 23, 45, 66, 15};
-  std::vector<absl::uint128> qs{45, 30, 100, 250, 15};
-
-  for (int i = 0; i < interval_count; ++i) {
-    Interval* interval = mic_parameters.add_intervals();
-
-    interval->mutable_lower_bound()->mutable_value_uint128()->set_low(
-        absl::Uint128Low64(ps[i]));
-
-    interval->mutable_upper_bound()->mutable_value_uint128()->set_low(
-        absl::Uint128Low64(qs[i]));
-  }
-
-  EXPECT_THAT(
-      MultipleIntervalContainmentGate::Create(mic_parameters),
-      dpf_internal::StatusIs(absl::StatusCode::kInvalidArgument,
-                             "log_group_size should be in > 0 and < 128"));
-}
-
-TEST(MICTest, CreateFailsForIntervalBoundariesOutsideGroup) {
-  MicParameters mic_parameters;
-  const int group_size = 20;
-  const uint64_t interval_count = 1;
-  const absl::uint128 two_power_20 = absl::uint128(1) << 20;
-
-  // Setting input and output group to be Z_{2^20}
-  mic_parameters.set_log_group_size(group_size);
-
-  // Setting up the lower bound and upper bounds for intervals
-  std::vector<absl::uint128> ps{4};
-  std::vector<absl::uint128> qs{two_power_20};
-
-  for (int i = 0; i < interval_count; ++i) {
-    Interval* interval = mic_parameters.add_intervals();
-
-    interval->mutable_lower_bound()->mutable_value_uint128()->set_low(
-        absl::Uint128Low64(ps[i]));
-
-    interval->mutable_upper_bound()->mutable_value_uint128()->set_low(
-        absl::Uint128Low64(qs[i]));
-  }
-
-  EXPECT_THAT(MultipleIntervalContainmentGate::Create(mic_parameters),
-              dpf_internal::StatusIs(
-                  absl::StatusCode::kInvalidArgument,
-                  "Interval bounds should be between 0 and 2^log_group_size"));
-}
-
-TEST(MICTest, CreateFailsForInvalidIntervalBoundaries) {
-  MicParameters mic_parameters;
-  const int group_size = 20;
-  const uint64_t interval_count = 1;
-
-  // Setting input and output group to be Z_{2^20}
-  mic_parameters.set_log_group_size(group_size);
-
-  // Setting up the lower bound and upper bounds for intervals
-  std::vector<absl::uint128> ps{4};
-  std::vector<absl::uint128> qs{3};
-
-  for (int i = 0; i < interval_count; ++i) {
-    Interval* interval = mic_parameters.add_intervals();
-
-    interval->mutable_lower_bound()->mutable_value_uint128()->set_low(
-        absl::Uint128Low64(ps[i]));
-
-    interval->mutable_upper_bound()->mutable_value_uint128()->set_low(
-        absl::Uint128Low64(qs[i]));
-  }
-
-  EXPECT_THAT(
-      MultipleIntervalContainmentGate::Create(mic_parameters),
-      dpf_internal::StatusIs(absl::StatusCode::kInvalidArgument,
-                             "Interval upper bounds should be >= lower bound"));
-}
-
-TEST(MICTest, CreateFailsForEmptyInterval) {
-  MicParameters mic_parameters;
-  const int group_size = 20;
-  const uint64_t interval_count = 1;
-
-  // Setting input and output group to be Z_{2^20}
-  mic_parameters.set_log_group_size(group_size);
-
-  // Setting up the lower bound and upper bounds for intervals
-  std::vector<absl::uint128> ps{};
-  std::vector<absl::uint128> qs{3};
-
-  for (int i = 0; i < interval_count; ++i) {
-    Interval* interval = mic_parameters.add_intervals();
-
-    // Only setting upper bound (and skipping lower bound)
-    interval->mutable_upper_bound()->mutable_value_uint128()->set_low(
-        absl::Uint128Low64(qs[i]));
-  }
-
-  EXPECT_THAT(MultipleIntervalContainmentGate::Create(mic_parameters),
-              dpf_internal::StatusIs(absl::StatusCode::kInvalidArgument,
-                                     "Intervals should be non-empty"));
-}
-
-TEST(MICTest, GenFailsForIncorrectNumberOfOutputMasks) {
-  MicParameters mic_parameters;
-  const int group_size = 64;
-  const uint64_t interval_count = 5;
-
-  // Setting input and output group to be Z_{2^64}
-  mic_parameters.set_log_group_size(group_size);
-
-  // Setting up the lower bound and upper bounds for intervals
-  std::vector<absl::uint128> ps{10, 23, 45, 66, 15};
-  std::vector<absl::uint128> qs{45, 30, 100, 250, 15};
-
-  for (int i = 0; i < interval_count; ++i) {
-    Interval* interval = mic_parameters.add_intervals();
-
-    interval->mutable_lower_bound()->mutable_value_uint128()->set_low(
-        absl::Uint128Low64(ps[i]));
-
-    interval->mutable_upper_bound()->mutable_value_uint128()->set_low(
-        absl::Uint128Low64(qs[i]));
-  }
-
-  // Creating a MIC gate
-  DPF_ASSERT_OK_AND_ASSIGN(
-      std::unique_ptr<MultipleIntervalContainmentGate> MicGate,
-      MultipleIntervalContainmentGate::Create(mic_parameters));
-
-  MicKey key_0, key_1;
-
-  absl::uint128 N = absl::uint128(1) << mic_parameters.log_group_size();
-
-  // Initializing the input and output masks uniformly at random;
-  const absl::string_view kSampleSeed = absl::string_view();
-  DPF_ASSERT_OK_AND_ASSIGN(
-      auto rng, distributed_point_functions::BasicRng::Create(kSampleSeed));
-
-  DPF_ASSERT_OK_AND_ASSIGN(absl::uint128 r_in, rng->Rand64());
-  r_in = r_in % N;
-
-  std::vector<absl::uint128> r_outs;
-
-  // Setting only (interval_count - 1) many output masks
-  for (int i = 0; i < interval_count - 1; ++i) {
-    DPF_ASSERT_OK_AND_ASSIGN(absl::uint128 r_out, rng->Rand64());
-    r_out = r_out % N;
-    r_outs.push_back(r_out);
-  }
-
-  // Generating MIC gate keys
-  EXPECT_THAT(
-      MicGate->Gen(r_in, r_outs),
-      dpf_internal::StatusIs(
-          absl::StatusCode::kInvalidArgument,
-          "Count of output masks should be equal to the number of intervals"));
-}
-
-TEST(MICTest, GenFailsForInputMaskOutsideGroup) {
-  MicParameters mic_parameters;
-  const int group_size = 10;
-  const uint64_t interval_count = 1;
-
-  // Setting input and output group to be Z_{2^10}
-  mic_parameters.set_log_group_size(group_size);
-
-  // Setting up the lower bound and upper bounds for intervals
-  std::vector<absl::uint128> ps{10};
-  std::vector<absl::uint128> qs{45};
-
-  for (int i = 0; i < interval_count; ++i) {
-    Interval* interval = mic_parameters.add_intervals();
-
-    interval->mutable_lower_bound()->mutable_value_uint128()->set_low(
-        absl::Uint128Low64(ps[i]));
-
-    interval->mutable_upper_bound()->mutable_value_uint128()->set_low(
-        absl::Uint128Low64(qs[i]));
-  }
-
-  // Creating a MIC gate
-  DPF_ASSERT_OK_AND_ASSIGN(
-      std::unique_ptr<MultipleIntervalContainmentGate> MicGate,
-      MultipleIntervalContainmentGate::Create(mic_parameters));
-
-  MicKey key_0, key_1;
-
-  absl::uint128 N = absl::uint128(1) << mic_parameters.log_group_size();
-
-  const absl::string_view kSampleSeed = absl::string_view();
-  DPF_ASSERT_OK_AND_ASSIGN(
-      auto rng, distributed_point_functions::BasicRng::Create(kSampleSeed));
-
-  // Fixing r_in to be an element outside group
-  absl::uint128 r_in = 2048;
-
-  std::vector<absl::uint128> r_outs;
-
-  // Initializing the output masks uniformly at random;
-  for (int i = 0; i < interval_count; ++i) {
-    DPF_ASSERT_OK_AND_ASSIGN(absl::uint128 r_out, rng->Rand64());
-    r_out = r_out % N;
-    r_outs.push_back(r_out);
-  }
-
-  // Generating MIC gate keys
-  EXPECT_THAT(MicGate->Gen(r_in, r_outs),
-              dpf_internal::StatusIs(
-                  absl::StatusCode::kInvalidArgument,
-                  "Input mask should be between 0 and 2^log_group_size"));
-}
-
-TEST(MICTest, GenFailsForOutputMaskOutsideGroup) {
-  MicParameters mic_parameters;
-  const int group_size = 10;
-  const uint64_t interval_count = 1;
-
-  // Setting input and output group to be Z_{2^10}
-  mic_parameters.set_log_group_size(group_size);
-
-  // Setting up the lower bound and upper bounds for intervals
-  std::vector<absl::uint128> ps{10};
-  std::vector<absl::uint128> qs{45};
-
-  for (int i = 0; i < interval_count; ++i) {
-    Interval* interval = mic_parameters.add_intervals();
-
-    interval->mutable_lower_bound()->mutable_value_uint128()->set_low(
-        absl::Uint128Low64(ps[i]));
-
-    interval->mutable_upper_bound()->mutable_value_uint128()->set_low(
-        absl::Uint128Low64(qs[i]));
-  }
-
-  // Creating a MIC gate
-  DPF_ASSERT_OK_AND_ASSIGN(
-      std::unique_ptr<MultipleIntervalContainmentGate> MicGate,
-      MultipleIntervalContainmentGate::Create(mic_parameters));
-
-  MicKey key_0, key_1;
-
-  absl::uint128 N = absl::uint128(1) << mic_parameters.log_group_size();
-
-  const absl::string_view kSampleSeed = absl::string_view();
-  DPF_ASSERT_OK_AND_ASSIGN(
-      auto rng, distributed_point_functions::BasicRng::Create(kSampleSeed));
-
-  // Initializing the input masks uniformly at random;
-  DPF_ASSERT_OK_AND_ASSIGN(absl::uint128 r_in, rng->Rand64());
-  r_in = r_in % N;
-
-  std::vector<absl::uint128> r_outs;
-
-  // Fixing the output masks to be elements outside group;
-  for (int i = 0; i < interval_count; ++i) {
-    absl::uint128 r_out = 2048;
-    r_outs.push_back(r_out);
-  }
-
-  // Generating MIC gate keys
-  EXPECT_THAT(MicGate->Gen(r_in, r_outs),
-              dpf_internal::StatusIs(
-                  absl::StatusCode::kInvalidArgument,
-                  "Output mask should be between 0 and 2^log_group_size"));
-}
-
-TEST(MICTest, EvalFailsForMaskedInputOutsideGroup) {
-  MicParameters mic_parameters;
-  const int group_size = 64;
-  const uint64_t interval_count = 1;
-
-  // Setting input and output group to be Z_{2^64}
-  mic_parameters.set_log_group_size(group_size);
-
-  // Setting up the lower bound and upper bounds for intervals
-  std::vector<absl::uint128> ps{10};
-  std::vector<absl::uint128> qs{45};
-
-  for (int i = 0; i < interval_count; ++i) {
-    Interval* interval = mic_parameters.add_intervals();
-
-    interval->mutable_lower_bound()->mutable_value_uint128()->set_low(
-        absl::Uint128Low64(ps[i]));
-
-    interval->mutable_upper_bound()->mutable_value_uint128()->set_low(
-        absl::Uint128Low64(qs[i]));
-  }
-
-  // Creating a MIC gate
-  DPF_ASSERT_OK_AND_ASSIGN(
-      std::unique_ptr<MultipleIntervalContainmentGate> MicGate,
-      MultipleIntervalContainmentGate::Create(mic_parameters));
-
-  MicKey key_0, key_1;
-
-  // Initializing the input and output masks uniformly at random;
-  const absl::string_view kSampleSeed = absl::string_view();
-  DPF_ASSERT_OK_AND_ASSIGN(
-      auto rng, distributed_point_functions::BasicRng::Create(kSampleSeed));
-
-  absl::uint128 N = absl::uint128(1) << mic_parameters.log_group_size();
-
-  DPF_ASSERT_OK_AND_ASSIGN(absl::uint128 r_in, rng->Rand64());
-  r_in = r_in % N;
-
-  std::vector<absl::uint128> r_outs;
-
-  for (int i = 0; i < interval_count; ++i) {
-    DPF_ASSERT_OK_AND_ASSIGN(absl::uint128 r_out, rng->Rand64());
-    r_out = r_out % N;
-    r_outs.push_back(r_out);
-  }
-
-  // Generating MIC gate keys
-  DPF_ASSERT_OK_AND_ASSIGN(std::tie(key_0, key_1), MicGate->Gen(r_in, r_outs));
-
-  // Calling Eval on a masked input which is not a group element
-  EXPECT_THAT(MicGate->Eval(key_0, absl::uint128(1) << 72),
-              dpf_internal::StatusIs(
-                  absl::StatusCode::kInvalidArgument,
-                  "Masked input should be between 0 and 2^log_group_size"));
-}
-
-}  // namespace
-}  // namespace fss_gates
-}  // namespace distributed_point_functions
diff --git a/third_party/distributed_point_functions/code/dcf/fss_gates/prng/BUILD b/third_party/distributed_point_functions/code/dcf/fss_gates/prng/BUILD
deleted file mode 100644
index 160fa9a9..0000000
--- a/third_party/distributed_point_functions/code/dcf/fss_gates/prng/BUILD
+++ /dev/null
@@ -1,42 +0,0 @@
-load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
-
-package(
-    default_visibility = ["//:__subpackages__"],
-)
-
-licenses(["notice"])
-
-cc_library(
-    name = "prng",
-    hdrs = ["prng.h"],
-    deps = [
-        "//dpf:status_macros",
-        "@com_google_absl//absl/numeric:int128",
-        "@com_google_absl//absl/status:statusor",
-        "@com_google_absl//absl/strings",
-    ],
-)
-
-cc_library(
-    name = "basic_rng",
-    hdrs = ["basic_rng.h"],
-    deps = [
-        ":prng",
-        "@boringssl//:crypto",
-        "@com_google_absl//absl/base",
-        "@com_google_absl//absl/memory",
-        "@com_google_absl//absl/numeric:int128",
-        "@com_google_absl//absl/strings",
-    ],
-)
-
-cc_test(
-    name = "basic_rng_test",
-    srcs = ["basic_rng_test.cc"],
-    deps = [
-        ":basic_rng",
-        "//dpf/internal:status_matchers",
-        "@com_github_google_googletest//:gtest_main",
-        "@com_google_absl//absl/numeric:int128",
-    ],
-)
diff --git a/third_party/distributed_point_functions/code/dcf/fss_gates/prng/basic_rng.h b/third_party/distributed_point_functions/code/dcf/fss_gates/prng/basic_rng.h
deleted file mode 100644
index 13b0da8b..0000000
--- a/third_party/distributed_point_functions/code/dcf/fss_gates/prng/basic_rng.h
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2021 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef DISTRIBUTED_POINT_FUNCTIONS_PRNG_BASIC_RNG_H_
-#define DISTRIBUTED_POINT_FUNCTIONS_PRNG_BASIC_RNG_H_
-
-#include <openssl/rand.h>
-
-#include "absl/base/casts.h"
-#include "absl/memory/memory.h"
-#include "absl/numeric/int128.h"
-#include "absl/strings/string_view.h"
-#include "dcf/fss_gates/prng/prng.h"
-
-namespace distributed_point_functions {
-
-// Basic RNG class that uses RAND_bytes from OpenSSL to sample randomness.
-// BasicRng does not require a seed internally.
-class BasicRng : public SecurePrng {
- public:
-  // Create a BasicRng object.
-  // Returns an INTERNAL error code if the creation fails.
-  static absl::StatusOr<std::unique_ptr<BasicRng>> Create(
-      absl::string_view seed) {
-    return absl::make_unique<BasicRng>();
-  }
-
-  // Sample 8 bits of randomness using OpenSSL RAND_bytes.
-  // Returns an INTERNAL error code if the sampling fails.
-  inline absl::StatusOr<uint8_t> Rand8() override { return Rand<uint8_t>(); }
-
-  // Sample 64 bits of randomness using OPENSSL RAND_bytes.
-  // Returns an INTERNAL error code if the sampling fails.
-  inline absl::StatusOr<uint64_t> Rand64() override { return Rand<uint64_t>(); }
-
-  // Sample 128 bits of randomness using OPENSSL RAND_bytes.
-  // Returns an INTERNAL error code if the sampling fails.
-  inline absl::StatusOr<absl::uint128> Rand128() override {
-    return Rand<absl::uint128>();
-  }
-
-  // BasicRng does not use seeds.
-  static absl::StatusOr<std::string> GenerateSeed() { return std::string(); }
-  static int SeedLength() { return 0; }
-
- private:
-  template <typename T>
-  absl::StatusOr<T> Rand() {
-    std::array<uint8_t, sizeof(T)> rand;
-    int success = RAND_bytes(rand.data(), rand.size());
-    if (!success) {
-      return absl::InternalError(
-          "BasicRng::Rand - Failed to create randomness");
-    }
-    return absl::bit_cast<T>(rand);
-  }
-};
-
-}  // namespace distributed_point_functions
-
-#endif  // DISTRIBUTED_POINT_FUNCTIONS_PRNG_BASIC_RNG_H_
diff --git a/third_party/distributed_point_functions/code/dcf/fss_gates/prng/basic_rng_test.cc b/third_party/distributed_point_functions/code/dcf/fss_gates/prng/basic_rng_test.cc
deleted file mode 100644
index 5d955210..0000000
--- a/third_party/distributed_point_functions/code/dcf/fss_gates/prng/basic_rng_test.cc
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright 2021 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "dcf/fss_gates/prng/basic_rng.h"
-
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-
-#include "absl/numeric/int128.h"
-#include "dpf/internal/status_matchers.h"
-
-namespace distributed_point_functions {
-namespace {
-
-using ::testing::Test;
-
-const absl::string_view kSampleSeed = absl::string_view();
-constexpr int kNumSamples = 10;
-
-class BasicRngTest : public Test {
- protected:
-  void SetUp() {
-    DPF_ASSERT_OK_AND_ASSIGN(rng_, BasicRng::Create(kSampleSeed));
-  }
-
-  std::unique_ptr<BasicRng> rng_;
-};
-
-TEST_F(BasicRngTest, Test8BitRand) {
-  // Two random 8 bit strings have 1/256 probability of being equal. Instead,
-  // we check that 8 consecutively generated strings are not all equal.
-  bool equal = true;
-  DPF_ASSERT_OK_AND_ASSIGN(uint8_t prev, rng_->Rand8());
-  for (int i = 0; i < 8; ++i) {
-    DPF_ASSERT_OK_AND_ASSIGN(uint8_t next, rng_->Rand8());
-    if (next != prev) {
-      equal = false;
-    }
-    prev = next;
-  }
-  EXPECT_FALSE(equal);
-}
-
-TEST_F(BasicRngTest, Test64BitRand) {
-  DPF_ASSERT_OK_AND_ASSIGN(uint64_t r1, rng_->Rand64());
-  DPF_ASSERT_OK_AND_ASSIGN(uint64_t r2, rng_->Rand64());
-  EXPECT_NE(r1, r2);
-}
-
-TEST_F(BasicRngTest, Test128BitRand) {
-  DPF_ASSERT_OK_AND_ASSIGN(absl::uint128 r1, rng_->Rand128());
-  DPF_ASSERT_OK_AND_ASSIGN(absl::uint128 r2, rng_->Rand128());
-  EXPECT_NE(r1, r2);
-}
-
-TEST_F(BasicRngTest, BytesAreDifferent64) {
-  std::vector<uint64_t> rand(kNumSamples);
-  for (size_t i = 0; i < kNumSamples; ++i) {
-    DPF_ASSERT_OK_AND_ASSIGN(rand[i], rng_->Rand64());
-  }
-
-  for (int i = 0; i < sizeof(uint64_t); ++i) {
-    bool not_all_equal = false;
-    for (int j = 1; j < kNumSamples; ++j) {
-      auto byte1 = static_cast<uint8_t>(rand[j - 1] >> (8 * i));
-      auto byte2 = static_cast<uint8_t>(rand[j] >> (8 * i));
-      if (byte1 != byte2) {
-        not_all_equal = true;
-      }
-    }
-    EXPECT_TRUE(not_all_equal);
-  }
-}
-
-TEST_F(BasicRngTest, BytesAreDifferent128) {
-  std::vector<absl::uint128> rand(kNumSamples);
-  for (int i = 0; i < kNumSamples; ++i) {
-    DPF_ASSERT_OK_AND_ASSIGN(rand[i], rng_->Rand128());
-  }
-
-  for (size_t i = 0; i < sizeof(absl::uint128); ++i) {
-    bool not_all_equal = false;
-    for (int j = 1; j < kNumSamples; ++j) {
-      auto byte1 = static_cast<uint8_t>(rand[j - 1] >> (8 * i));
-      auto byte2 = static_cast<uint8_t>(rand[j] >> (8 * i));
-      if (byte1 != byte2) {
-        not_all_equal = true;
-      }
-    }
-    EXPECT_TRUE(not_all_equal);
-  }
-}
-
-}  // namespace
-}  // namespace distributed_point_functions
diff --git a/third_party/distributed_point_functions/code/dcf/fss_gates/prng/prng.h b/third_party/distributed_point_functions/code/dcf/fss_gates/prng/prng.h
deleted file mode 100644
index 1920deb..0000000
--- a/third_party/distributed_point_functions/code/dcf/fss_gates/prng/prng.h
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2021 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef DISTRIBUTED_POINT_FUNCTIONS_PRNG_PRNG_H_
-#define DISTRIBUTED_POINT_FUNCTIONS_PRNG_PRNG_H_
-
-#include "absl/numeric/int128.h"
-#include "absl/status/statusor.h"
-#include "absl/strings/string_view.h"
-#include "dpf/status_macros.h"
-
-namespace distributed_point_functions {
-
-// An interface for a secure pseudo-random number generator.
-class SecurePrng {
- public:
-  virtual absl::StatusOr<uint8_t> Rand8() = 0;
-  virtual absl::StatusOr<uint64_t> Rand64() = 0;
-  virtual absl::StatusOr<absl::uint128> Rand128() = 0;
-  virtual ~SecurePrng() = default;
-  static absl::StatusOr<std::unique_ptr<SecurePrng>> Create(
-      absl::string_view seed);
-  static absl::StatusOr<std::string> GenerateSeed();
-  static int SeedLength();
-};
-
-}  // namespace distributed_point_functions
-
-#endif  // DISTRIBUTED_POINT_FUNCTIONS_PRNG_PRNG_H_
diff --git a/third_party/gvr-android-sdk/OWNERS b/third_party/gvr-android-sdk/OWNERS
index 019bb305..cdc8cf48 100644
--- a/third_party/gvr-android-sdk/OWNERS
+++ b/third_party/gvr-android-sdk/OWNERS
@@ -1,5 +1,3 @@
-alcooper@chromium.org
-bajones@chromium.org
-bialpio@chromium.org
+file://components/webxr/OWNERS
 mthiesse@chromium.org
 vollick@chromium.org
diff --git a/third_party/openxr/OWNERS b/third_party/openxr/OWNERS
index fa80476..0b752de 100644
--- a/third_party/openxr/OWNERS
+++ b/third_party/openxr/OWNERS
@@ -1,2 +1,2 @@
-alcooper@chromium.org
+file://components/webxr/OWNERS
 rafael.cintron@microsoft.com
diff --git a/third_party/webxr_test_pages/OWNERS b/third_party/webxr_test_pages/OWNERS
index e01af6d..d95d28c 100644
--- a/third_party/webxr_test_pages/OWNERS
+++ b/third_party/webxr_test_pages/OWNERS
@@ -1,4 +1 @@
-alcooper@chromium.org
-bajones@chromium.org
-bialpio@chromium.org
-klausw@chromium.org
+file://components/webxr/OWNERS
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 5eb9ffc..b75dda82 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -65035,6 +65035,10 @@
   <int value="4" label="responses loaded from HTTP cache">
     Subset of Suggest responses that were loaded from the HTTP cache.
   </int>
+  <int value="5" label="out-of-date responses loaded from HTTP cache">
+    Subset of Suggest responses that were loaded from the HTTP cache and were
+    out-of-date.
+  </int>
 </enum>
 
 <enum name="OnServiceConnectedTimedOutResult">
@@ -73902,6 +73906,7 @@
   <int value="6" label="Thanks"/>
   <int value="7" label="Unsure"/>
   <int value="8" label="Heart"/>
+  <int value="9" label="Laugh Cry"/>
 </enum>
 
 <enum name="ReadDynamicRulesJSONStatus">
diff --git a/tools/metrics/histograms/metadata/optimization/histograms.xml b/tools/metrics/histograms/metadata/optimization/histograms.xml
index fa298ae..dc74ed9 100644
--- a/tools/metrics/histograms/metadata/optimization/histograms.xml
+++ b/tools/metrics/histograms/metadata/optimization/histograms.xml
@@ -393,6 +393,17 @@
   </summary>
 </histogram>
 
+<histogram name="OptimizationGuide.HintsManager.ConcurrentBatchUpdateFetches"
+    units="counts" expires_after="M106">
+  <owner>sophiechang@chromium.org</owner>
+  <owner>mcrouse@chromium.org</owner>
+  <summary>
+    The number of active batch update hints fetches initiated by the hints
+    manager. Recorded when a fetch is initiated to the remote Optimization Guide
+    Service for a batch update context (i.e. on SRP, other UI surfaces, etc.).
+  </summary>
+</histogram>
+
 <histogram
     name="OptimizationGuide.HintsManager.ConcurrentPageNavigationFetches"
     units="counts" expires_after="M106">
diff --git a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
index 800d567b..55402512 100644
--- a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
+++ b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
@@ -54,7 +54,8 @@
       summary="ESB enabled duration was between 1 hour and 24 hours"/>
   <variant name="NeverEnabled"
       summary="There is no enabled duration because ESB wasn't manually
-               enabled"/>
+               enabled, or it has been longer than kEventMaxDurationDay days
+               since the latest enabled event."/>
   <variant name="ShortEnabled"
       summary="ESB enabled duration was less than 1 hour"/>
 </variants>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index e56ee27..8bb4d07 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -9,16 +9,16 @@
             "remote_path": "perfetto_binaries/trace_processor_shell/win/9c674c8ed6844fb88b545a8df9282a9405f8a072/trace_processor_shell.exe"
         },
         "mac": {
-            "hash": "0b89b9b7de51b70e2218120061b62f9011bfcd08",
-            "remote_path": "perfetto_binaries/trace_processor_shell/mac/15e3467297c3877d090e92940a04b085318ab933/trace_processor_shell"
+            "hash": "83a92c4b88f963f5606a1e3c953d2051b0cb1958",
+            "remote_path": "perfetto_binaries/trace_processor_shell/mac/9c674c8ed6844fb88b545a8df9282a9405f8a072/trace_processor_shell"
         },
         "linux_arm64": {
             "hash": "5074025a2898ec41a872e70a5719e417acb0a380",
             "remote_path": "perfetto_binaries/trace_processor_shell/linux_arm64/49b4b5dcbc312d8d2c3751cf29238b8efeb4e494/trace_processor_shell"
         },
         "linux": {
-            "hash": "46bd1cb0b881714948a74f091500c22f241fea08",
-            "remote_path": "perfetto_binaries/trace_processor_shell/linux/15e3467297c3877d090e92940a04b085318ab933/trace_processor_shell"
+            "hash": "9e5e4909b2b6a6f4e1aeb2b5bc081567935aeea0",
+            "remote_path": "perfetto_binaries/trace_processor_shell/linux/cefb3e0ec3a0580c996f801e854fe02963c03d5c/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/ui/base/BUILD.gn b/ui/base/BUILD.gn
index 37151bc..acfc3ff 100644
--- a/ui/base/BUILD.gn
+++ b/ui/base/BUILD.gn
@@ -670,7 +670,10 @@
     if (is_win) {
       data += [ "//tools/skia_goldctl/win/goldctl.exe" ]
     } else if (is_mac) {
-      data += [ "//tools/skia_goldctl/mac/goldctl" ]
+      data += [
+        "//tools/skia_goldctl/mac_amd64/goldctl",
+        "//tools/skia_goldctl/mac_arm64/goldctl",
+      ]
     } else {
       data += [ "//tools/skia_goldctl/linux/goldctl" ]
     }
diff --git a/ui/base/test/skia_gold_pixel_diff.cc b/ui/base/test/skia_gold_pixel_diff.cc
index a754f6c0..2f6c231 100644
--- a/ui/base/test/skia_gold_pixel_diff.cc
+++ b/ui/base/test/skia_gold_pixel_diff.cc
@@ -40,7 +40,11 @@
 #if defined(OS_WIN)
 const wchar_t* kSkiaGoldCtl = L"tools/skia_goldctl/win/goldctl.exe";
 #elif defined(OS_APPLE)
-const char* kSkiaGoldCtl = "tools/skia_goldctl/mac/goldctl";
+#if defined(ARCH_CPU_ARM64)
+const char* kSkiaGoldCtl = "tools/skia_goldctl/mac_arm64/goldctl";
+#else
+const char* kSkiaGoldCtl = "tools/skia_goldctl/mac_amd64/goldctl";
+#endif  // defined(ARCH_CPU_ARM64)
 #else
 const char* kSkiaGoldCtl = "tools/skia_goldctl/linux/goldctl";
 #endif
diff --git a/ui/events/keycodes/dom/dom_code_data.inc b/ui/events/keycodes/dom/dom_code_data.inc
index c96987d..dd8ac49 100644
--- a/ui/events/keycodes/dom/dom_code_data.inc
+++ b/ui/events/keycodes/dom/dom_code_data.inc
@@ -76,6 +76,13 @@
 //       Apple keyboards with USB 0x070049 [Insert] labelled "Help" have not
 //       been made since 2007.
 
+// ChromeOS notes:
+//
+//  Any keys that are added or updated specifically for use in the ChromeOS
+//  top-row should also be updated in sections of Input Diagnostics:
+//    ash/webui/diagnostics_ui/mojom/input_data_provider.mojom: enum TopRowKey
+//    ash/webui/diagnostics_ui/backend/input_data_provider_keyboard.cc: kScancodeMapping
+
 DOM_CODE_DECLARATION {
 
   //            USB     evdev    XKB     Win     Mac   Code
diff --git a/ui/gtk/window_frame_provider_gtk.cc b/ui/gtk/window_frame_provider_gtk.cc
index 4c7773f..e9b7a98 100644
--- a/ui/gtk/window_frame_provider_gtk.cc
+++ b/ui/gtk/window_frame_provider_gtk.cc
@@ -8,6 +8,7 @@
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/geometry/insets.h"
 #include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/skia_conversions.h"
 #include "ui/gfx/scoped_canvas.h"
 #include "ui/gtk/gtk_compat.h"
 #include "ui/gtk/gtk_util.h"
@@ -47,7 +48,9 @@
 
 GtkCssContext DecorationContext(bool solid_frame, bool focused) {
   auto context = WindowContext(solid_frame, focused);
-  context = AppendCssNodeToStyleContext(context, "#decoration");
+  // GTK4 renders the decoration directly on the window.
+  if (!GtkCheckVersion(4))
+    context = AppendCssNodeToStyleContext(context, "#decoration");
   if (!focused)
     gtk_style_context_set_state(context, GTK_STATE_FLAG_BACKDROP);
 
@@ -105,8 +108,11 @@
 int ComputeTopCornerRadius() {
   // In GTK4, there's no way to directly obtain CSS values for a context, so we
   // need to experimentally determine the corner radius by rendering a sample.
-  auto context = HeaderContext(false, false);
-  ApplyCssToContext(context, R"(headerbar {
+  // Additionally, in GTK4, the headerbar corners get clipped by the window
+  // rather than the headerbar having its own rounded corners.
+  auto context = GtkCheckVersion(4) ? DecorationContext(false, false)
+                                    : HeaderContext(false, false);
+  ApplyCssToContext(context, R"(window, headerbar {
     background-image: none;
     background-color: black;
     box-shadow: none;
@@ -115,8 +121,10 @@
     border-bottom-right-radius: 0;
     border-top-right-radius: 0;
   })");
-  auto bitmap =
-      PaintHeaderbar({kMaxCornerRadiusDip, kMaxCornerRadiusDip}, context, 1);
+  gfx::Size size_dip{kMaxCornerRadiusDip, kMaxCornerRadiusDip};
+  auto bitmap = GtkCheckVersion(4)
+                    ? PaintBitmap(size_dip, {{0, 0}, size_dip}, context, 1)
+                    : PaintHeaderbar(size_dip, context, 1);
   DCHECK_EQ(bitmap.width(), bitmap.height());
   for (int i = 0; i < bitmap.width(); ++i) {
     if (SkColorGetA(bitmap.getColor(0, i)) == 255 &&
@@ -258,6 +266,17 @@
   auto header = PaintHeaderbar({client_bounds_px.width(), top_area_height_px},
                                HeaderContext(solid_frame_, focused), scale);
   image = gfx::ImageSkia::CreateFrom1xBitmap(header);
+  // In GTK4, the headerbar gets clipped by the window.
+  if (GtkCheckVersion(4)) {
+    gfx::RectF bounds_px =
+        gfx::RectF(client_bounds_px.x(), client_bounds_px.y(), header.width(),
+                   header.height());
+    float radius_px = scale * top_corner_radius_dip_;
+    SkVector radii[4]{{radius_px, radius_px}, {radius_px, radius_px}, {}, {}};
+    SkRRect clip;
+    clip.setRectRadii(gfx::RectFToSkRect(bounds_px), radii);
+    canvas->sk_canvas()->clipRRect(clip, SkClipOp::kIntersect, true);
+  }
   draw_image(0, 0, header.width(), header.height(), client_bounds_px.x(),
              client_bounds_px.y(), header.width(), header.height());
 }
diff --git a/ui/webui/resources/BUILD.gn b/ui/webui/resources/BUILD.gn
index 830ee0f..9d9dcab5 100644
--- a/ui/webui/resources/BUILD.gn
+++ b/ui/webui/resources/BUILD.gn
@@ -215,6 +215,7 @@
     "cr_elements/mwb_shared_style.ts",
     "cr_elements/mwb_shared_vars.ts",
     "cr_elements/search_highlight_style_css.ts",
+    "js/custom_element.ts",
     "js/i18n_mixin.ts",
     "js/list_property_update_mixin.ts",
     "js/web_ui_listener_mixin.ts",
@@ -260,7 +261,6 @@
     "$root_dir/js/cr/ui/focus_outline_manager.m.d.ts",
     "$root_dir/js/cr/ui/focus_row.m.d.ts",
     "$root_dir/js/cr/ui/keyboard_shortcut_list.m.d.ts",
-    "$root_dir/js/custom_element.d.ts",
     "$root_dir/js/event_tracker.m.d.ts",
     "$root_dir/js/load_time_data.m.d.ts",
     "$root_dir/js/plural_string_proxy.d.ts",
@@ -323,7 +323,6 @@
     "js/cr/ui/focus_row.m.js",
     "js/cr/ui/keyboard_shortcut_list.m.js",
     "js/cr/ui/store.js",
-    "js/custom_element.js",
     "js/event_tracker.m.js",
     "js/icon.js",
     "js/load_time_data.m.js",
diff --git a/ui/webui/resources/cr_components/BUILD.gn b/ui/webui/resources/cr_components/BUILD.gn
index c2fefdb..c66ad39 100644
--- a/ui/webui/resources/cr_components/BUILD.gn
+++ b/ui/webui/resources/cr_components/BUILD.gn
@@ -12,6 +12,7 @@
     "$root_gen_dir/ui/webui/resources/preprocessed/cr_components"
 preprocess_gen_manifest = "preprocessed_gen_manifest.json"
 preprocess_mojom_manifest = "preprocessed_mojom_manifest.json"
+preprocess_external_mojo_manifest = "preprocessed_external_mojo_manifest.json"
 if (is_chromeos_ash) {
   preprocess_polymer2_manifest = "preprocessed_polymer2_manifest.json"
 }
@@ -20,7 +21,10 @@
 generate_grd("build_grdp") {
   grd_prefix = "cr_components"
   out_grd = "$target_gen_dir/${grd_prefix}_resources.grdp"
-  deps = [ ":preprocess" ]
+  deps = [
+    ":preprocess",
+    ":preprocess_external_mojo",
+  ]
   if (is_chromeos_ash) {
     input_files_base_dir = rebase_path(".", "//")
     input_files = [
@@ -64,6 +68,7 @@
     "$target_gen_dir/$preprocess_gen_manifest",
     "$target_gen_dir/$preprocess_mojom_manifest",
     "$target_gen_dir/$preprocess_src_manifest",
+    "$target_gen_dir/$preprocess_external_mojo_manifest",
   ]
 
   # TODO(crbug.com/1184053): Fully remove once no longer used by CrOS.
@@ -72,6 +77,13 @@
   }
 
   resource_path_prefix = "cr_components"
+
+  resource_path_rewrites = [
+    "mojo/public/mojom/base/file_path.mojom-lite.js|app_management/file_path.mojom-lite.js",
+    "mojo/public/mojom/base/safe_base_name.mojom-lite.js|app_management/safe_base_name.mojom-lite.js",
+    "ui/gfx/image/mojom/image.mojom-lite.js|app_management/image.mojom-lite.js",
+    "components/services/app_service/public/mojom/types.mojom-lite.js|app_management/types.mojom-lite.js",
+  ]
 }
 
 group("preprocess") {
@@ -103,6 +115,12 @@
     "most_visited/window_proxy.js",
     "color_change_listener/browser_proxy.js",
     "color_change_listener/colors_css_updater.js",
+    "app_management/permission_constants.js",
+    "app_management/permission_util.js",
+    "app_management/browser_proxy.js",
+    "app_management/constants.js",
+    "app_management/types.js",
+    "app_management/util.js",
   ]
 }
 
@@ -121,7 +139,10 @@
 }
 
 preprocess_if_expr("preprocess_generated") {
-  deps = [ ":polymer3_elements" ]
+  deps = [
+    ":polymer3_elements",
+    "//ui/webui/resources/cr_components/app_management:mojo_bindings_js__generator",
+  ]
   in_folder = target_gen_dir
   out_folder = preprocess_folder
   out_manifest = "$target_gen_dir/$preprocess_gen_manifest"
@@ -131,6 +152,11 @@
     "managed_dialog/managed_dialog.js",
     "managed_footnote/managed_footnote.js",
     "omnibox/cr_autocomplete_match_list.js",
+    "app_management/permission_item.js",
+    "app_management/shared_style.js",
+    "app_management/shared_vars.js",
+    "app_management/toggle_row.js",
+    "app_management/app_management.mojom-lite.js",
   ]
 
   if (is_chromeos_ash) {
@@ -402,6 +428,7 @@
 
 group("closure_compile") {
   deps = [
+    "app_management:closure_compile",
     "color_change_listener:closure_compile",
     "managed_dialog:closure_compile",
     "managed_footnote:closure_compile",
@@ -416,6 +443,7 @@
 
 group("polymer3_elements") {
   public_deps = [
+    "app_management:web_components",
     "customize_themes:web_components",
     "iph_bubble:web_components",
     "managed_dialog:web_components",
@@ -428,3 +456,25 @@
     public_deps += [ "chromeos:polymer3_elements" ]
   }
 }
+
+preprocess_if_expr("preprocess_external_mojo") {
+  deps = [
+    "//components/services/app_service/public/mojom:mojom_js",
+    "//mojo/public/js:bindings_lite",
+    "//mojo/public/mojom/base",
+    "//ui/gfx/image/mojom:mojom_js",
+  ]
+  in_folder = "$root_gen_dir"
+
+  # It does not matter which preprocess folder these files are pasted into, as
+  # they are not used for bundling; the purpose of this build rule is to
+  # include them in the generated grd file.
+  out_folder = "$preprocess_folder"
+  out_manifest = "$target_gen_dir/$preprocess_external_mojo_manifest"
+  in_files = [
+    "mojo/public/mojom/base/file_path.mojom-lite.js",
+    "mojo/public/mojom/base/safe_base_name.mojom-lite.js",
+    "ui/gfx/image/mojom/image.mojom-lite.js",
+    "components/services/app_service/public/mojom/types.mojom-lite.js",
+  ]
+}
diff --git a/ui/webui/resources/cr_components/app_management/BUILD.gn b/ui/webui/resources/cr_components/app_management/BUILD.gn
new file mode 100644
index 0000000..e6bd6b82
--- /dev/null
+++ b/ui/webui/resources/cr_components/app_management/BUILD.gn
@@ -0,0 +1,97 @@
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+import("//third_party/closure_compiler/compile_js.gni")
+import("//tools/polymer/html_to_js.gni")
+
+js_type_check("closure_compile") {
+  is_polymer3 = true
+  deps = [
+    ":browser_proxy",
+    ":constants",
+    ":permission_constants",
+    ":permission_item",
+    ":permission_util",
+    ":shared_style",
+    ":shared_vars",
+    ":toggle_row",
+    ":types",
+    ":util",
+  ]
+}
+
+js_library("permission_item") {
+  deps = [
+    ":browser_proxy",
+    ":permission_constants",
+    ":toggle_row",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+  ]
+}
+
+js_library("shared_style") {
+  deps = []
+}
+
+js_library("shared_vars") {
+  deps = []
+}
+
+js_library("permission_constants") {
+  deps = [ ":mojo_bindings_js_library_for_compile" ]
+}
+
+js_library("permission_util") {
+  deps = [
+    ":permission_constants",
+    "//ui/webui/resources/js:assert.m",
+  ]
+}
+
+js_library("browser_proxy") {
+  deps = [
+    ":mojo_bindings_js_library_for_compile",
+    "//ui/webui/resources/js:cr.m",
+  ]
+}
+
+js_library("toggle_row") {
+  deps = [
+    ":types",
+    "//ui/webui/resources/cr_elements/cr_toggle:cr_toggle.m",
+    "//ui/webui/resources/cr_elements/policy:cr_policy_indicator.m",
+  ]
+}
+
+js_library("constants") {
+  deps = [ ":mojo_bindings_js_library_for_compile" ]
+}
+
+js_library("types") {
+  deps = []
+}
+
+js_library("util") {
+  deps = [ ":permission_constants" ]
+  externs_list = [ "//third_party/closure_compiler/externs/metrics_private.js" ]
+}
+
+html_to_js("web_components") {
+  js_files = [
+    "permission_item.js",
+    "shared_style.js",
+    "shared_vars.js",
+    "toggle_row.js",
+  ]
+}
+
+mojom("mojo_bindings") {
+  sources = [ "app_management.mojom" ]
+
+  public_deps = [
+    "//components/services/app_service/public/mojom",
+    "//mojo/public/mojom/base",
+  ]
+}
diff --git a/ui/webui/resources/cr_components/app_management/OWNERS b/ui/webui/resources/cr_components/app_management/OWNERS
new file mode 100644
index 0000000..d3f5d59
--- /dev/null
+++ b/ui/webui/resources/cr_components/app_management/OWNERS
@@ -0,0 +1,4 @@
+file://chrome/browser/resources/settings/chromeos/os_apps_page/OWNERS
+
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
diff --git a/chrome/browser/ui/webui/app_management/app_management.mojom b/ui/webui/resources/cr_components/app_management/app_management.mojom
similarity index 100%
rename from chrome/browser/ui/webui/app_management/app_management.mojom
rename to ui/webui/resources/cr_components/app_management/app_management.mojom
diff --git a/ui/webui/resources/cr_components/app_management/browser_proxy.js b/ui/webui/resources/cr_components/app_management/browser_proxy.js
new file mode 100644
index 0000000..983f19a
--- /dev/null
+++ b/ui/webui/resources/cr_components/app_management/browser_proxy.js
@@ -0,0 +1,30 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'chrome://resources/mojo/mojo/public/js/mojo_bindings_lite.js';
+import 'chrome://resources/mojo/skia/public/mojom/image_info.mojom-lite.js';
+import 'chrome://resources/mojo/skia/public/mojom/bitmap.mojom-lite.js';
+import 'chrome://resources/mojo/url/mojom/url.mojom-lite.js';
+import './file_path.mojom-lite.js';
+import './image.mojom-lite.js';
+import './types.mojom-lite.js';
+import './app_management.mojom-lite.js';
+
+import {addSingletonGetter} from 'chrome://resources/js/cr.m.js';
+
+export class BrowserProxy {
+  constructor() {
+    /** @type {appManagement.mojom.PageCallbackRouter} */
+    this.callbackRouter = new appManagement.mojom.PageCallbackRouter();
+
+    /** @type {appManagement.mojom.PageHandlerRemote} */
+    this.handler = new appManagement.mojom.PageHandlerRemote();
+    const factory = appManagement.mojom.PageHandlerFactory.getRemote();
+    factory.createPageHandler(
+        this.callbackRouter.$.bindNewPipeAndPassRemote(),
+        this.handler.$.bindNewPipeAndPassReceiver());
+  }
+}
+
+addSingletonGetter(BrowserProxy);
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/constants.js b/ui/webui/resources/cr_components/app_management/constants.js
similarity index 90%
rename from chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/constants.js
rename to ui/webui/resources/cr_components/app_management/constants.js
index 6a95b0f..6130d23 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/constants.js
+++ b/ui/webui/resources/cr_components/app_management/constants.js
@@ -7,11 +7,11 @@
 import 'chrome://resources/mojo/skia/public/mojom/image_info.mojom-lite.js';
 import 'chrome://resources/mojo/skia/public/mojom/bitmap.mojom-lite.js';
 import 'chrome://resources/mojo/url/mojom/url.mojom-lite.js';
-import '/app-management/file_path.mojom-lite.js';
-import '/app-management/image.mojom-lite.js';
-import '/app-management/safe_base_name.mojom-lite.js';
-import '/app-management/types.mojom-lite.js';
-import '/app-management/app_management.mojom-lite.js';
+import './file_path.mojom-lite.js';
+import './image.mojom-lite.js';
+import './safe_base_name.mojom-lite.js';
+import './types.mojom-lite.js';
+import './app_management.mojom-lite.js';
 
 /**
  * The number of apps displayed in app list in the main view before expanding.
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/permission_constants.js b/ui/webui/resources/cr_components/app_management/permission_constants.js
similarity index 66%
rename from chrome/browser/resources/settings/chromeos/os_apps_page/permission_constants.js
rename to ui/webui/resources/cr_components/app_management/permission_constants.js
index 37e2e580..26d8bd84 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/permission_constants.js
+++ b/ui/webui/resources/cr_components/app_management/permission_constants.js
@@ -4,10 +4,10 @@
 
 import 'chrome://resources/mojo/mojo/public/js/mojo_bindings_lite.js';
 
-import '/app-management/file_path.mojom-lite.js';
-import '/app-management/image.mojom-lite.js';
-import '/app-management/safe_base_name.mojom-lite.js';
-import '/app-management/types.mojom-lite.js';
+import './file_path.mojom-lite.js';
+import './image.mojom-lite.js';
+import './safe_base_name.mojom-lite.js';
+import './types.mojom-lite.js';
 
 
 export const TriState = apps.mojom.TriState;
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/permission_item.html b/ui/webui/resources/cr_components/app_management/permission_item.html
similarity index 100%
rename from chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/permission_item.html
rename to ui/webui/resources/cr_components/app_management/permission_item.html
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/permission_item.js b/ui/webui/resources/cr_components/app_management/permission_item.js
similarity index 93%
rename from chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/permission_item.js
rename to ui/webui/resources/cr_components/app_management/permission_item.js
index ac3489d..4bfade1 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/permission_item.js
+++ b/ui/webui/resources/cr_components/app_management/permission_item.js
@@ -7,22 +7,16 @@
 import {assert, assertNotReached} from '//resources/js/assert.m.js';
 import {afterNextRender, flush, html, Polymer, TemplateInstanceBase, Templatizer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {recordClick, recordNavigation, recordPageBlur, recordPageFocus, recordSearch, recordSettingChange, setUserActionRecorderForTesting} from '../../metrics_recorder.m.js';
-import {PermissionType, PermissionValue, TriState} from '../permission_constants.js';
-import {createBoolPermission, createTriStatePermission, getBoolPermissionValue, getTriStatePermissionValue, isBoolValue, isTriStateValue} from '../permission_util.js';
-
 import {BrowserProxy} from './browser_proxy.js';
 import {AppManagementUserAction} from './constants.js';
-import {AppManagementStoreClient} from './store_client.js';
+import {PermissionType, PermissionValue, TriState} from './permission_constants.js';
+import {createBoolPermission, createTriStatePermission, getBoolPermissionValue, getTriStatePermissionValue, isBoolValue, isTriStateValue} from './permission_util.js';
 import {getPermission, getPermissionValueBool, getSelectedApp, recordAppManagementUserAction} from './util.js';
 
 Polymer({
   _template: html`{__html_template__}`,
   is: 'app-management-permission-item',
 
-  behaviors: [
-    AppManagementStoreClient,
-  ],
 
   properties: {
     /**
@@ -82,11 +76,6 @@
 
   listeners: {click: 'onClick_', change: 'togglePermission_'},
 
-  attached() {
-    this.watch('app_', state => getSelectedApp(state));
-    this.updateFromStore();
-  },
-
   /**
    * Returns true if the permission type is available for the app.
    *
@@ -186,7 +175,6 @@
     BrowserProxy.getInstance().handler.setPermission(
         this.app_.id, newPermission);
 
-    recordSettingChange();
     recordAppManagementUserAction(
         this.app_.type,
         this.getUserMetricActionForPermission_(
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/permission_util.js b/ui/webui/resources/cr_components/app_management/permission_util.js
similarity index 100%
rename from chrome/browser/resources/settings/chromeos/os_apps_page/permission_util.js
rename to ui/webui/resources/cr_components/app_management/permission_util.js
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/shared_style.html b/ui/webui/resources/cr_components/app_management/shared_style.html
similarity index 100%
rename from chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/shared_style.html
rename to ui/webui/resources/cr_components/app_management/shared_style.html
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/shared_style.js b/ui/webui/resources/cr_components/app_management/shared_style.js
similarity index 100%
rename from chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/shared_style.js
rename to ui/webui/resources/cr_components/app_management/shared_style.js
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/shared_vars.html b/ui/webui/resources/cr_components/app_management/shared_vars.html
similarity index 100%
rename from chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/shared_vars.html
rename to ui/webui/resources/cr_components/app_management/shared_vars.html
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/shared_vars.js b/ui/webui/resources/cr_components/app_management/shared_vars.js
similarity index 100%
rename from chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/shared_vars.js
rename to ui/webui/resources/cr_components/app_management/shared_vars.js
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/toggle_row.html b/ui/webui/resources/cr_components/app_management/toggle_row.html
similarity index 100%
rename from chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/toggle_row.html
rename to ui/webui/resources/cr_components/app_management/toggle_row.html
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/toggle_row.js b/ui/webui/resources/cr_components/app_management/toggle_row.js
similarity index 92%
rename from chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/toggle_row.js
rename to ui/webui/resources/cr_components/app_management/toggle_row.js
index 54f4457..6bc6fd00 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/toggle_row.js
+++ b/ui/webui/resources/cr_components/app_management/toggle_row.js
@@ -7,9 +7,6 @@
 
 import {afterNextRender, flush, html, Polymer, TemplateInstanceBase, Templatizer} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {BrowserProxy} from './browser_proxy.js';
-import {AppManagementStoreClient} from './store_client.js';
-
 Polymer({
   _template: html`{__html_template__}`,
   is: 'app-management-toggle-row',
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/types.js b/ui/webui/resources/cr_components/app_management/types.js
similarity index 100%
rename from chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/types.js
rename to ui/webui/resources/cr_components/app_management/types.js
diff --git a/ui/webui/resources/cr_components/app_management/util.js b/ui/webui/resources/cr_components/app_management/util.js
new file mode 100644
index 0000000..2f23b5f
--- /dev/null
+++ b/ui/webui/resources/cr_components/app_management/util.js
@@ -0,0 +1,190 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {assert} from 'chrome://resources/js/assert.m.js';
+import {assertNotReached} from 'chrome://resources/js/assert.m.js';
+
+import {AppManagementUserAction, AppType, OptionalBool} from './constants.js';
+import {PermissionType} from './permission_constants.js';
+import {isPermissionEnabled} from './permission_util.js';
+
+
+/**
+ * @fileoverview Utility functions for the App Management page.
+ */
+
+/**
+ * @return {!AppManagementPageState}
+ */
+export function createEmptyState() {
+  return {
+    apps: {},
+    selectedAppId: null,
+  };
+}
+
+/**
+ * @param {!Array<App>} apps
+ * @return {!AppManagementPageState}
+ */
+export function createInitialState(apps) {
+  const initialState = createEmptyState();
+
+  for (const app of apps) {
+    initialState.apps[app.id] = app;
+  }
+
+  return initialState;
+}
+
+/**
+ * @param {App} app
+ * @return {string}
+ */
+export function getAppIcon(app) {
+  return `chrome://app-icon/${app.id}/64`;
+}
+
+/**
+ * If the given value is not in the set, returns a new set with the value
+ * added, otherwise returns the old set.
+ * @template T
+ * @param {!Set<T>} set
+ * @param {T} value
+ * @return {!Set<T>}
+ */
+export function addIfNeeded(set, value) {
+  if (!set.has(value)) {
+    set = new Set(set);
+    set.add(value);
+  }
+  return set;
+}
+
+/**
+ * If the given value is in the set, returns a new set without the value,
+ * otherwise returns the old set.
+ * @template T
+ * @param {!Set<T>} set
+ * @param {T} value
+ * @return {!Set<T>}
+ */
+export function removeIfNeeded(set, value) {
+  if (set.has(value)) {
+    set = new Set(set);
+    set.delete(value);
+  }
+  return set;
+}
+
+/**
+ * @param {App} app
+ * @param {string} permissionType
+ * @return {boolean}
+ */
+export function getPermissionValueBool(app, permissionType) {
+  const permission = getPermission(app, permissionType);
+  assert(permission);
+
+  return isPermissionEnabled(permission.value);
+}
+
+/**
+ * Undefined is returned when the app does not request a permission.
+ *
+ * @param {App} app
+ * @param {string} permissionType
+ * @return {Permission|undefined}
+ */
+export function getPermission(app, permissionType) {
+  return app.permissions[PermissionType[permissionType]];
+}
+
+/**
+ * @param {AppManagementPageState} state
+ * @return {?App}
+ */
+export function getSelectedApp(state) {
+  const selectedAppId = state.selectedAppId;
+  return selectedAppId ? state.apps[selectedAppId] : null;
+}
+
+/**
+ * A comparator function to sort strings alphabetically.
+ *
+ * @param {string} a
+ * @param {string} b
+ */
+export function alphabeticalSort(a, b) {
+  return a.localeCompare(b);
+}
+
+/**
+ * Toggles an OptionalBool
+ *
+ * @param {OptionalBool} bool
+ * @return {OptionalBool}
+ */
+export function toggleOptionalBool(bool) {
+  switch (bool) {
+    case OptionalBool.kFalse:
+      return OptionalBool.kTrue;
+    case OptionalBool.kTrue:
+      return OptionalBool.kFalse;
+    default:
+      assertNotReached();
+  }
+}
+
+/**
+ * @param {OptionalBool} optionalBool
+ * @returns {boolean}
+ */
+export function convertOptionalBoolToBool(optionalBool) {
+  switch (optionalBool) {
+    case OptionalBool.kTrue:
+      return true;
+    case OptionalBool.kFalse:
+      return false;
+    default:
+      assertNotReached();
+  }
+}
+
+/**
+ * @param {AppType} appType
+ * @return {string}
+ * @private
+ */
+export function getUserActionHistogramNameForAppType_(appType) {
+  switch (appType) {
+    case AppType.kArc:
+      return 'AppManagement.AppDetailViews.ArcApp';
+    case AppType.kChromeApp:
+    case AppType.kStandaloneBrowser:
+    case AppType.kStandaloneBrowserChromeApp:
+      // TODO(https://crbug.com/1225848): Figure out appropriate behavior for
+      // Lacros-hosted chrome-apps.
+      return 'AppManagement.AppDetailViews.ChromeApp';
+    case AppType.kWeb:
+      return 'AppManagement.AppDetailViews.WebApp';
+    case AppType.kPluginVm:
+      return 'AppManagement.AppDetailViews.PluginVmApp';
+    case AppType.kBorealis:
+      return 'AppManagement.AppDetailViews.BorealisApp';
+    default:
+      assertNotReached();
+  }
+}
+
+/**
+ * @param {AppType} appType
+ * @param {AppManagementUserAction} userAction
+ */
+export function recordAppManagementUserAction(appType, userAction) {
+  const histogram = getUserActionHistogramNameForAppType_(appType);
+  const enumLength = Object.keys(AppManagementUserAction).length;
+  chrome.metricsPrivate.recordEnumerationValue(
+      histogram, userAction, enumLength);
+}
diff --git a/ui/webui/resources/js/BUILD.gn b/ui/webui/resources/js/BUILD.gn
index 8bb2f10..29fe466 100644
--- a/ui/webui/resources/js/BUILD.gn
+++ b/ui/webui/resources/js/BUILD.gn
@@ -47,6 +47,7 @@
   in_folder = "./"
   out_folder = "$preprocess_folder"
   in_files = [
+    "custom_element.ts",
     "i18n_mixin.ts",
     "list_property_update_mixin.ts",
     "web_ui_listener_mixin.ts",
@@ -62,7 +63,6 @@
     "assert.js",
     "color_utils.js",
     "cr.m.js",
-    "custom_element.js",
     "i18n_template_no_process.js",
     "icon.js",
     "load_time_data.m.js",
@@ -292,7 +292,6 @@
   deps = [
     ":assert.m",
     ":cr.m",
-    ":custom_element",
     ":event_tracker.m",
     ":i18n_behavior.m",
     ":icon",
@@ -328,9 +327,6 @@
   externs_list = [ "$externs_path/chrome_send.js" ]
 }
 
-js_library("custom_element") {
-}
-
 js_library("event_tracker.m") {
   sources = [ "$root_gen_dir/ui/webui/resources/js/event_tracker.m.js" ]
   extra_deps = [ ":modulize_local" ]
diff --git a/ui/webui/resources/js/custom_element.js b/ui/webui/resources/js/custom_element.js
deleted file mode 100644
index 9d74568..0000000
--- a/ui/webui/resources/js/custom_element.js
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview Base class for Web Components that don't use Polymer.
- * See the following file for usage:
- * chrome/test/data/webui/js/custom_element_test.js
- */
-export class CustomElement extends HTMLElement {
-  constructor() {
-    super();
-
-    this.attachShadow({mode: 'open'});
-    const template = document.createElement('template');
-    template.innerHTML = this.constructor.template || '';
-    this.shadowRoot.appendChild(template.content.cloneNode(true));
-  }
-
-  /**
-   * @param {string} query
-   * @return {?Element}
-   */
-  $(query) {
-    return this.shadowRoot.querySelector(query);
-  }
-
-  /**
-   * @param {string} query
-   * @return {!NodeList<!Element>}
-   */
-  $all(query) {
-    return this.shadowRoot.querySelectorAll(query);
-  }
-}
diff --git a/ui/webui/resources/js/custom_element.ts b/ui/webui/resources/js/custom_element.ts
new file mode 100644
index 0000000..b2f0f1a2
--- /dev/null
+++ b/ui/webui/resources/js/custom_element.ts
@@ -0,0 +1,33 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview Base class for Web Components that don't use Polymer.
+ * See the following file for usage:
+ * chrome/test/data/webui/js/custom_element_test.js
+ */
+
+export class CustomElement extends HTMLElement {
+  static get template(): string {
+    return '';
+  }
+
+  constructor() {
+    super();
+
+    this.attachShadow({mode: 'open'});
+    const template = document.createElement('template');
+    template.innerHTML =
+        (this.constructor as typeof CustomElement).template || '';
+    this.shadowRoot!.appendChild(template.content.cloneNode(true));
+  }
+
+  $<E extends Element = Element>(query: string): E|null {
+    return this.shadowRoot!.querySelector<E>(query);
+  }
+
+  $all<E extends Element = Element>(query: string): NodeListOf<E> {
+    return this.shadowRoot!.querySelectorAll<E>(query);
+  }
+}
diff --git a/weblayer/browser/android/javatests/skew/expectations.txt b/weblayer/browser/android/javatests/skew/expectations.txt
index ba796a08f..d73b58c 100644
--- a/weblayer/browser/android/javatests/skew/expectations.txt
+++ b/weblayer/browser/android/javatests/skew/expectations.txt
@@ -5,7 +5,7 @@
 # with versions less than or equal to $VERSION of the implementation.
 #
 # These lines are not comments! They define the set of known tags and other information.
-# tags: [ client_lte_91 client_lte_94 client_lte_95 impl_lte_95 ]
+# tags: [ client_lte_91 client_lte_94 client_lte_95 impl_lte_95 client_lte_98 impl_lte_98 ]
 # 'all' disables the test from any skew test.
 # tags: [ all ]
 # results: [ Skip ]
@@ -95,3 +95,19 @@
 # fail in this case. Unfortunately there is no way to skip tests based on
 # the impl being >= a given version.
 crbug.com/1233480 [ all ] org.chromium.weblayer.test.NavigationTest#testInitialRendererInitiatedNavigationToAboutBlankFails [ Skip ]
+
+# In 99 we added logic to avoid the disambiguation dialog and a test for it.
+crbug.com/1278017 [ client_lte_98 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentAfterRedirectInBackgroundTabLaunchedWhenBackgroundLaunchesAllowed [ Skip ]
+crbug.com/1278017 [ client_lte_98 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentAfterRedirectLaunched [ Skip ]
+crbug.com/1278017 [ client_lte_98 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentInNewTabLaunchedOnLinkClick [ Skip ]
+crbug.com/1278017 [ client_lte_98 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentInSameTabLaunchedOnLinkClick [ Skip ]
+crbug.com/1278017 [ client_lte_98 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentNavigationParamSetOnIntentLaunchViaLinkClick [ Skip ]
+crbug.com/1278017 [ client_lte_98 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentNavigationParamSetOnNavigationsToIntents [ Skip ]
+crbug.com/1278017 [ client_lte_98 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentViaOnLoadLaunched [ Skip ]
+crbug.com/1278017 [ client_lte_98 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentWithFallbackUrlAfterRedirectLaunched [ Skip ]
+crbug.com/1278017 [ client_lte_98 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentWithNoRedirectInBackgroundTabLaunchedWhenBackgroundLaunchesAllowed [ Skip ]
+crbug.com/1278017 [ client_lte_98 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentWithNoRedirectInBrowserStartupLaunchedWhenBackgroundLaunchesAllowed [ Skip ]
+crbug.com/1278017 [ client_lte_98 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentWithNoRedirectLaunched [ Skip ]
+crbug.com/1278017 [ client_lte_98 ] org.chromium.weblayer.test.ExternalNavigationTest#testUserClicksLinkToPageWithExternalIntentLaunchedViaOnLoad [ Skip ]
+crbug.com/1278017 [ client_lte_98 ] org.chromium.weblayer.test.ExternalNavigationTest#testAvoidDisambiguationDialog [ Skip ]
+crbug.com/1278017 [ impl_lte_98 ] org.chromium.weblayer.test.ExternalNavigationTest#testAvoidDisambiguationDialog [ Skip ]