diff --git a/DEPS b/DEPS
index 09df101..c3ba0b6 100644
--- a/DEPS
+++ b/DEPS
@@ -299,15 +299,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': '8943ec85c41cc1dd976785f2d29f712952fc2e92',
+  'skia_revision': 'b368746d696a72bff291dddf7ab1492f5d72267f',
   # 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': '2227d8bbb76f71e31edca694815522149547c305',
+  'v8_revision': '6eadb14bb7a7908c7c0b86a0381260430b6c81fc',
   # 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': '2072aea9998c4170bff1811a5013605b99c66547',
+  'angle_revision': '0f3aaebf742f0ac16360a1191d75f99843d1b255',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -434,11 +434,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libavif
   # and whatever else without interference from each other.
-  'libavif_revision': 'bfb84ad6f9791be5b1d2d68e43bf1c79fc17b06c',
+  'libavif_revision': '96b8450ca5e5acf173645aafc5c5e91959dfe0c1',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling nearby
   # and whatever else without interference from each other.
-  'nearby_revision': '73c1fba668a86dd003fa2ac6af86ab7d89c09908',
+  'nearby_revision': 'b3f85342261db10bbeb2e5a07c7e54b616309b9e',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling securemessage
   # and whatever else without interference from each other.
@@ -478,7 +478,7 @@
 
   # If you change this, also update the libc++ revision in
   # //buildtools/deps_revisions.gni.
-  'libcxx_revision':       '19ffb9c00636bcabb6c6ce76ccaa4370583d3649',
+  'libcxx_revision':       '2fc3d704672fbd3e85fad8492d39e02d49412891',
 
   # GN CIPD package version.
   'gn_version': 'git_revision:5e19d2fb166fbd4f6f32147fbb2f497091a54ad8',
@@ -1205,7 +1205,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'e1c8efebe0a3cce42ca46d6057b6d4bd909ad203',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '18359bb9a19ee9b31a33fae83aa49ced770a9b81',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1640,7 +1640,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '165c172f3a6e30aa98495eaef67d5042cb4133a8',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'aa7e493dcf9bf77707735b98723b916e08561bbe',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1822,10 +1822,10 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'd1b65aa5a88f6efd900604dfcda840154e9f16e2',
 
   'src/third_party/webgpu-cts/src':
-    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'b99b81d6c5f615447fbdb469fa720808faf998b1',
+    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '216c8e78167b6643afb2dbb170f1501471971112',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '597a2ba41aaa0b7ce09c8e0db94060cd6af7a52d',
+    Var('webrtc_git') + '/src.git' + '@' + '919b79b7efc34a059cafef7e0901c467b230d69a',
 
   # Wuffs' canonical repository is at github.com/google/wuffs, but we use
   # Skia's mirror of Wuffs, the same as in upstream Skia's DEPS file.
@@ -1895,7 +1895,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@5232f9de253ba0a63dfdd8c7965d59fb4cb2b315',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@94562e58e6e22f0f8b5c1dd89ab590f94494bf85',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java b/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
index 24dee108..fbc78d2cc 100644
--- a/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
+++ b/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
@@ -189,6 +189,9 @@
             Flag.baseFeature(AutofillFeatures.AUTOFILL_SERVER_BEHAVIORS,
                     "When enabled, Autofill will request experimental "
                             + "predictions from the Autofill API."),
+            Flag.baseFeature(AutofillFeatures.AUTOFILL_SUPPORT_POOR_MANS_PLACEHOLDER,
+                    "When enabled, Autofill will infer labels from artificial placeholders, "
+                            + "placed on top of input fields using CSS."),
             Flag.baseFeature(FeatureConstants.KEYBOARD_ACCESSORY_PAYMENT_VIRTUAL_CARD_FEATURE,
                     "When enabled, merchant bound virtual cards will be offered in the keyboard "
                             + "accessory."),
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AcceptLanguageTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AcceptLanguageTest.java
index def3b07..0ad75e76 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AcceptLanguageTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AcceptLanguageTest.java
@@ -4,11 +4,9 @@
 
 package org.chromium.android_webview.test;
 
-import android.os.Build;
 import android.os.LocaleList;
 import android.support.test.InstrumentationRegistry;
 
-import androidx.annotation.RequiresApi;
 import androidx.test.filters.SmallTest;
 
 import org.junit.After;
@@ -21,7 +19,6 @@
 import org.chromium.android_webview.AwContents;
 import org.chromium.android_webview.test.util.JSUtils;
 import org.chromium.base.test.util.Feature;
-import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.net.test.EmbeddedTestServer;
 
 import java.util.Locale;
@@ -80,20 +77,12 @@
 
     private boolean isSingleLocale(String lang, String country) {
         String languageTag = String.format("%s-%s", lang, country);
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
-            // In N+, multiple locales can be set.
-            return languageTag.equals(LocaleList.getDefault().toLanguageTags());
-        } else {
-            return languageTag.equals(Locale.getDefault().toLanguageTag());
-        }
+        // In N+, multiple locales can be set.
+        return languageTag.equals(LocaleList.getDefault().toLanguageTags());
     }
 
     private void setSingleLocale(String lang, String country) {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
-            LocaleList.setDefault(new LocaleList(new Locale(lang, country)));
-        } else {
-            Locale.setDefault(new Locale(lang, country));
-        }
+        LocaleList.setDefault(new LocaleList(new Locale(lang, country)));
     }
 
     private void setLocaleForTesting(String lang, String country) {
@@ -154,8 +143,6 @@
      */
     @Test
     @SmallTest
-    @MinAndroidSdkLevel(Build.VERSION_CODES.N)
-    @RequiresApi(Build.VERSION_CODES.N)
     @Feature({"AndroidWebView"})
     public void testAcceptLanguagesWithenUS() throws Throwable {
         LocaleList.setDefault(new LocaleList(new Locale("ko", "KR")));
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/devui/DeveloperUiTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/devui/DeveloperUiTest.java
index 24d7953..0abc802 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/devui/DeveloperUiTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/devui/DeveloperUiTest.java
@@ -34,7 +34,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
-import android.os.Build;
 import android.provider.Settings;
 import android.support.test.InstrumentationRegistry;
 import android.view.View;
@@ -46,7 +45,6 @@
 
 import org.hamcrest.Matcher;
 import org.junit.After;
-import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -178,9 +176,6 @@
     @MediumTest
     @Feature({"AndroidWebView"})
     public void testMenuOptions_switchProvider_shownOnNougat() throws Throwable {
-        Assume.assumeTrue("This test verifies behavior introduced in Nougat and above",
-                Build.VERSION.SDK_INT >= Build.VERSION_CODES.N);
-
         launchHomeFragment();
 
         openOptionsMenu();
@@ -192,19 +187,6 @@
     @Test
     @MediumTest
     @Feature({"AndroidWebView"})
-    public void testMenuOptions_switchProvider_notShown() throws Throwable {
-        Assume.assumeTrue("This test verifies pre-Nougat behavior",
-                Build.VERSION.SDK_INT < Build.VERSION_CODES.N);
-
-        launchHomeFragment();
-
-        openOptionsMenu();
-        onView(withId(R.id.options_menu_switch_provider)).check(doesNotExist());
-    }
-
-    @Test
-    @MediumTest
-    @Feature({"AndroidWebView"})
     public void testMenuOptions_reportBug() throws Throwable {
         launchHomeFragment();
 
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/devui/HomeFragmentTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/devui/HomeFragmentTest.java
index 52c7602..20f740c 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/devui/HomeFragmentTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/devui/HomeFragmentTest.java
@@ -40,7 +40,6 @@
 
 import org.junit.After;
 import org.junit.Assert;
-import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -242,9 +241,6 @@
     @MediumTest
     @Feature({"AndroidWebView"})
     public void testDifferentWebViewPackageError_bannerMessage_postNougat() throws Throwable {
-        Assume.assumeTrue("This test verifies behavior introduced in Nougat and above",
-                Build.VERSION.SDK_INT >= Build.VERSION_CODES.N);
-
         Context context = InstrumentationRegistry.getTargetContext();
         // Inject a dummy PackageInfo as the current WebView package to make sure it will always be
         // different from the test's app package.
@@ -271,9 +267,6 @@
     @Feature({"AndroidWebView"})
     // Test the dialog shown when the WebView package error message is clicked.
     public void testDifferentWebViewPackageError_dialog_postNougat() throws Throwable {
-        Assume.assumeTrue("This test verifies behavior introduced in Nougat and above",
-                Build.VERSION.SDK_INT >= Build.VERSION_CODES.N);
-
         Context context = InstrumentationRegistry.getTargetContext();
         // Inject a dummy PackageInfo as the current WebView package to make sure it will always be
         // different from the test's app package.
@@ -298,65 +291,4 @@
                 .perform(click());
         intended(IntentMatchers.hasAction(Settings.ACTION_WEBVIEW_SETTINGS));
     }
-
-    @Test
-    @MediumTest
-    @Feature({"AndroidWebView"})
-    // Test that error message is shown when system's WebView provider package is different from dev
-    // UI's on a preNougat android versions (where WebView provider can't be changed).
-    public void testDifferentWebViewPackageError_bannerMessage_preNougat() throws Throwable {
-        Assume.assumeTrue("This test verifies pre-Nougat behavior",
-                Build.VERSION.SDK_INT < Build.VERSION_CODES.N);
-
-        Context context = InstrumentationRegistry.getTargetContext();
-        // Inject a dummy PackageInfo as the current WebView package to make sure it will always be
-        // different from the test's app package.
-        WebViewPackageHelper.setCurrentWebViewPackageForTesting(FAKE_WEBVIEW_PACKAGE);
-        launchHomeFragment();
-
-        String expectedErrorMessage = String.format(Locale.US,
-                WebViewPackageError.DIFFERENT_WEBVIEW_PROVIDER_ERROR_MESSAGE,
-                WebViewPackageHelper.loadLabel(context));
-        ViewUtils.waitForView(withId(R.id.main_error_view));
-        onView(withId(R.id.main_error_view)).check(matches(isDisplayed()));
-        onView(withId(R.id.error_text)).check(matches(withText(expectedErrorMessage)));
-        // Since the current provider is set to a fake package not an actual installed WebView
-        // provider, the UI shouldn't offer opening current WebView provider dev UI. It should not
-        // offer to change system WebView provider because this is not supported on pre-Nougat
-        // android versions.
-        onView(withId(R.id.action_button)).check(matches(not(isDisplayed())));
-    }
-
-    @Test
-    @MediumTest
-    @Feature({"AndroidWebView"})
-    // Test the dialog shown when the WebView package error message is clicked (where WebView
-    // provider can't be changed).
-    public void testDifferentWebViewPackageError_dialog_preNougat() throws Throwable {
-        Assume.assumeTrue("This test verifies pre-Nougat behavior",
-                Build.VERSION.SDK_INT < Build.VERSION_CODES.N);
-
-        Context context = InstrumentationRegistry.getTargetContext();
-        // Inject a dummy PackageInfo as the current WebView package to make sure it will always be
-        // different from the test's app package.
-        WebViewPackageHelper.setCurrentWebViewPackageForTesting(FAKE_WEBVIEW_PACKAGE);
-        launchHomeFragment();
-
-        String dialogExpectedMessage = String.format(Locale.US,
-                WebViewPackageError.DIFFERENT_WEBVIEW_PROVIDER_DIALOG_MESSAGE,
-                WebViewPackageHelper.loadLabel(context));
-        ViewUtils.waitForView(withId(R.id.main_error_view));
-        onView(withId(R.id.main_error_view)).perform(click());
-        ViewUtils.waitForView(withText(dialogExpectedMessage));
-        onView(withText(dialogExpectedMessage)).check(matches(isDisplayed()));
-        // Since the current provider is set to a fake package not an actual installed WebView
-        // provider, the UI shouldn't offer opening current WebView provider dev UI. It should not
-        // offer to change system WebView provider because this is not supported on pre-Nougat
-        // android versions.
-        //
-        // There should be no buttons in the Dialog.
-        onView(withId(android.R.id.button1)).check(matches(not(isDisplayed())));
-        onView(withId(android.R.id.button2)).check(matches(not(isDisplayed())));
-        onView(withId(android.R.id.button3)).check(matches(not(isDisplayed())));
-    }
 }
diff --git a/ash/ambient/util/ambient_util.h b/ash/ambient/util/ambient_util.h
index 054a171a..9292674 100644
--- a/ash/ambient/util/ambient_util.h
+++ b/ash/ambient/util/ambient_util.h
@@ -33,10 +33,12 @@
 // Ambient mode uses non-standard colors for some text and the media icon, so
 // provides a wrapper for |AshColorProvider::GetContentLayerColor|. This is
 // currently only supported for primary and secondary text and icons.
+// TODO(b/262012604) rework to use ui::ColorProvider.
 ASH_EXPORT SkColor
 GetContentLayerColor(AshColorProvider::ContentLayerType content_layer_type,
                      bool dark_mode_enable);
 // Version of the above that uses AshColorProvider::IsDarkModeEnabled().
+// TODO(b/262012604) rework to use ui::ColorProvider.
 ASH_EXPORT SkColor
 GetContentLayerColor(AshColorProvider::ContentLayerType content_layer_type);
 
diff --git a/ash/app_list/views/app_list_view_pixeltest.cc b/ash/app_list/views/app_list_view_pixeltest.cc
index 2dec915..103efa2 100644
--- a/ash/app_list/views/app_list_view_pixeltest.cc
+++ b/ash/app_list/views/app_list_view_pixeltest.cc
@@ -83,6 +83,8 @@
   SetUpAnswerCardResult(results, 1, 1);
   test_helper->GetProductivityLauncherSearchView()
       ->OnSearchResultContainerResultsChanged();
+  // OnSearchResultContainerResultsChanged will schedule show animations().
+  base::RunLoop().RunUntilIdle();
 
   HideCursor();
   EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
diff --git a/ash/metrics/login_unlock_throughput_recorder.cc b/ash/metrics/login_unlock_throughput_recorder.cc
index 9bc43c8..1fa227ace 100644
--- a/ash/metrics/login_unlock_throughput_recorder.cc
+++ b/ash/metrics/login_unlock_throughput_recorder.cc
@@ -28,6 +28,7 @@
 #include "ui/compositor/layer.h"
 #include "ui/compositor/total_animation_throughput_reporter.h"
 #include "ui/views/animation/bounds_animator.h"
+#include "ui/views/animation/bounds_animator_observer.h"
 
 namespace ash {
 namespace {
@@ -351,6 +352,32 @@
   ReportLogin(start, data);
 }
 
+void LoginUnlockThroughputRecorder::OnArcOptedIn() {
+  arc_opt_in_time_ = base::TimeTicks::Now();
+}
+
+void LoginUnlockThroughputRecorder::OnArcAppListReady() {
+  if (arc_app_list_ready_reported_)
+    return;
+
+  // |Ash.ArcAppInitialAppsInstallDuration| histogram is only reported for
+  // the first user session after they opted into the ARC++.
+  // |arc_opt_in_time_| will only have value if user opted in into the ARC++
+  // in this session (in this browser instance).
+  if (arc_opt_in_time_.has_value()) {
+    const auto duration = base::TimeTicks::Now() - arc_opt_in_time_.value();
+    UmaHistogramCustomTimes("Ash.ArcAppInitialAppsInstallDuration", duration,
+                            base::Seconds(1) /* min */,
+                            base::Hours(1) /* max */, 100 /* buckets */);
+  }
+
+  arc_app_list_ready_reported_ = true;
+}
+
+bool LoginUnlockThroughputRecorder::NeedReportArcAppListReady() const {
+  return arc_opt_in_time_.has_value() && !arc_app_list_ready_reported_;
+}
+
 void LoginUnlockThroughputRecorder::ScheduleWaitForShelfAnimationEnd() {
   ShelfView* shelf_view =
       RootWindowController::ForWindow(
diff --git a/ash/metrics/login_unlock_throughput_recorder.h b/ash/metrics/login_unlock_throughput_recorder.h
index 7925dba..46d3fe5 100644
--- a/ash/metrics/login_unlock_throughput_recorder.h
+++ b/ash/metrics/login_unlock_throughput_recorder.h
@@ -17,6 +17,7 @@
 #include "base/time/time.h"
 #include "cc/metrics/frame_sequence_metrics.h"
 #include "chromeos/ash/components/login/login_state/login_state.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/compositor/total_animation_throughput_reporter.h"
 
 namespace ui {
@@ -70,6 +71,16 @@
   // This is called when the list of shelf icons is updated.
   void UpdateShelfIconList(const ShelfModel* model);
 
+  // This is called when ARC++ becomes enabled.
+  void OnArcOptedIn();
+
+  // This is called when list of ARC++ apps is updated.
+  void OnArcAppListReady();
+
+  // This is true if we need to report Ash.ArcAppInitialAppsInstallDuration
+  // histogram in this session but it has not been reported yet.
+  bool NeedReportArcAppListReady() const;
+
   void ResetScopedThroughputReporterBlockerForTesting();
 
   const ui::TotalAnimationThroughputReporter*
@@ -137,6 +148,10 @@
 
   bool user_logged_in_ = false;
 
+  bool arc_app_list_ready_reported_ = false;
+
+  absl::optional<base::TimeTicks> arc_opt_in_time_;
+
   base::WeakPtr<ui::TotalAnimationThroughputReporter>
       login_animation_throughput_reporter_;
 
diff --git a/ash/projector/projector_controller_impl.cc b/ash/projector/projector_controller_impl.cc
index 51ef9c4..f670cc7c 100644
--- a/ash/projector/projector_controller_impl.cc
+++ b/ash/projector/projector_controller_impl.cc
@@ -507,7 +507,6 @@
   DCHECK(client_->GetSpeechRecognitionAvailability().IsAvailable());
 
   client_->StopSpeechRecognition();
-  is_speech_recognition_on_ = false;
 }
 
 void ProjectorControllerImpl::OnContainerFolderCreated(
diff --git a/ash/public/cpp/BUILD.gn b/ash/public/cpp/BUILD.gn
index e112fbd..8ca459cc 100644
--- a/ash/public/cpp/BUILD.gn
+++ b/ash/public/cpp/BUILD.gn
@@ -342,8 +342,8 @@
     "update_types.h",
     "view_shadow.cc",
     "view_shadow.h",
-    "views_text_services_context_menu_impl.cc",
-    "views_text_services_context_menu_impl.h",
+    "views_text_services_context_menu_ash.cc",
+    "views_text_services_context_menu_ash.h",
     "vm_camera_mic_constants.cc",
     "vm_camera_mic_constants.h",
     "wallpaper/google_photos_wallpaper_params.cc",
diff --git a/ash/public/cpp/views_text_services_context_menu_impl.cc b/ash/public/cpp/views_text_services_context_menu_ash.cc
similarity index 66%
rename from ash/public/cpp/views_text_services_context_menu_impl.cc
rename to ash/public/cpp/views_text_services_context_menu_ash.cc
index 74e87ca..fb262a8 100644
--- a/ash/public/cpp/views_text_services_context_menu_impl.cc
+++ b/ash/public/cpp/views_text_services_context_menu_ash.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "ash/public/cpp/views_text_services_context_menu_impl.h"
+#include "ash/public/cpp/views_text_services_context_menu_ash.h"
 
 #include "ash/public/cpp/clipboard_history_controller.h"
 #include "chromeos/crosapi/mojom/clipboard_history.mojom.h"
@@ -13,16 +13,25 @@
 
 namespace ash {
 
-ViewsTextServicesContextMenuImpl::ViewsTextServicesContextMenuImpl(
+ViewsTextServicesContextMenuAsh::ViewsTextServicesContextMenuAsh(
     ui::SimpleMenuModel* menu,
     views::Textfield* client)
     : views::ViewsTextServicesContextMenuBase(menu, client) {
-  AddClipboardHistoryMenuOption(menu);
+  // If the menu has a paste option, add a clipboard history option as well.
+  const absl::optional<size_t> paste_index =
+      menu->GetIndexOfCommandId(ui::TouchEditable::kPaste);
+
+  if (!paste_index.has_value())
+    return;
+
+  const size_t target_index = paste_index.value() + 1;
+  menu->InsertItemAt(target_index, IDS_APP_SHOW_CLIPBOARD_HISTORY,
+                     l10n_util::GetStringUTF16(IDS_APP_SHOW_CLIPBOARD_HISTORY));
 }
 
-ViewsTextServicesContextMenuImpl::~ViewsTextServicesContextMenuImpl() = default;
+ViewsTextServicesContextMenuAsh::~ViewsTextServicesContextMenuAsh() = default;
 
-bool ViewsTextServicesContextMenuImpl::GetAcceleratorForCommandId(
+bool ViewsTextServicesContextMenuAsh::GetAcceleratorForCommandId(
     int command_id,
     ui::Accelerator* accelerator) const {
   if (command_id == IDS_APP_SHOW_CLIPBOARD_HISTORY) {
@@ -34,35 +43,34 @@
       command_id, accelerator);
 }
 
-bool ViewsTextServicesContextMenuImpl::IsCommandIdChecked(
-    int command_id) const {
+bool ViewsTextServicesContextMenuAsh::IsCommandIdChecked(int command_id) const {
   if (command_id == IDS_APP_SHOW_CLIPBOARD_HISTORY)
     return true;
 
   return ViewsTextServicesContextMenuBase::IsCommandIdChecked(command_id);
 }
 
-bool ViewsTextServicesContextMenuImpl::IsCommandIdEnabled(
-    int command_id) const {
+bool ViewsTextServicesContextMenuAsh::IsCommandIdEnabled(int command_id) const {
   if (command_id == IDS_APP_SHOW_CLIPBOARD_HISTORY)
     return ClipboardHistoryController::Get()->CanShowMenu();
 
   return ViewsTextServicesContextMenuBase::IsCommandIdEnabled(command_id);
 }
 
-void ViewsTextServicesContextMenuImpl::ExecuteCommand(int command_id,
-                                                      int event_flags) {
+void ViewsTextServicesContextMenuAsh::ExecuteCommand(int command_id,
+                                                     int event_flags) {
   if (command_id == IDS_APP_SHOW_CLIPBOARD_HISTORY) {
     auto* clipboard_history_controller = ClipboardHistoryController::Get();
 
     // Calculate the menu source type from `event_flags`.
     ui::MenuSourceType source_type;
-    if (event_flags & ui::EF_LEFT_MOUSE_BUTTON)
+    if (event_flags & ui::EF_LEFT_MOUSE_BUTTON) {
       source_type = ui::MENU_SOURCE_MOUSE;
-    else if (event_flags & ui::EF_FROM_TOUCH)
+    } else if (event_flags & ui::EF_FROM_TOUCH) {
       source_type = ui::MENU_SOURCE_TOUCH;
-    else
+    } else {
       source_type = ui::MENU_SOURCE_KEYBOARD;
+    }
 
     clipboard_history_controller->ShowMenu(
         client()->GetCaretBounds(), source_type,
@@ -74,26 +82,11 @@
   ViewsTextServicesContextMenuBase::ExecuteCommand(command_id, event_flags);
 }
 
-bool ViewsTextServicesContextMenuImpl::SupportsCommand(int command_id) const {
+bool ViewsTextServicesContextMenuAsh::SupportsCommand(int command_id) const {
   if (command_id == IDS_APP_SHOW_CLIPBOARD_HISTORY)
     return true;
 
   return ViewsTextServicesContextMenuBase::SupportsCommand(command_id);
 }
 
-void ViewsTextServicesContextMenuImpl::AddClipboardHistoryMenuOption(
-    ui::SimpleMenuModel* menu) {
-  const absl::optional<size_t> index_of_paste =
-      menu->GetIndexOfCommandId(ui::TouchEditable::kPaste);
-
-  // Only add the clipboard history menu option when having the menu option
-  // for paste.
-  if (!index_of_paste.has_value())
-    return;
-
-  const size_t target_index = index_of_paste.value() + 1;
-  menu->InsertItemAt(target_index, IDS_APP_SHOW_CLIPBOARD_HISTORY,
-                     l10n_util::GetStringUTF16(IDS_APP_SHOW_CLIPBOARD_HISTORY));
-}
-
 }  // namespace ash
diff --git a/ash/public/cpp/views_text_services_context_menu_ash.h b/ash/public/cpp/views_text_services_context_menu_ash.h
new file mode 100644
index 0000000..b0b243fb
--- /dev/null
+++ b/ash/public/cpp/views_text_services_context_menu_ash.h
@@ -0,0 +1,42 @@
+// Copyright 2021 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_PUBLIC_CPP_VIEWS_TEXT_SERVICES_CONTEXT_MENU_ASH_H_
+#define ASH_PUBLIC_CPP_VIEWS_TEXT_SERVICES_CONTEXT_MENU_ASH_H_
+
+#include "ash/public/cpp/ash_public_export.h"
+#include "ui/views/controls/views_text_services_context_menu_base.h"
+
+namespace views {
+class Textfield;
+}
+
+namespace ash {
+
+// This class implements support for adding and handling text service items in
+// ChromeOS system UI textfields and ash-chrome browser native textfields (i.e.,
+// the omnibox but not the WebUI embedded in the browser).
+class ASH_PUBLIC_EXPORT ViewsTextServicesContextMenuAsh
+    : public views::ViewsTextServicesContextMenuBase {
+ public:
+  ViewsTextServicesContextMenuAsh(ui::SimpleMenuModel* menu,
+                                  views::Textfield* client);
+  ViewsTextServicesContextMenuAsh(const ViewsTextServicesContextMenuAsh&) =
+      delete;
+  ViewsTextServicesContextMenuAsh& operator=(
+      const ViewsTextServicesContextMenuAsh&) = delete;
+  ~ViewsTextServicesContextMenuAsh() override;
+
+  // ViewsTextServicesContextMenuBase:
+  bool GetAcceleratorForCommandId(int command_id,
+                                  ui::Accelerator* accelerator) const override;
+  bool IsCommandIdChecked(int command_id) const override;
+  bool IsCommandIdEnabled(int command_id) const override;
+  void ExecuteCommand(int command_id, int event_flags) override;
+  bool SupportsCommand(int command_id) const override;
+};
+
+}  // namespace ash
+
+#endif  // ASH_PUBLIC_CPP_VIEWS_TEXT_SERVICES_CONTEXT_MENU_ASH_H_
diff --git a/ash/public/cpp/views_text_services_context_menu_impl.h b/ash/public/cpp/views_text_services_context_menu_impl.h
deleted file mode 100644
index 8856872..0000000
--- a/ash/public/cpp/views_text_services_context_menu_impl.h
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2021 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef ASH_PUBLIC_CPP_VIEWS_TEXT_SERVICES_CONTEXT_MENU_IMPL_H_
-#define ASH_PUBLIC_CPP_VIEWS_TEXT_SERVICES_CONTEXT_MENU_IMPL_H_
-
-#include "ash/public/cpp/ash_public_export.h"
-#include "ui/views/controls/views_text_services_context_menu_base.h"
-
-namespace views {
-class Textfield;
-}
-
-namespace ash {
-
-// This class supports the text context menu with the exclusive functions under
-// the CrOS environment.
-class ASH_PUBLIC_EXPORT ViewsTextServicesContextMenuImpl
-    : public views::ViewsTextServicesContextMenuBase {
- public:
-  ViewsTextServicesContextMenuImpl(ui::SimpleMenuModel* menu,
-                                   views::Textfield* client);
-  ViewsTextServicesContextMenuImpl(const ViewsTextServicesContextMenuImpl&) =
-      delete;
-  ViewsTextServicesContextMenuImpl& operator=(
-      const ViewsTextServicesContextMenuImpl&) = delete;
-  ~ViewsTextServicesContextMenuImpl() override;
-
-  // ViewsTextServicesContextMenuBase:
-  bool GetAcceleratorForCommandId(int command_id,
-                                  ui::Accelerator* accelerator) const override;
-  bool IsCommandIdChecked(int command_id) const override;
-  bool IsCommandIdEnabled(int command_id) const override;
-  void ExecuteCommand(int command_id, int event_flags) override;
-  bool SupportsCommand(int command_id) const override;
-
- private:
-  // Adds the menu option which shows the clipboard history menu after
-  // activation.
-  void AddClipboardHistoryMenuOption(ui::SimpleMenuModel* menu);
-};
-
-}  // namespace ash
-
-#endif  // ASH_PUBLIC_CPP_VIEWS_TEXT_SERVICES_CONTEXT_MENU_IMPL_H_
diff --git a/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.cc b/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.cc
index 8670cbcb..6cbe14a 100644
--- a/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.cc
+++ b/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.cc
@@ -131,7 +131,9 @@
   // If this is a v1 pairing, we pass off the responsibility to the Bluetooth
   // pairing dialog, and will listen for the
   // BluetoothAdapter::Observer::DevicePairedChanged event before firing the
-  // |paired_callback|.
+  // |paired_callback|. V1 devices only support the "initial pairing" protocol,
+  // not the "retroactive" or "subsequent" pairing protocols, so only
+  // "initial pairing" metrics are emitted to here.
   if (device_->version().value() == DeviceFastPairVersion::kV1) {
     RecordInitialSuccessFunnelFlow(
         FastPairInitialSuccessFunnelEvent::kV1DeviceDetected);
@@ -479,8 +481,11 @@
   // be.
   if (!ShouldBeEnabledForLoginStatus(
           Shell::Get()->session_controller()->login_status())) {
-    RecordInitialSuccessFunnelFlow(
-        FastPairInitialSuccessFunnelEvent::kGuestModeDetected);
+    if (device_->protocol == Protocol::kFastPairInitial) {
+      RecordInitialSuccessFunnelFlow(
+          FastPairInitialSuccessFunnelEvent::kGuestModeDetected);
+    }
+
     QP_LOG(VERBOSE) << __func__ << ": No logged in user to save account key to";
     std::move(pairing_procedure_complete_).Run(device_);
     return;
@@ -538,8 +543,12 @@
     QP_LOG(INFO) << __func__
                  << ": Device is already saved, skipping write account key. "
                     "Pairing procedure complete.";
-    RecordInitialSuccessFunnelFlow(
-        FastPairInitialSuccessFunnelEvent::kDeviceAlreadyAssociatedToAccount);
+
+    if (device_->protocol == Protocol::kFastPairInitial) {
+      RecordInitialSuccessFunnelFlow(
+          FastPairInitialSuccessFunnelEvent::kDeviceAlreadyAssociatedToAccount);
+    }
+
     std::move(pairing_procedure_complete_).Run(device_);
     return;
   }
@@ -558,8 +567,11 @@
   RAND_bytes(account_key.data(), account_key.size());
   account_key[0] = 0x04;
 
-  RecordInitialSuccessFunnelFlow(
-      FastPairInitialSuccessFunnelEvent::kPreparingToWriteAccountKey);
+  if (device_->protocol == Protocol::kFastPairInitial) {
+    RecordInitialSuccessFunnelFlow(
+        FastPairInitialSuccessFunnelEvent::kPreparingToWriteAccountKey);
+  }
+
   fast_pair_gatt_service_client_->WriteAccountKey(
       account_key, fast_pair_handshake_->fast_pair_data_encryptor(),
       base::BindOnce(&FastPairPairerImpl::OnWriteAccountKey,
@@ -600,8 +612,12 @@
   QP_LOG(INFO)
       << __func__
       << ": Account key written to device. Pairing procedure complete.";
-  RecordInitialSuccessFunnelFlow(
-      FastPairInitialSuccessFunnelEvent::kAccountKeyWritten);
+
+  if (device_->protocol == Protocol::kFastPairInitial) {
+    RecordInitialSuccessFunnelFlow(
+        FastPairInitialSuccessFunnelEvent::kAccountKeyWritten);
+  }
+
   std::move(pairing_procedure_complete_).Run(device_);
 }
 
diff --git a/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl_unittest.cc b/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl_unittest.cc
index 52e76b9..1ef93c520 100644
--- a/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl_unittest.cc
+++ b/ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl_unittest.cc
@@ -2494,5 +2494,34 @@
   EXPECT_EQ(GetPairFailure(), PairFailure::kCreateBondTimeout);
 }
 
+TEST_F(FastPairPairerImplTest, RetroactiveNotLoggedToInitial) {
+  Login(user_manager::UserType::USER_TYPE_REGULAR);
+  fast_pair_repository_.SetOptInStatus(
+      nearby::fastpair::OptInStatus::STATUS_OPTED_OUT);
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      /*enabled_features=*/{features::kFastPairSavedDevices},
+      /*disabled_features=*/{features::kFastPairSavedDevicesStrictOptIn});
+  CreateMockDevice(DeviceFastPairVersion::kHigherThanV1,
+                   /*protocol=*/Protocol::kFastPairRetroactive);
+  // When pairing starts, if the classic address can't be resolved to
+  // a device then we pair via address.
+  SetGetDeviceNullptr();
+  CreatePairer();
+  fake_fast_pair_handshake_->InvokeCallback();
+  EXPECT_CALL(pairing_procedure_complete_, Run);
+  RunWriteAccountKeyCallback();
+  histogram_tester().ExpectTotalCount(
+      kWriteAccountKeyCharacteristicResultMetric, 1);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kInitialSuccessFunnelMetric,
+                FastPairInitialSuccessFunnelEvent::kPreparingToWriteAccountKey),
+            0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kInitialSuccessFunnelMetric,
+                FastPairInitialSuccessFunnelEvent::kAccountKeyWritten),
+            0);
+}
+
 }  // namespace quick_pair
 }  // namespace ash
diff --git a/ash/quick_pair/pairing/retroactive_pairing_detector_impl.cc b/ash/quick_pair/pairing/retroactive_pairing_detector_impl.cc
index a0813d76..5c53335 100644
--- a/ash/quick_pair/pairing/retroactive_pairing_detector_impl.cc
+++ b/ash/quick_pair/pairing/retroactive_pairing_detector_impl.cc
@@ -127,20 +127,18 @@
 
 void RetroactivePairingDetectorImpl::OnDevicePaired(
     scoped_refptr<Device> device) {
-  // When a device is paired to via Fast Pair, we save the device's classic
-  // pairing address here so when we get the the BluetoothAdapter's
-  // |DevicePairedChanged| fired, we can determine if it was the one we already
-  // have paired to. The classic address is assigned to the Device during the
-  // initial Fast Pair pairing protocol during the key exchange, and if it
-  // doesn't exist, then it wasn't properly paired during initial Fast Pair
+  // The classic address is assigned to the Device during the
+  // initial Fast Pair pairing protocol and if it doesn't exist,
+  // then it wasn't properly paired during initial Fast Pair
   // pairing.
   if (!device->classic_address())
     return;
 
-  // Sometimes we might encounter the case where |DevicePairedChanged| fires
-  // before FastPair's |OnDevicePaired|, and if that is the case and a device
-  // has been inserted in the |potential_retroactive_addresses_|, we need
-  // to remove it.
+  // The Bluetooth Adapter system event `DevicePairedChanged` fires before
+  // Fast Pair's `OnDevicePaired`, and a Fast Pair pairing is expected to have
+  // both events. If a device is Fast Paired, it is already inserted in the
+  // |potential_retroactive_addresses_| in `DevicePairedChanged`; we need to
+  // remove it to prevent a false positive.
   if (base::Contains(potential_retroactive_addresses_,
                      device->classic_address().value())) {
     QP_LOG(VERBOSE)
@@ -151,9 +149,6 @@
     RemoveDeviceInformation(device->classic_address().value());
     return;
   }
-
-  QP_LOG(INFO) << __func__ << ": Storing Fast Pair device address";
-  fast_pair_addresses_.insert(device->classic_address().value());
 }
 
 void RetroactivePairingDetectorImpl::DevicePairedChanged(
@@ -166,17 +161,19 @@
   // This event fires whenever a device pairing has changed with the adapter.
   // If the |new_paired_status| is false, it means a device was unpaired with
   // the adapter, so we early return since it would not be a device to
-  // retroactively pair to. If the device that was paired to that fires this
-  // event is a device we just paired to with Fast Pair, then we early return
-  // since it also wouldn't be one to retroactively pair to. We want to only
-  // continue our check here if we have a newly paired device that was paired
-  // with classic Bluetooth pairing.
-  const std::string& classic_address = device->GetAddress();
-  if (!new_paired_status ||
-      base::Contains(fast_pair_addresses_, classic_address)) {
+  // retroactively pair to.
+  if (!new_paired_status) {
     return;
   }
 
+  // Both classic paired and Fast paired devices call this function, so we
+  // have to add the device to |potential_retroactive_addresses_|. We expect
+  // devices paired via Fast Pair to always call `OnDevicePaired` after calling
+  // this function, which will remove the device from
+  // |potential_retroactive_addresses_|.
+  const std::string& classic_address = device->GetAddress();
+  potential_retroactive_addresses_.insert(classic_address);
+
   // In order to confirm that this device is a retroactive pairing, we need to
   // first check if it has already been saved to the user's account. If it has
   // already been saved, we don't want to prompt the user to save a device
@@ -192,18 +189,12 @@
     bool is_device_saved_to_account) {
   if (is_device_saved_to_account) {
     QP_LOG(INFO) << __func__ << ": device already saved to user's account";
+    RemoveDeviceInformation(classic_address);
     return;
   }
 
   QP_LOG(VERBOSE) << __func__ << ": device = " << classic_address;
 
-  // The device pairing just changed, and this means that it was just
-  // classically paired. Because we have now verified that it not saved to the
-  // user's account, we can continue verifying this device for the retroactive
-  // pairing scenario by checking if the Message Stream contains the model id
-  // and ble address.
-  potential_retroactive_addresses_.insert(classic_address);
-
   // Attempt to retrieve a MessageStream instance immediately, if it was
   // already connected.
   MessageStream* message_stream =
diff --git a/ash/quick_pair/pairing/retroactive_pairing_detector_impl.h b/ash/quick_pair/pairing/retroactive_pairing_detector_impl.h
index 74212eb..67b038a 100644
--- a/ash/quick_pair/pairing/retroactive_pairing_detector_impl.h
+++ b/ash/quick_pair/pairing/retroactive_pairing_detector_impl.h
@@ -133,10 +133,6 @@
 
   void RemoveDeviceInformation(const std::string& device_address);
 
-  // The classic pairing addresses of Fast Pair devices that we have already
-  // paired to.
-  base::flat_set<std::string> fast_pair_addresses_;
-
   // The classic pairing addresses of potential Retroactive Pair supported
   // devices that are found in the adapter. We have to store them and wait for a
   // MessageStream instance to be created for the device in order to fully
diff --git a/ash/quick_pair/pairing/retroactive_pairing_detector_unittest.cc b/ash/quick_pair/pairing/retroactive_pairing_detector_unittest.cc
index ac04d4f1..2b2ff53 100644
--- a/ash/quick_pair/pairing/retroactive_pairing_detector_unittest.cc
+++ b/ash/quick_pair/pairing/retroactive_pairing_detector_unittest.cc
@@ -240,22 +240,6 @@
   std::unique_ptr<RetroactivePairingDetector> retroactive_pairing_detector_;
 };
 
-TEST_F(RetroactivePairingDetectorTest, DevicedPaired_FastPair) {
-  Login(user_manager::UserType::USER_TYPE_REGULAR);
-  fast_pair_repository_.SetOptInStatus(
-      nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
-  base::RunLoop().RunUntilIdle();
-  CreateRetroactivePairingDetector();
-
-  EXPECT_FALSE(retroactive_pair_found_);
-
-  PairFastPairDeviceWithFastPair(kTestDeviceAddress);
-  PairFastPairDeviceWithClassicBluetooth(
-      /*new_paired_status=*/true, kTestDeviceAddress);
-
-  EXPECT_FALSE(retroactive_pair_found_);
-}
-
 TEST_F(RetroactivePairingDetectorTest,
        DevicedPaired_FastPair_BluetoothEventFiresFirst) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
@@ -273,6 +257,46 @@
   EXPECT_FALSE(retroactive_pair_found_);
 }
 
+// Regression test for b/261041950
+TEST_F(RetroactivePairingDetectorTest,
+       FastPairPairingEventCalledDuringBluetoothAdapterPairingEvent) {
+  Login(user_manager::UserType::USER_TYPE_REGULAR);
+  fast_pair_repository_.SetOptInStatus(
+      nearby::fastpair::OptInStatus::STATUS_OPTED_IN);
+  CreateRetroactivePairingDetector();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+
+  SetMessageStream(kModelIdBleAddressBytes);
+
+  // Simulate the Bluetooth Adapter event firing, with the callback to
+  // `IsDeviceSavedToAccount` delayed.
+  fast_pair_repository_.SetIsDeviceSavedToAccountCallbackDelayed(
+      /*is_delayed=*/true);
+  PairFastPairDeviceWithClassicBluetooth(
+      /*new_paired_status=*/true, kTestDeviceAddress);
+
+  // Simulate the Fast Pair pairing event firing during the Bluetooth Adapter
+  // pairing event call stack. The Bluetooth Adapter system
+  // event response has not finished completing because of the delay set in
+  // `SetIsDeviceSavedToAccountCallbackDelayed`.
+  PairFastPairDeviceWithFastPair(kTestDeviceAddress);
+
+  // Trigger the callback to check the repository after the Fast Pair pairing
+  // event fires. This will conclude the BluetoothAdapter pairing event call
+  // stack.
+  fast_pair_repository_.TriggerIsDeviceSavedToAccountCallback();
+
+  // Simulate data being received via Message Stream for the device. It should
+  // not be detected since the Fast Pair event has been fired, removing it
+  // as a possible retroactive device.
+  fake_socket_->TriggerReceiveCallback();
+  NotifyMessageStreamConnected(kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_FALSE(retroactive_pair_found_);
+}
+
 TEST_F(RetroactivePairingDetectorTest, DeviceUnpaired) {
   Login(user_manager::UserType::USER_TYPE_REGULAR);
   fast_pair_repository_.SetOptInStatus(
diff --git a/ash/quick_pair/repository/fake_fast_pair_repository.cc b/ash/quick_pair/repository/fake_fast_pair_repository.cc
index ca1d6bb..1d5e0e3 100644
--- a/ash/quick_pair/repository/fake_fast_pair_repository.cc
+++ b/ash/quick_pair/repository/fake_fast_pair_repository.cc
@@ -178,6 +178,11 @@
 void FakeFastPairRepository::IsDeviceSavedToAccount(
     const std::string& mac_address,
     IsDeviceSavedToAccountCallback callback) {
+  if (saved_to_account_callback_is_delayed_) {
+    saved_to_account_callback_ = std::move(callback);
+    return;
+  }
+
   if (base::Contains(saved_mac_addresses_, mac_address)) {
     std::move(callback).Run(true);
     return;
@@ -186,5 +191,9 @@
   std::move(callback).Run(false);
 }
 
+void FakeFastPairRepository::TriggerIsDeviceSavedToAccountCallback() {
+  std::move(saved_to_account_callback_).Run(false);
+}
+
 }  // namespace quick_pair
 }  // namespace ash
diff --git a/ash/quick_pair/repository/fake_fast_pair_repository.h b/ash/quick_pair/repository/fake_fast_pair_repository.h
index 8d042e6..e055d23 100644
--- a/ash/quick_pair/repository/fake_fast_pair_repository.h
+++ b/ash/quick_pair/repository/fake_fast_pair_repository.h
@@ -89,9 +89,19 @@
   void IsDeviceSavedToAccount(const std::string& mac_address,
                               IsDeviceSavedToAccountCallback callback) override;
 
+  // `SetIsDeviceSavedToAccountCallbackDelay` and
+  // `TriggerIsDeviceSavedToAccountCallback` are used together to control when
+  // the callback is triggered.
+  void TriggerIsDeviceSavedToAccountCallback();
+  void SetIsDeviceSavedToAccountCallbackDelayed(bool is_delayed) {
+    saved_to_account_callback_is_delayed_ = is_delayed;
+  }
+
  private:
   static void SetInstance(FastPairRepository* instance);
 
+  IsDeviceSavedToAccountCallback saved_to_account_callback_;
+  bool saved_to_account_callback_is_delayed_ = false;
   nearby::fastpair::OptInStatus status_ =
       nearby::fastpair::OptInStatus::STATUS_UNKNOWN;
   std::vector<nearby::fastpair::FastPairDevice> devices_;
diff --git a/ash/shelf/swipe_home_to_overview_controller_unittest.cc b/ash/shelf/swipe_home_to_overview_controller_unittest.cc
index 668179d..f81f9914 100644
--- a/ash/shelf/swipe_home_to_overview_controller_unittest.cc
+++ b/ash/shelf/swipe_home_to_overview_controller_unittest.cc
@@ -24,9 +24,11 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/simple_test_tick_clock.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
+#include "ui/compositor/compositor.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/layer_animator.h"
 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
+#include "ui/compositor/test/layer_animation_stopped_waiter.h"
 #include "ui/compositor/test/test_utils.h"
 #include "ui/gfx/geometry/point_f.h"
 #include "ui/gfx/geometry/rect_f.h"
@@ -106,17 +108,12 @@
   }
 
   void WaitForHomeLauncherAnimationToFinish() {
-    auto* compositor =
-        Shell::GetPrimaryRootWindowController()->GetHost()->compositor();
-    // Wait until home launcher animation finishes.
-    while (GetAppListTestHelper()
-               ->GetAppListView()
-               ->GetWidget()
-               ->GetLayer()
-               ->GetAnimator()
-               ->is_animating()) {
-      EXPECT_TRUE(ui::WaitForNextFrameToBePresented(compositor));
-    }
+    ui::LayerAnimationStoppedWaiter animation_waiter;
+    ui::Layer* app_list_layer =
+        GetAppListTestHelper()->GetAppListView()->GetWidget()->GetLayer();
+    animation_waiter.Wait(app_list_layer);
+
+    ui::Compositor* compositor = app_list_layer->GetCompositor();
 
     // Ensure there is one more frame presented after animation finishes
     // to allow animation throughput data is passed from cc to ui.
diff --git a/ash/shell.cc b/ash/shell.cc
index dfdabe87..5a3baa3 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -100,7 +100,7 @@
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/public/cpp/system_sounds_delegate.h"
 #include "ash/public/cpp/tab_cluster/tab_cluster_ui_controller.h"
-#include "ash/public/cpp/views_text_services_context_menu_impl.h"
+#include "ash/public/cpp/views_text_services_context_menu_ash.h"
 #include "ash/quick_pair/keyed_service/quick_pair_mediator.h"
 #include "ash/rgb_keyboard/rgb_keyboard_manager.h"
 #include "ash/root_window_controller.h"
@@ -1536,8 +1536,8 @@
       base::BindRepeating(
           [](ui::SimpleMenuModel* menu_model, views::Textfield* textfield)
               -> std::unique_ptr<views::ViewsTextServicesContextMenu> {
-            return std::make_unique<ViewsTextServicesContextMenuImpl>(
-                menu_model, textfield);
+            return std::make_unique<ViewsTextServicesContextMenuAsh>(menu_model,
+                                                                     textfield);
           }));
 
   for (auto& observer : shell_observers_)
diff --git a/ash/style/dark_light_mode_controller_impl.cc b/ash/style/dark_light_mode_controller_impl.cc
index 56a15eb..4b2c9b0 100644
--- a/ash/style/dark_light_mode_controller_impl.cc
+++ b/ash/style/dark_light_mode_controller_impl.cc
@@ -134,6 +134,7 @@
                                         !IsDarkModeEnabled());
   active_user_pref_service_->CommitPendingWrite();
   NotifyColorModeChanges();
+  SystemNudgeController::RecordNudgeAction(NudgeCatalogName::kDarkLightMode);
 
   // Updates showing logic of educational nudge on toggling the entry points of
   // dark/light mode.
diff --git a/ash/system/camera/autozoom_controller_impl.cc b/ash/system/camera/autozoom_controller_impl.cc
index dfdff6b..b3ace6a 100644
--- a/ash/system/camera/autozoom_controller_impl.cc
+++ b/ash/system/camera/autozoom_controller_impl.cc
@@ -66,6 +66,7 @@
   SetState(state_ == cros::mojom::CameraAutoFramingState::OFF
                ? cros::mojom::CameraAutoFramingState::ON_SINGLE
                : cros::mojom::CameraAutoFramingState::OFF);
+  SystemNudgeController::RecordNudgeAction(NudgeCatalogName::kAutozoom);
 }
 
 void AutozoomControllerImpl::AddObserver(AutozoomObserver* observer) {
diff --git a/ash/system/do_not_disturb_notification_controller.cc b/ash/system/do_not_disturb_notification_controller.cc
index fed5bde5..8a40391 100644
--- a/ash/system/do_not_disturb_notification_controller.cc
+++ b/ash/system/do_not_disturb_notification_controller.cc
@@ -22,7 +22,6 @@
 
 using message_center::MessageCenter;
 
-const char kDoNotDisturbNotificationId[] = "do_not_disturb";
 const char kDoNotDisturbNotifierId[] =
     "ash.do_not_disturb_notification_controller";
 
@@ -36,6 +35,10 @@
   MessageCenter::Get()->RemoveObserver(this);
 }
 
+// static
+const char DoNotDisturbNotificationController::kDoNotDisturbNotificationId[] =
+    "do_not_disturb";
+
 std::unique_ptr<message_center::Notification>
 DoNotDisturbNotificationController::CreateNotification() {
   message_center::RichNotificationData optional_fields;
diff --git a/ash/system/do_not_disturb_notification_controller.h b/ash/system/do_not_disturb_notification_controller.h
index d4e513f8..a52232a 100644
--- a/ash/system/do_not_disturb_notification_controller.h
+++ b/ash/system/do_not_disturb_notification_controller.h
@@ -30,6 +30,8 @@
 
   ~DoNotDisturbNotificationController() override;
 
+  static const char kDoNotDisturbNotificationId[];
+
   // message_center::MessageCenterObserver:
   void OnQuietModeChanged(bool in_quiet_mode) override;
 
diff --git a/ash/system/do_not_disturb_notification_controller_unittest.cc b/ash/system/do_not_disturb_notification_controller_unittest.cc
index 5a5b207..bb0051a 100644
--- a/ash/system/do_not_disturb_notification_controller_unittest.cc
+++ b/ash/system/do_not_disturb_notification_controller_unittest.cc
@@ -15,8 +15,6 @@
 
 using message_center::MessageCenter;
 
-const char kDoNotDisturbNotificationId[] = "do_not_disturb";
-
 }  // namespace
 
 class DoNotDisturbNotificationControllerTest
@@ -42,7 +40,7 @@
 
   bool IsDoNotDisturbNotificationPresent() {
     return MessageCenter::Get()->FindNotificationById(
-        kDoNotDisturbNotificationId);
+        DoNotDisturbNotificationController::kDoNotDisturbNotificationId);
   }
 
  private:
@@ -87,8 +85,8 @@
   ASSERT_TRUE(IsDoNotDisturbNotificationPresent());
 
   // Simulate a click on the notification's "Turn off" button.
-  auto* notification =
-      message_center->FindNotificationById(kDoNotDisturbNotificationId);
+  auto* notification = message_center->FindNotificationById(
+      DoNotDisturbNotificationController::kDoNotDisturbNotificationId);
   notification->delegate()->Click(0, absl::nullopt);
   EXPECT_FALSE(IsDoNotDisturbNotificationPresent());
   EXPECT_FALSE(message_center->IsQuietMode());
diff --git a/ash/system/message_center/session_state_notification_blocker.cc b/ash/system/message_center/session_state_notification_blocker.cc
index 9c5e130..eb87c26 100644
--- a/ash/system/message_center/session_state_notification_blocker.cc
+++ b/ash/system/message_center/session_state_notification_blocker.cc
@@ -6,12 +6,11 @@
 
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
-#include "ash/system/message_center/ash_message_center_lock_screen_controller.h"
+#include "ash/system/do_not_disturb_notification_controller.h"
 #include "ash/system/power/battery_notification.h"
 #include "base/containers/contains.h"
 #include "ui/message_center/message_center.h"
 #include "ui/message_center/public/cpp/notification.h"
-#include "ui/message_center/public/cpp/notification_types.h"
 #include "ui/message_center/public/cpp/notifier_id.h"
 
 using session_manager::SessionState;
@@ -96,6 +95,15 @@
   if (Shell::Get()->session_controller()->IsRunningInAppMode())
     return false;
 
+  // Do not show the "Do not disturb" notification if there is no active
+  // session.
+  if (notification.id() ==
+          DoNotDisturbNotificationController::kDoNotDisturbNotificationId &&
+      Shell::Get()->session_controller()->GetSessionState() !=
+          SessionState::ACTIVE) {
+    return false;
+  }
+
   if (notification.id() == BatteryNotification::kNotificationId)
     return true;
 
diff --git a/ash/system/message_center/session_state_notification_blocker_unittest.cc b/ash/system/message_center/session_state_notification_blocker_unittest.cc
index 2c9ab2a..d65199dd 100644
--- a/ash/system/message_center/session_state_notification_blocker_unittest.cc
+++ b/ash/system/message_center/session_state_notification_blocker_unittest.cc
@@ -9,9 +9,9 @@
 #include "ash/constants/ash_features.h"
 #include "ash/constants/notifier_catalogs.h"
 #include "ash/session/test_session_controller_client.h"
+#include "ash/system/do_not_disturb_notification_controller.h"
 #include "ash/system/power/battery_notification.h"
 #include "ash/test/ash_test_base.h"
-#include "base/command_line.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_feature_list.h"
 #include "ui/message_center/message_center.h"
@@ -95,6 +95,19 @@
     return blocker_->ShouldShowNotificationAsPopup(notification);
   }
 
+  bool ShouldShowDoNotDisturbNotification() {
+    message_center::Notification notification(
+        message_center::NOTIFICATION_TYPE_SIMPLE,
+        DoNotDisturbNotificationController::kDoNotDisturbNotificationId,
+        u"chromeos-title", u"chromeos-message", ui::ImageModel(),
+        u"chromeos-source", GURL(),
+        message_center::NotifierId(
+            message_center::NotifierType::SYSTEM_COMPONENT, "test-notifier",
+            NotificationCatalogName::kDoNotDisturb),
+        message_center::RichNotificationData(), nullptr);
+    return blocker_->ShouldShowNotification(notification);
+  }
+
   void SetLockedState(bool locked) {
     GetSessionControllerClient()->SetSessionState(
         locked ? SessionState::LOCKED : SessionState::ACTIVE);
@@ -261,5 +274,27 @@
   SessionStateNotificationBlocker::SetUseLoginNotificationDelayForTest(false);
 }
 
+TEST_P(SessionStateNotificationBlockerTest, DoNotDisturbNotification) {
+  // OOBE.
+  GetSessionControllerClient()->SetSessionState(SessionState::OOBE);
+  EXPECT_FALSE(ShouldShowDoNotDisturbNotification());
+
+  // Login screen.
+  GetSessionControllerClient()->SetSessionState(SessionState::LOGIN_PRIMARY);
+  EXPECT_FALSE(ShouldShowDoNotDisturbNotification());
+
+  // Logged in as a normal user.
+  SimulateUserLogin("user@test.com");
+  EXPECT_TRUE(ShouldShowDoNotDisturbNotification());
+
+  // Lock.
+  SetLockedState(true);
+  EXPECT_FALSE(ShouldShowDoNotDisturbNotification());
+
+  // Unlock.
+  SetLockedState(false);
+  EXPECT_TRUE(ShouldShowDoNotDisturbNotification());
+}
+
 }  // namespace
 }  // namespace ash
diff --git a/ash/system/microphone_mute/microphone_mute_notification_controller.cc b/ash/system/microphone_mute/microphone_mute_notification_controller.cc
index 7a14527..f90baeb 100644
--- a/ash/system/microphone_mute/microphone_mute_notification_controller.cc
+++ b/ash/system/microphone_mute/microphone_mute_notification_controller.cc
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "ash/constants/notifier_catalogs.h"
+#include "ash/public/cpp/new_window_delegate.h"
 #include "ash/public/cpp/notification_utils.h"
 #include "ash/public/cpp/sensor_disabled_notification_delegate.h"
 #include "ash/shell.h"
@@ -21,10 +22,14 @@
 #include "components/vector_icons/vector_icons.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/message_center/message_center.h"
+#include "ui/message_center/public/cpp/notification_delegate.h"
 
 namespace ash {
 namespace {
 
+// TODO(b/244529735): Replace the generic support URL with the final one.
+const char kLearnMoreUrl[] = "https://www.support.google.com/chromebook";
+
 void SetMicrophoneNotificationVisible(const bool visible) {
   PrivacyHubNotificationController* const privacy_hub_notification_controller =
       Shell::Get()->system_notification_controller()->privacy_hub();
@@ -132,12 +137,30 @@
   notification_data.remove_on_click = true;
 
   scoped_refptr<message_center::NotificationDelegate> delegate;
-  // Don't show a button to unmute device if the microphone was muted by a HW
-  // mute switch, as in that case unmute action would not work.
-  if (!mic_muted_by_mute_switch_) {
+
+  if (mic_muted_by_mute_switch_) {
+    // If microphone is muted by the hardware(HW) switch, show the 'Learn more'
+    // button, pointing to the instructions how to unmute the system (unmute
+    // can't be done programmatically).
+    notification_data.buttons.emplace_back(
+        l10n_util::GetStringUTF16(IDS_ASH_LEARN_MORE));
+    delegate =
+        base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
+            base::BindRepeating([](absl::optional<int> button_index) {
+              if (!button_index) {
+                return;
+              }
+
+              NewWindowDelegate::GetPrimary()->OpenUrl(
+                  GURL(kLearnMoreUrl),
+                  NewWindowDelegate::OpenUrlFrom::kUserInteraction,
+                  NewWindowDelegate::Disposition::kNewForegroundTab);
+            }));
+  } else {
+    // If microphone is muted by the software(SW) switch, add the unmute
+    // button to the notification.
     notification_data.buttons.emplace_back(l10n_util::GetStringUTF16(
         IDS_MICROPHONE_MUTED_NOTIFICATION_ACTION_BUTTON));
-
     delegate =
         base::MakeRefCounted<message_center::HandleNotificationClickDelegate>(
             base::BindRepeating([](absl::optional<int> button_index) {
diff --git a/ash/system/microphone_mute/microphone_mute_notification_controller_unittest.cc b/ash/system/microphone_mute/microphone_mute_notification_controller_unittest.cc
index aff8c4d2..8c96c27 100644
--- a/ash/system/microphone_mute/microphone_mute_notification_controller_unittest.cc
+++ b/ash/system/microphone_mute/microphone_mute_notification_controller_unittest.cc
@@ -8,6 +8,7 @@
 
 #include "ash/constants/ash_features.h"
 #include "ash/public/cpp/sensor_disabled_notification_delegate.h"
+#include "ash/public/cpp/test/test_new_window_delegate.h"
 #include "ash/public/cpp/test/test_system_tray_client.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/system/microphone_mute/microphone_mute_notification_controller.h"
@@ -17,8 +18,12 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
+#include "chromeos/ash/components/audio/cras_audio_handler.h"
+#include "chromeos/ash/components/dbus/audio/cras_audio_client.h"
 #include "chromeos/ash/components/dbus/audio/fake_cras_audio_client.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
+#include "ui/aura/window.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/message_center/message_center.h"
 #include "ui/message_center/public/cpp/notification.h"
@@ -26,6 +31,8 @@
 
 namespace ash {
 
+namespace {
+
 class FakeSensorDisabledNotificationDelegate
     : public SensorDisabledNotificationDelegate {
  public:
@@ -48,16 +55,32 @@
   std::vector<std::u16string> apps_accessing_microphone_;
 };
 
+class MockNewWindowDelegate : public testing::NiceMock<TestNewWindowDelegate> {
+ public:
+  // TestNewWindowDelegate:
+  MOCK_METHOD(void,
+              OpenUrl,
+              (const GURL& url, OpenUrlFrom from, Disposition disposition),
+              (override));
+};
+
+}  // namespace
+
 class MicrophoneMuteNotificationControllerTest : public AshTestBase {
  public:
   MicrophoneMuteNotificationControllerTest() {
     scoped_feature_list_.InitAndEnableFeature(features::kMicMuteNotifications);
+    auto delegate = std::make_unique<MockNewWindowDelegate>();
+    new_window_delegate_ = delegate.get();
+    window_delegate_provider_ =
+        std::make_unique<TestNewWindowDelegateProvider>(std::move(delegate));
   }
   ~MicrophoneMuteNotificationControllerTest() override = default;
 
   // AshTestBase:
   void SetUp() override {
     AshTestBase::SetUp();
+
     controller_ = std::make_unique<MicrophoneMuteNotificationController>();
     delegate_ = std::make_unique<FakeSensorDisabledNotificationDelegate>();
   }
@@ -137,11 +160,15 @@
     return histogram_tester_;
   }
 
+  MockNewWindowDelegate& new_window_delegate() { return *new_window_delegate_; }
+
  private:
   const base::HistogramTester histogram_tester_;
   base::test::ScopedFeatureList scoped_feature_list_;
   std::unique_ptr<MicrophoneMuteNotificationController> controller_;
   std::unique_ptr<FakeSensorDisabledNotificationDelegate> delegate_;
+  MockNewWindowDelegate* new_window_delegate_ = nullptr;
+  std::unique_ptr<TestNewWindowDelegateProvider> window_delegate_provider_;
 };
 
 TEST_F(MicrophoneMuteNotificationControllerTest, SimpleMuteUnMute) {
@@ -269,7 +296,8 @@
   EXPECT_FALSE(GetNotification());
 }
 
-TEST_F(MicrophoneMuteNotificationControllerTest, MuteNotificationActionButton) {
+TEST_F(MicrophoneMuteNotificationControllerTest,
+       SwMuteNotificationActionButton) {
   MuteMicrophone();
   LaunchApp(u"junior");
   SetNumberOfActiveInputStreams(1);
@@ -296,7 +324,7 @@
             1);
 }
 
-TEST_F(MicrophoneMuteNotificationControllerTest, MuteNotificationActionBody) {
+TEST_F(MicrophoneMuteNotificationControllerTest, SwMuteNotificationActionBody) {
   MuteMicrophone();
   LaunchApp(u"junior");
   SetNumberOfActiveInputStreams(1);
@@ -325,23 +353,45 @@
 }
 
 TEST_F(MicrophoneMuteNotificationControllerTest,
-       NoNotificationActionButtonIfMutedByHwSwitch) {
+       HwMuteNotificationActionButton) {
   SetMicrophoneMuteSwitchState(/*muted=*/true);
 
   LaunchApp(u"junior");
   SetNumberOfActiveInputStreams(1);
 
-  // The mute notification should not have an action button if device is muted
-  // by a mute switch.
+  // The mute notification should have a "Learn more" button.
   message_center::Notification* notification = GetNotification();
   ASSERT_TRUE(notification);
-  EXPECT_EQ(0u, notification->buttons().size());
+  EXPECT_EQ(1u, notification->buttons().size());
+
+  // Clicking the "Learn more" button should open a new Chrome tab with the
+  // support link.
+  EXPECT_CALL(new_window_delegate(), OpenUrl).Times(1);
+  ClickOnNotificationButton();
+
+  EXPECT_TRUE(chromeos::CrasAudioHandler::Get()->IsInputMuted());
 
   SetMicrophoneMuteSwitchState(/*muted=*/false);
   ASSERT_FALSE(chromeos::CrasAudioHandler::Get()->IsInputMuted());
   EXPECT_FALSE(GetNotification());
 }
 
+TEST_F(MicrophoneMuteNotificationControllerTest, HwMuteNotificationActionBody) {
+  SetMicrophoneMuteSwitchState(/*muted=*/true);
+  LaunchApp(u"junior");
+  SetNumberOfActiveInputStreams(1);
+
+  message_center::Notification* notification = GetNotification();
+  ASSERT_TRUE(notification);
+  EXPECT_EQ(1u, notification->buttons().size());
+
+  ClickOnNotificationBody();
+
+  // Check that clicking the body has no effect and notification disappears.
+  EXPECT_TRUE(chromeos::CrasAudioHandler::Get()->IsInputMuted());
+  EXPECT_FALSE(GetNotification());
+}
+
 TEST_F(MicrophoneMuteNotificationControllerTest,
        TogglingMuteSwitchRemovesNotificationActionButton) {
   // Mute microphone, and activate an audio input stream.
@@ -353,12 +403,18 @@
   message_center::Notification* notification = GetNotification();
   ASSERT_TRUE(notification);
   EXPECT_EQ(1u, notification->buttons().size());
+  EXPECT_EQ(l10n_util::GetStringUTF16(
+                IDS_MICROPHONE_MUTED_NOTIFICATION_ACTION_BUTTON),
+            notification->buttons()[0].title);
 
-  // Toggle microphone mute switch and verify the action button disappears.
+  // Toggle microphone mute switch and verify that new notification appears with
+  // a "Learn more" button.
   SetMicrophoneMuteSwitchState(/*muted=*/true);
   notification = GetNotification();
   ASSERT_TRUE(notification);
-  EXPECT_EQ(0u, notification->buttons().size());
+  EXPECT_EQ(1u, notification->buttons().size());
+  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_ASH_LEARN_MORE),
+            notification->buttons()[0].title);
 
   SetMicrophoneMuteSwitchState(/*muted=*/false);
   ASSERT_FALSE(chromeos::CrasAudioHandler::Get()->IsInputMuted());
diff --git a/ash/system/notification_center/notification_center_test_api.cc b/ash/system/notification_center/notification_center_test_api.cc
index 07d14d64..5088263b 100644
--- a/ash/system/notification_center/notification_center_test_api.cc
+++ b/ash/system/notification_center/notification_center_test_api.cc
@@ -14,6 +14,7 @@
 #include "ash/system/notification_center/notification_center_view.h"
 #include "ash/system/notification_center/notification_list_view.h"
 #include "ash/system/notification_center/stacked_notification_bar.h"
+#include "ash/system/unified/notification_counter_view.h"
 #include "ash/system/unified/unified_system_tray.h"
 #include "base/strings/string_number_conversions.h"
 #include "ui/base/models/image_model.h"
@@ -84,6 +85,12 @@
   return notification_center_tray_->GetVisible();
 }
 
+bool NotificationCenterTestApi::IsDoNotDisturbIconShown() {
+  return notification_center_tray_->notification_icons_controller_
+      ->quiet_mode_view()
+      ->GetVisible();
+}
+
 views::View* NotificationCenterTestApi::GetNotificationViewForId(
     const std::string& id) {
   // Ensure this api is only called when the notification list view exists, i.e.
diff --git a/ash/system/notification_center/notification_center_test_api.h b/ash/system/notification_center/notification_center_test_api.h
index b9573d6..9c2fe57 100644
--- a/ash/system/notification_center/notification_center_test_api.h
+++ b/ash/system/notification_center/notification_center_test_api.h
@@ -61,10 +61,14 @@
   // otherwise.
   bool IsPopupShown(const std::string& id);
 
-  // Returns true if `NotificationCenterTray`` is showing in the shelf, false
+  // Returns true if `NotificationCenterTray` is showing in the shelf, false
   // otherwise.
   bool IsTrayShown();
 
+  // Returns true if `QuietModeView` is showing in the `NotificationCenterTray`,
+  // false otherwise.
+  bool IsDoNotDisturbIconShown();
+
   // Returns the notification view associated with the provided notification id.
   // Should be only used when the notifications bubble is open.
   views::View* GetNotificationViewForId(const std::string& id);
diff --git a/ash/system/notification_center/notification_center_tray.h b/ash/system/notification_center/notification_center_tray.h
index d5324ec..be8560b 100644
--- a/ash/system/notification_center/notification_center_tray.h
+++ b/ash/system/notification_center/notification_center_tray.h
@@ -13,7 +13,6 @@
 #include "ash/system/tray/tray_background_view.h"
 #include "ash/system/unified/notification_icons_controller.h"
 #include "ui/base/metadata/metadata_header_macros.h"
-#include "ui/message_center/message_center.h"
 #include "ui/message_center/message_center_observer.h"
 #include "ui/message_center/message_center_types.h"
 
diff --git a/ash/system/notification_center/notification_center_tray_unittest.cc b/ash/system/notification_center/notification_center_tray_unittest.cc
index 4f0ea38..9d4366d2 100644
--- a/ash/system/notification_center/notification_center_tray_unittest.cc
+++ b/ash/system/notification_center/notification_center_tray_unittest.cc
@@ -199,6 +199,31 @@
   EXPECT_FALSE(test_api()->IsBubbleShown());
 }
 
+// Tests that visibility of the Do not disturb icon changes with Do not disturb
+// mode.
+TEST_F(NotificationCenterTrayTest, DoNotDisturbIconVisibility) {
+  // Test the case where the tray is not initially visible.
+  ASSERT_FALSE(test_api()->IsTrayShown());
+  EXPECT_FALSE(test_api()->IsDoNotDisturbIconShown());
+  message_center::MessageCenter::Get()->SetQuietMode(true);
+  EXPECT_TRUE(test_api()->IsTrayShown());
+  EXPECT_TRUE(test_api()->IsDoNotDisturbIconShown());
+  message_center::MessageCenter::Get()->SetQuietMode(false);
+  EXPECT_FALSE(test_api()->IsTrayShown());
+  EXPECT_FALSE(test_api()->IsDoNotDisturbIconShown());
+
+  // Test the case where the tray is initially visible.
+  test_api()->AddNotification();
+  ASSERT_TRUE(test_api()->IsTrayShown());
+  EXPECT_FALSE(test_api()->IsDoNotDisturbIconShown());
+  message_center::MessageCenter::Get()->SetQuietMode(true);
+  EXPECT_TRUE(test_api()->IsTrayShown());
+  EXPECT_TRUE(test_api()->IsDoNotDisturbIconShown());
+  message_center::MessageCenter::Get()->SetQuietMode(false);
+  EXPECT_TRUE(test_api()->IsTrayShown());
+  EXPECT_FALSE(test_api()->IsDoNotDisturbIconShown());
+}
+
 // TODO(b/252875025):
 // Add following test cases as we add relevant functionality:
 // - Focus Change dismissing bubble
diff --git a/ash/system/time/time_tray_item_view.cc b/ash/system/time/time_tray_item_view.cc
index fe84631..a070e851 100644
--- a/ash/system/time/time_tray_item_view.cc
+++ b/ash/system/time/time_tray_item_view.cc
@@ -22,7 +22,7 @@
                                      : TimeView::ClockLayout::VERTICAL_CLOCK;
   time_view_ = AddChildView(std::make_unique<TimeView>(
       clock_layout, Shell::Get()->system_tray_model()->clock(), type));
-  time_view_->SetTextColor(kColorAshIconColorPrimary);
+  time_view_->SetTextColorId(kColorAshIconColorPrimary);
 }
 
 TimeTrayItemView::~TimeTrayItemView() = default;
diff --git a/ash/system/time/time_view.cc b/ash/system/time/time_view.cc
index 3d676fb..017702bb 100644
--- a/ash/system/time/time_view.cc
+++ b/ash/system/time/time_view.cc
@@ -194,10 +194,28 @@
   Layout();
 }
 
-void TimeView::SetTextColor(ui::ColorId color_id,
+void TimeView::SetTextColorId(ui::ColorId color_id,
+                              bool auto_color_readability_enabled) {
+  auto set_color_id = [&](views::Label* label) {
+    label->SetEnabledColorId(color_id);
+    label->SetAutoColorReadabilityEnabled(auto_color_readability_enabled);
+  };
+
+  switch (type_) {
+    case kTime:
+      set_color_id(horizontal_label_);
+      set_color_id(vertical_label_hours_);
+      set_color_id(vertical_label_minutes_);
+      return;
+    case kDate:
+      set_color_id(horizontal_label_date_);
+  }
+}
+
+void TimeView::SetTextColor(SkColor color,
                             bool auto_color_readability_enabled) {
   auto set_color = [&](views::Label* label) {
-    label->SetEnabledColorId(color_id);
+    label->SetEnabledColor(color);
     label->SetAutoColorReadabilityEnabled(auto_color_readability_enabled);
   };
 
diff --git a/ash/system/time/time_view.h b/ash/system/time/time_view.h
index 898627e..1419fe9 100644
--- a/ash/system/time/time_view.h
+++ b/ash/system/time/time_view.h
@@ -76,9 +76,12 @@
   // Updates clock layout.
   void UpdateClockLayout(ClockLayout clock_layout);
 
-  // Updates the time text color.
-  void SetTextColor(ui::ColorId color_id,
-                    bool auto_color_readability_enabled = false);
+  // Updates the time text color id.
+  void SetTextColorId(ui::ColorId color_id,
+                      bool auto_color_readability_enabled = false);
+
+  // Updates the text color.
+  void SetTextColor(SkColor color, bool auto_color_readability_enabled = false);
 
   // Updates the time text fontlist.
   void SetTextFont(const gfx::FontList& font_list);
diff --git a/ash/system/tray/system_nudge.cc b/ash/system/tray/system_nudge.cc
index 6e55e7e..32d8b77f 100644
--- a/ash/system/tray/system_nudge.cc
+++ b/ash/system/tray/system_nudge.cc
@@ -189,9 +189,6 @@
   const std::u16string accessibility_text = GetAccessibilityText();
   if (!accessibility_text.empty())
     nudge_view_->GetViewAccessibility().AnnounceText(accessibility_text);
-
-  base::UmaHistogramEnumeration("Ash.NotifierFramework.Nudge.ShownCount",
-                                params_.catalog_name);
 }
 
 void SystemNudge::Close() {
diff --git a/ash/system/tray/system_nudge.h b/ash/system/tray/system_nudge.h
index e89cefd8..c786cfc 100644
--- a/ash/system/tray/system_nudge.h
+++ b/ash/system/tray/system_nudge.h
@@ -66,6 +66,8 @@
 
   views::Widget* widget() { return widget_.get(); }
 
+  NudgeCatalogName catalog_name() { return params_.catalog_name; }
+
  protected:
   // Each SystemNudge subclass must override these methods to customize
   // their nudge by creating a label and getting an icon specific to the feature
diff --git a/ash/system/tray/system_nudge_controller.cc b/ash/system/tray/system_nudge_controller.cc
index 330e33dc..aa54c08 100644
--- a/ash/system/tray/system_nudge_controller.cc
+++ b/ash/system/tray/system_nudge_controller.cc
@@ -10,6 +10,8 @@
 #include "ash/shell.h"
 #include "ash/system/tray/system_nudge.h"
 #include "base/bind.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/strings/strcat.h"
 #include "base/time/time.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
@@ -27,6 +29,19 @@
     gfx::Tween::LINEAR;
 constexpr gfx::Tween::Type kNudgeFadeScalingAnimationTweenType =
     gfx::Tween::LINEAR_OUT_SLOW_IN;
+
+constexpr char NotifierFrameworkNudgeHistogram[] =
+    "Ash.NotifierFramework.Nudge";
+
+// Used in histogram names.
+std::string GetNudgeTimeToActionRange(const base::TimeDelta& time) {
+  if (time <= base::Minutes(1))
+    return "Within1m";
+  if (time <= base::Hours(1))
+    return "Within1h";
+  return "WithinSession";
+}
+
 }  // namespace
 
 // A class for observing the nudge fade out animation. Once the fade
@@ -67,6 +82,31 @@
   hide_nudge_animation_observer_.reset();
 }
 
+// static
+void SystemNudgeController::RecordNudgeAction(NudgeCatalogName catalog_name) {
+  auto& nudge_registry = GetNudgeRegistry();
+  auto it = std::find_if(
+      std::begin(nudge_registry), std::end(nudge_registry),
+      [catalog_name](
+          const std::pair<NudgeCatalogName, base::TimeTicks> registry_entry) {
+        return catalog_name == registry_entry.first;
+      });
+
+  // Don't record "TimeToAction" metric if the nudge hasn't been shown.
+  if (it == std::end(nudge_registry))
+    return;
+
+  const base::TimeDelta delta = base::TimeTicks::Now() - (*it).second;
+  const std::string time_range = GetNudgeTimeToActionRange(delta);
+
+  base::UmaHistogramEnumeration(
+      base::StrCat({NotifierFrameworkNudgeHistogram, ".TimeToAction.",
+                    time_range.c_str()}),
+      catalog_name);
+
+  nudge_registry.erase(it);
+}
+
 void SystemNudgeController::ShowNudge() {
   if (nudge_ && !nudge_->widget()->IsClosed()) {
     hide_nudge_timer_.AbandonAndStop();
@@ -77,6 +117,7 @@
   nudge_ = CreateSystemNudge();
   nudge_->Show();
   StartFadeAnimation(/*show=*/true);
+  RecordNudgeShown(nudge_->catalog_name());
 
   // Start a timer to close the nudge after a set amount of time.
   hide_nudge_timer_.Start(FROM_HERE, kNudgeShowTime,
@@ -92,10 +133,22 @@
   hide_nudge_timer_.FireNow();
 }
 
+void SystemNudgeController::ResetNudgeRegistryForTesting() {
+  GetNudgeRegistry().clear();
+}
+
 void SystemNudgeController::HideNudge() {
   StartFadeAnimation(/*show=*/false);
 }
 
+// static
+std::vector<std::pair<NudgeCatalogName, base::TimeTicks>>&
+SystemNudgeController::GetNudgeRegistry() {
+  static auto nudge_registry =
+      std::vector<std::pair<NudgeCatalogName, base::TimeTicks>>();
+  return nudge_registry;
+}
+
 void SystemNudgeController::StartFadeAnimation(bool show) {
   // Clean any pending animation observer.
   hide_nudge_animation_observer_.reset();
@@ -142,4 +195,23 @@
   }
 }
 
+void SystemNudgeController::RecordNudgeShown(NudgeCatalogName catalog_name) {
+  base::UmaHistogramEnumeration("Ash.NotifierFramework.Nudge.ShownCount",
+                                catalog_name);
+  auto& nudge_registry = GetNudgeRegistry();
+
+  auto it = std::find_if(
+      std::begin(nudge_registry), std::end(nudge_registry),
+      [catalog_name](
+          const std::pair<NudgeCatalogName, base::TimeTicks> registry_entry) {
+        return catalog_name == registry_entry.first;
+      });
+
+  if (it == std::end(nudge_registry)) {
+    nudge_registry.emplace_back(catalog_name, base::TimeTicks::Now());
+  } else {
+    (*it).second = base::TimeTicks::Now();
+  }
+}
+
 }  // namespace ash
diff --git a/ash/system/tray/system_nudge_controller.h b/ash/system/tray/system_nudge_controller.h
index b07a38c..85223ece 100644
--- a/ash/system/tray/system_nudge_controller.h
+++ b/ash/system/tray/system_nudge_controller.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "ash/ash_export.h"
+#include "ash/constants/notifier_catalogs.h"
 #include "base/memory/weak_ptr.h"
 #include "base/timer/timer.h"
 #include "ui/compositor/layer_animation_observer.h"
@@ -28,6 +29,10 @@
   SystemNudgeController& operator=(const SystemNudgeController&) = delete;
   virtual ~SystemNudgeController();
 
+  // Records Nudge "TimeToAction" metric, which tracks the time from when a
+  // nudge was shown to when the action the nudge informs of was performed.
+  static void RecordNudgeAction(NudgeCatalogName catalog_name);
+
   // Shows the nudge widget.
   void ShowNudge();
 
@@ -40,6 +45,10 @@
   // Get the system nudge for testing purpose.
   SystemNudge* GetSystemNudgeForTesting() { return nudge_.get(); }
 
+  // Resets the `nudge_registry` object that records the time a nudge was last
+  // shown.
+  void ResetNudgeRegistryForTesting();
+
  protected:
   // Concrete subclasses must implement this method to return a
   // SystemNudge that creates a label and specifies an icon specific
@@ -50,9 +59,17 @@
   void HideNudge();
 
  private:
+  // Returns the registry which keeps track of when a nudge was last shown.
+  static std::vector<std::pair<NudgeCatalogName, base::TimeTicks>>&
+  GetNudgeRegistry();
+
   // Begins the animation for fading in or fading out the nudge.
   void StartFadeAnimation(bool show);
 
+  // Records the time a nudge was last shown and stores it in the
+  // `nudge_registry`.
+  void RecordNudgeShown(NudgeCatalogName catalog_name);
+
   // Contextual nudge which shows a view.
   std::unique_ptr<SystemNudge> nudge_;
 
diff --git a/ash/system/tray/system_nudge_unittest.cc b/ash/system/tray/system_nudge_unittest.cc
index 5f87de40e..66644ff1 100644
--- a/ash/system/tray/system_nudge_unittest.cc
+++ b/ash/system/tray/system_nudge_unittest.cc
@@ -9,9 +9,11 @@
 #include "ash/public/cpp/shelf_types.h"
 #include "ash/shelf/shelf.h"
 #include "ash/shelf/shelf_layout_manager.h"
+#include "ash/system/tray/system_nudge_controller.h"
 #include "ash/system/tray/system_nudge_label.h"
 #include "ash/test/ash_test_base.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/task_environment.h"
 #include "ui/gfx/vector_icon_types.h"
 
 namespace ash {
@@ -26,12 +28,22 @@
 
 constexpr char kNudgeName[] = "TestSystemNudge";
 
+constexpr NudgeCatalogName kTestCatalogName =
+    NudgeCatalogName::kTestCatalogName;
+
+constexpr char kNudgeShownCount[] = "Ash.NotifierFramework.Nudge.ShownCount";
+constexpr char kNudgeTimeToActionWithin1m[] =
+    "Ash.NotifierFramework.Nudge.TimeToAction.Within1m";
+constexpr char kNudgeTimeToActionWithin1h[] =
+    "Ash.NotifierFramework.Nudge.TimeToAction.Within1h";
+constexpr char kNudgeTimeToActionWithinSession[] =
+    "Ash.NotifierFramework.Nudge.TimeToAction.WithinSession";
+
 gfx::VectorIcon kEmptyIcon;
 
 class TestSystemNudge : public SystemNudge {
  public:
-  explicit TestSystemNudge(
-      NudgeCatalogName catalog_name = NudgeCatalogName::kTestCatalogName)
+  explicit TestSystemNudge(NudgeCatalogName catalog_name = kTestCatalogName)
       : SystemNudge(kNudgeName,
                     catalog_name,
                     kIconSize,
@@ -54,14 +66,26 @@
   }
 };
 
-constexpr char kNudgeShownCountHistogramName[] =
-    "Ash.NotifierFramework.Nudge.ShownCount";
+class TestSystemNudgeController : public SystemNudgeController {
+ public:
+  TestSystemNudgeController() = default;
+  TestSystemNudgeController(const TestSystemNudgeController&) = delete;
+  TestSystemNudgeController& operator=(const TestSystemNudgeController&) =
+      delete;
+  ~TestSystemNudgeController() override = default;
+
+  // SystemNudgeController:
+  std::unique_ptr<SystemNudge> CreateSystemNudge() override {
+    return std::make_unique<TestSystemNudge>(kTestCatalogName);
+  }
+};
 
 }  // namespace
 
 class SystemNudgeTest : public AshTestBase {
  public:
-  SystemNudgeTest() = default;
+  SystemNudgeTest()
+      : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
   SystemNudgeTest(const SystemNudgeTest&) = delete;
   SystemNudgeTest& operator=(const SystemNudgeTest&) = delete;
   ~SystemNudgeTest() override = default;
@@ -107,20 +131,54 @@
 
 TEST_F(SystemNudgeTest, ShownCountMetric) {
   base::HistogramTester histogram_tester;
+  auto nudge_controller = std::make_unique<TestSystemNudgeController>();
 
-  const NudgeCatalogName catalog_name_1 = static_cast<NudgeCatalogName>(1);
-  const NudgeCatalogName catalog_name_2 = static_cast<NudgeCatalogName>(2);
-  TestSystemNudge nudge_1(catalog_name_1);
-  TestSystemNudge nudge_2(catalog_name_2);
+  histogram_tester.ExpectBucketCount(kNudgeShownCount, kTestCatalogName, 0);
 
-  nudge_1.Show();
-  histogram_tester.ExpectBucketCount(kNudgeShownCountHistogramName,
-                                     catalog_name_1, 1);
+  nudge_controller->ShowNudge();
+  histogram_tester.ExpectBucketCount(kNudgeShownCount, kTestCatalogName, 1);
 
-  nudge_2.Show();
-  nudge_2.Show();
-  histogram_tester.ExpectBucketCount(kNudgeShownCountHistogramName,
-                                     catalog_name_2, 2);
+  nudge_controller->ShowNudge();
+  nudge_controller->ShowNudge();
+  histogram_tester.ExpectBucketCount(kNudgeShownCount, kTestCatalogName, 3);
+}
+
+TEST_F(SystemNudgeTest, TimeToActionMetric) {
+  base::HistogramTester histogram_tester;
+  auto nudge_controller = std::make_unique<TestSystemNudgeController>();
+  nudge_controller->ResetNudgeRegistryForTesting();
+
+  // Metric is not recorded if nudge has not been shown.
+  SystemNudgeController::RecordNudgeAction(kTestCatalogName);
+  histogram_tester.ExpectBucketCount(kNudgeTimeToActionWithin1m,
+                                     kTestCatalogName, 0);
+
+  // Metric is recorded after nudge is shown.
+  nudge_controller->ShowNudge();
+  task_environment()->FastForwardBy(base::Seconds(1));
+  SystemNudgeController::RecordNudgeAction(kTestCatalogName);
+  histogram_tester.ExpectBucketCount(kNudgeTimeToActionWithin1m,
+                                     kTestCatalogName, 1);
+
+  // Metric is not recorded if the nudge action is performed again without
+  // another nudge being shown.
+  SystemNudgeController::RecordNudgeAction(kTestCatalogName);
+  histogram_tester.ExpectBucketCount(kNudgeTimeToActionWithin1m,
+                                     kTestCatalogName, 1);
+
+  // Metric is recorded with appropriate time range after showing nudge again
+  // and waiting the time to fall into the next time bucket.
+  nudge_controller->ShowNudge();
+  task_environment()->FastForwardBy(base::Minutes(2));
+  SystemNudgeController::RecordNudgeAction(kTestCatalogName);
+  histogram_tester.ExpectBucketCount(kNudgeTimeToActionWithin1h,
+                                     kTestCatalogName, 1);
+
+  nudge_controller->ShowNudge();
+  task_environment()->FastForwardBy(base::Hours(2));
+  SystemNudgeController::RecordNudgeAction(kTestCatalogName);
+  histogram_tester.ExpectBucketCount(kNudgeTimeToActionWithinSession,
+                                     kTestCatalogName, 1);
 }
 
 TEST_F(SystemNudgeTest, NudgePositionChangeWhenShelfAutoHide) {
diff --git a/ash/system/unified/notification_counter_view.cc b/ash/system/unified/notification_counter_view.cc
index 75cc1ae..5eae9db7 100644
--- a/ash/system/unified/notification_counter_view.cc
+++ b/ash/system/unified/notification_counter_view.cc
@@ -4,19 +4,14 @@
 
 #include "ash/system/unified/notification_counter_view.h"
 
-#include <algorithm>
-
-#include "ash/public/cpp/vm_camera_mic_constants.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_id.h"
-#include "ash/style/ash_color_provider.h"
 #include "ash/system/message_center/ash_message_center_lock_screen_controller.h"
 #include "ash/system/message_center/message_center_utils.h"
 #include "ash/system/tray/tray_constants.h"
-#include "ash/system/tray/tray_utils.h"
 #include "ash/system/unified/notification_icons_controller.h"
 #include "base/i18n/number_formatting.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -25,11 +20,9 @@
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/font_list.h"
 #include "ui/gfx/image/canvas_image_source.h"
-#include "ui/gfx/vector_icon_utils.h"
 #include "ui/message_center/message_center.h"
 #include "ui/views/border.h"
 #include "ui/views/controls/image_view.h"
-#include "ui/views/controls/label.h"
 #include "ui/views/controls/separator.h"
 
 namespace ash {
@@ -196,11 +189,9 @@
 QuietModeView::~QuietModeView() = default;
 
 void QuietModeView::Update() {
-  // TODO(yamaguchi): Add this check when new style of the system tray is
-  // implemented, so that icon resizing will not happen here.
-  // DCHECK_EQ(kTrayIconSize,
-  //     gfx::GetDefaultSizeOfVectorIcon(kSystemTrayDoNotDisturbIcon));
-  if (message_center::MessageCenter::Get()->IsQuietMode()) {
+  if (message_center::MessageCenter::Get()->IsQuietMode() &&
+      Shell::Get()->session_controller()->GetSessionState() ==
+          session_manager::SessionState::ACTIVE) {
     image_view()->SetImage(ui::ImageModel::FromVectorIcon(
         kSystemTrayDoNotDisturbIcon, kColorAshIconColorPrimary));
     SetVisible(true);
diff --git a/ash/system/unified/notification_counter_view.h b/ash/system/unified/notification_counter_view.h
index 40b1063..1d6337a 100644
--- a/ash/system/unified/notification_counter_view.h
+++ b/ash/system/unified/notification_counter_view.h
@@ -7,7 +7,6 @@
 
 #include "ash/ash_export.h"
 #include "ash/system/tray/tray_item_view.h"
-#include "base/scoped_observation.h"
 
 namespace session_manager {
 enum class SessionState;
diff --git a/ash/system/unified/notification_counter_view_unittest.cc b/ash/system/unified/notification_counter_view_unittest.cc
index 3ee91d09..8fe1b164 100644
--- a/ash/system/unified/notification_counter_view_unittest.cc
+++ b/ash/system/unified/notification_counter_view_unittest.cc
@@ -12,8 +12,6 @@
 #include "ash/test/ash_test_base.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/test/scoped_feature_list.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "ui/gfx/image/image.h"
 #include "ui/message_center/message_center.h"
 #include "ui/message_center/public/cpp/notification.h"
 #include "ui/message_center/public/cpp/notification_types.h"
@@ -43,7 +41,8 @@
 class NotificationCounterViewTest : public AshTestBase,
                                     public testing::WithParamInterface<bool> {
  public:
-  NotificationCounterViewTest() = default;
+  NotificationCounterViewTest()
+      : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
   NotificationCounterViewTest(const NotificationCounterViewTest&) = delete;
   NotificationCounterViewTest& operator=(const NotificationCounterViewTest&) =
       delete;
@@ -72,6 +71,15 @@
                                      ->notification_counter_view();
   }
 
+  QuietModeView* GetDoNotDisturbIconView() {
+    auto* status_area_widget = GetPrimaryShelf()->status_area_widget();
+    return IsQsRevampEnabled()
+               ? status_area_widget->notification_center_tray()
+                     ->notification_icons_controller_->quiet_mode_view()
+               : status_area_widget->unified_system_tray()
+                     ->notification_icons_controller_->quiet_mode_view();
+  }
+
  private:
   std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_;
 };
@@ -166,4 +174,20 @@
   EXPECT_FALSE(GetNotificationCounterView()->GetVisible());
 }
 
+TEST_P(NotificationCounterViewTest, DoNotDisturbIconVisibility) {
+  ASSERT_FALSE(GetDoNotDisturbIconView()->GetVisible());
+
+  // Turn on Do not disturb mode.
+  message_center::MessageCenter::Get()->SetQuietMode(true);
+  EXPECT_TRUE(GetDoNotDisturbIconView()->GetVisible());
+
+  // Show the lock screen.
+  BlockUserSession(BLOCKED_BY_LOCK_SCREEN);
+  EXPECT_FALSE(GetDoNotDisturbIconView()->GetVisible());
+
+  // Log in.
+  UnblockUserSession();
+  EXPECT_TRUE(GetDoNotDisturbIconView()->GetVisible());
+}
+
 }  // namespace ash
diff --git a/ash/system/unified/notification_icons_controller.cc b/ash/system/unified/notification_icons_controller.cc
index e783eb5..dad44cf 100644
--- a/ash/system/unified/notification_icons_controller.cc
+++ b/ash/system/unified/notification_icons_controller.cc
@@ -175,15 +175,14 @@
   notification_counter_view_ = tray_container->AddChildView(
       std::make_unique<NotificationCounterView>(shelf_, /*controller=*/this));
 
-  // `quiet_mode_view_` and `separator_` are only shown in the
-  // `UnifiedSystemTray` with the QsRevamp feature disabled. Once kQsRevamp
-  // launches `quiet_mode_view_` will remain in the `UnifiedSystemTray`.
-  // The `separator_` will not be needed because the icons related to this
-  // controller will have their own dedicated tray button.
-  if (!features::IsQsRevampEnabled()) {
-    quiet_mode_view_ =
-        tray_container->AddChildView(std::make_unique<QuietModeView>(shelf_));
+  quiet_mode_view_ =
+      tray_container->AddChildView(std::make_unique<QuietModeView>(shelf_));
 
+  // `separator_` is only shown in the `UnifiedSystemTray` with the QsRevamp
+  // feature disabled. The `separator_` will not be needed once kQsRevamp
+  // launches because the icons related to this controller will have their own
+  // dedicated tray button.
+  if (!features::IsQsRevampEnabled()) {
     separator_ = tray_container->AddChildView(
         std::make_unique<SeparatorTrayItemView>(shelf_));
   }
@@ -222,8 +221,7 @@
 
 void NotificationIconsController::UpdateNotificationIndicators() {
   notification_counter_view_->Update();
-  if (!features::IsQsRevampEnabled())
-    quiet_mode_view_->Update();
+  quiet_mode_view_->Update();
 }
 
 void NotificationIconsController::OnSystemTrayButtonSizeChanged(
diff --git a/ash/system/unified/unified_system_tray.cc b/ash/system/unified/unified_system_tray.cc
index a4a7a6c..d41f53d 100644
--- a/ash/system/unified/unified_system_tray.cc
+++ b/ash/system/unified/unified_system_tray.cc
@@ -36,7 +36,6 @@
 #include "ash/system/tray/tray_background_view.h"
 #include "ash/system/tray/tray_constants.h"
 #include "ash/system/tray/tray_container.h"
-#include "ash/system/tray/tray_event_filter.h"
 #include "ash/system/unified/camera_mic_tray_item_view.h"
 #include "ash/system/unified/current_locale_view.h"
 #include "ash/system/unified/date_tray.h"
@@ -49,7 +48,6 @@
 #include "ash/system/unified/unified_system_tray_bubble.h"
 #include "ash/system/unified/unified_system_tray_model.h"
 #include "ash/system/unified/unified_system_tray_view.h"
-#include "base/debug/stack_trace.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
@@ -63,8 +61,6 @@
 #include "ui/display/display.h"
 #include "ui/display/screen.h"
 #include "ui/gfx/geometry/point.h"
-#include "ui/message_center/message_center.h"
-#include "ui/message_center/notification_view_controller.h"
 #include "ui/views/controls/image_view.h"
 
 namespace ash {
@@ -214,12 +210,6 @@
 
   AddTrayItemToContainer(std::make_unique<ScreenCaptureTrayItemView>(shelf));
 
-  if (features::IsQsRevampEnabled()) {
-    quiet_mode_view_ =
-        tray_container()->AddChildView(std::make_unique<QuietModeView>(shelf));
-    tray_items_.push_back(quiet_mode_view_);
-  }
-
   if (!features::IsQsRevampEnabled()) {
     tray_items_.push_back(
         notification_icons_controller_->notification_counter_view());
@@ -275,11 +265,13 @@
 
   ShelfConfig::Get()->AddObserver(this);
   Shell::Get()->tablet_mode_controller()->AddObserver(this);
+  message_center::MessageCenter::Get()->AddObserver(this);
 }
 
 UnifiedSystemTray::~UnifiedSystemTray() {
   Shell::Get()->tablet_mode_controller()->RemoveObserver(this);
   ShelfConfig::Get()->RemoveObserver(this);
+  message_center::MessageCenter::Get()->RemoveObserver(this);
 
   DestroyBubbles();
 
@@ -513,6 +505,12 @@
   UpdateLayout();
 }
 
+void UnifiedSystemTray::OnQuietModeChanged(bool in_quiet_mode) {
+  if (!features::IsQsRevampEnabled()) {
+    notification_icons_controller_->UpdateNotificationIndicators();
+  }
+}
+
 void UnifiedSystemTray::OnDateTrayActionPerformed(const ui::Event& event) {
   if (!bubble_)
     ShowBubble();
@@ -720,9 +718,6 @@
 }
 
 void UnifiedSystemTray::UpdateNotificationInternal() {
-  if (features::IsQsRevampEnabled())
-    quiet_mode_view_->Update();
-
   // Limit update frequency in order to avoid flashing when 2 updates are
   // incoming in a very short period of time. It happens when ARC++ apps
   // creating bundled notifications.
diff --git a/ash/system/unified/unified_system_tray.h b/ash/system/unified/unified_system_tray.h
index 67040800..0840971 100644
--- a/ash/system/unified/unified_system_tray.h
+++ b/ash/system/unified/unified_system_tray.h
@@ -13,8 +13,6 @@
 #include "ash/public/cpp/accelerators.h"
 #include "ash/public/cpp/shelf_config.h"
 #include "ash/public/cpp/tablet_mode_observer.h"
-#include "ash/system/status_area_widget.h"
-#include "ash/system/time/time_view.h"
 #include "ash/system/tray/tray_background_view.h"
 #include "ash/system/unified/unified_system_tray_controller.h"
 #include "ash/system/unified/unified_system_tray_model.h"
@@ -49,7 +47,6 @@
 class NotificationIconsController;
 class PrivacyIndicatorsTrayItemView;
 class PrivacyScreenToastController;
-class QuietModeView;
 class Shelf;
 class TrayBubbleView;
 class TrayItemView;
@@ -74,7 +71,8 @@
     : public TrayBackgroundView,
       public ShelfConfig::Observer,
       public UnifiedSystemTrayController::Observer,
-      public TabletModeObserver {
+      public TabletModeObserver,
+      public message_center::MessageCenterObserver {
  public:
   class Observer : public base::CheckedObserver {
    public:
@@ -216,6 +214,9 @@
   void OnTabletModeStarted() override;
   void OnTabletModeEnded() override;
 
+  // message_center::MessageCenterObserver:
+  void OnQuietModeChanged(bool in_quiet_mode) override;
+
   // Gets called when an action is performed on the `DateTray`.
   void OnDateTrayActionPerformed(const ui::Event& event);
 
@@ -309,7 +310,6 @@
 
   NetworkTrayView* network_tray_view_ = nullptr;
   ChannelIndicatorView* channel_indicator_view_ = nullptr;
-  QuietModeView* quiet_mode_view_ = nullptr;
 
   // Contains all tray items views added to tray_container().
   std::list<TrayItemView*> tray_items_;
diff --git a/ash/system/unified/unified_system_tray_unittest.cc b/ash/system/unified/unified_system_tray_unittest.cc
index 1f1a6d9..d5f266bc 100644
--- a/ash/system/unified/unified_system_tray_unittest.cc
+++ b/ash/system/unified/unified_system_tray_unittest.cc
@@ -6,7 +6,6 @@
 
 #include "ash/accessibility/accessibility_controller_impl.h"
 #include "ash/constants/ash_features.h"
-#include "ash/ime/ime_controller_impl.h"
 #include "ash/public/cpp/test/shell_test_api.h"
 #include "ash/root_window_controller.h"
 #include "ash/shelf/shelf.h"
@@ -21,10 +20,8 @@
 #include "ash/system/time/time_tray_item_view.h"
 #include "ash/system/time/time_view.h"
 #include "ash/system/unified/ime_mode_view.h"
-#include "ash/system/unified/notification_counter_view.h"
 #include "ash/system/unified/unified_slider_bubble_controller.h"
 #include "ash/system/unified/unified_system_tray_bubble.h"
-#include "ash/system/unified/unified_system_tray_controller.h"
 #include "ash/system/unified/unified_system_tray_view.h"
 #include "ash/test/ash_test_base.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
@@ -37,7 +34,6 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/display/display.h"
 #include "ui/display/screen.h"
-#include "ui/events/event.h"
 #include "ui/events/event_constants.h"
 #include "ui/message_center/message_center.h"
 
@@ -149,10 +145,6 @@
     return GetPrimaryUnifiedSystemTray()->ime_mode_view_;
   }
 
-  QuietModeView* quiet_mode_view() {
-    return GetPrimaryUnifiedSystemTray()->quiet_mode_view_;
-  }
-
  private:
   int id_ = 0;
   base::test::ScopedFeatureList feature_list_;
@@ -685,23 +677,6 @@
             ShelfConfig::Get()->GetShelfControlButtonColor(widget));
 }
 
-// Tests that the `quiet_mode_view_` is visible based on the system's quiet mode
-// setting.
-TEST_P(UnifiedSystemTrayTest, QuietModeViewVisibility) {
-  // `quiet_mode_view_` does not exist in `unified_system_tray_` if QsRevamp is
-  // not enabled. It is owned by `notification_icons_controller` in that case.
-  if (!IsQsRevampEnabled())
-    return;
-
-  auto* message_center = message_center::MessageCenter::Get();
-
-  message_center->SetQuietMode(false);
-  EXPECT_FALSE(quiet_mode_view()->GetVisible());
-
-  message_center->SetQuietMode(true);
-  EXPECT_TRUE(quiet_mode_view()->GetVisible());
-}
-
 // Tests that the bubble automatically hides if it is visible when another
 // bubble becomes visible, and otherwise does not automatically show or hide.
 TEST_P(UnifiedSystemTrayTest, BubbleHideBehavior) {
diff --git a/ash/webui/diagnostics_ui/resources/keyboard_tester.html b/ash/webui/diagnostics_ui/resources/keyboard_tester.html
index 9f3baef..7717a3b 100644
--- a/ash/webui/diagnostics_ui/resources/keyboard_tester.html
+++ b/ash/webui/diagnostics_ui/resources/keyboard_tester.html
@@ -1,13 +1,13 @@
 <style include="diagnostics-shared">
   @media (min-width: 600px) {
     :host {
-      --cr-dialog-width: 416px;
+      --cr-dialog-width: 504px;
     }
   }
 
   @media (min-width: 768px) {
     :host {
-      --cr-dialog-width: 512px;
+      --cr-dialog-width: 636px;
     }
   }
 
diff --git a/ash/webui/personalization_app/personalization_app_ui.cc b/ash/webui/personalization_app/personalization_app_ui.cc
index 8551a4c..84ca5aa6 100644
--- a/ash/webui/personalization_app/personalization_app_ui.cc
+++ b/ash/webui/personalization_app/personalization_app_ui.cc
@@ -307,7 +307,7 @@
       "script-src chrome://resources chrome://test chrome://webui-test "
       "'self';");
 
-  // TODO(crbug.com/1098690): Trusted Type Polymer
+  // TODO(crbug.com/1400799): Enable TrustedTypes.
   source->DisableTrustedTypesCSP();
 
   AddResources(source);
diff --git a/base/allocator/partition_allocator/BUILD.gn b/base/allocator/partition_allocator/BUILD.gn
index 0a6bb25..d8e0a94 100644
--- a/base/allocator/partition_allocator/BUILD.gn
+++ b/base/allocator/partition_allocator/BUILD.gn
@@ -393,6 +393,7 @@
     "BACKUP_REF_PTR_POISON_OOB_PTR=$backup_ref_ptr_poison_oob_ptr",
     "PUT_REF_COUNT_IN_PREVIOUS_SLOT=$put_ref_count_in_previous_slot",
     "USE_ASAN_BACKUP_REF_PTR=$use_asan_backup_ref_ptr",
+    "USE_ASAN_UNOWNED_PTR=$use_asan_unowned_ptr",
     "ENABLE_GWP_ASAN_SUPPORT=$_enable_gwp_asan_support",
 
     # Not to be used directly - instead use
diff --git a/base/allocator/partition_allocator/partition_alloc.gni b/base/allocator/partition_allocator/partition_alloc.gni
index cf598e18..88915bd5 100644
--- a/base/allocator/partition_allocator/partition_alloc.gni
+++ b/base/allocator/partition_allocator/partition_alloc.gni
@@ -83,6 +83,9 @@
   # research. ASAN BRP is not supported in Chromium-external builds.
   use_asan_backup_ref_ptr =
       build_with_chromium && is_asan && (is_win || is_android || is_linux)
+
+  # Use probe-on-destruct unowned ptr detection with ASAN.
+  use_asan_unowned_ptr = false
 }
 
 # AsanBackupRefPtr is not supported outside Chromium. The implementation is
@@ -96,6 +99,11 @@
     !enable_backup_ref_ptr_support || !use_asan_backup_ref_ptr,
     "Both BackupRefPtr and AsanBackupRefPtr can't be enabled at the same time")
 
+# AsanBackupRefPtr and AsanUnownedPtr are mutually exclusive variants of raw_ptr.
+assert(
+    !use_asan_unowned_ptr || !use_asan_backup_ref_ptr,
+    "Both AsanUnownedPtr and AsanBackupRefPtr can't be enabled at the same time")
+
 assert(!use_asan_backup_ref_ptr || is_asan,
        "AsanBackupRefPtr requires AddressSanitizer")
 
@@ -151,6 +159,7 @@
   enable_backup_ref_ptr_support = false
   enable_mte_checked_ptr_support = false
   use_asan_backup_ref_ptr = false
+  use_asan_unowned_ptr = false
   put_ref_count_in_previous_slot = false
   enable_backup_ref_ptr_slow_checks = false
   enable_dangling_raw_ptr_checks = false
@@ -187,6 +196,9 @@
     enable_backup_ref_ptr_support || !backup_ref_ptr_poison_oob_ptr,
     "Can't enable poisoning for OOB pointers if BackupRefPtr isn't enabled at all")
 
+assert(!use_asan_unowned_ptr || is_asan,
+       "AsanUnownedPtr requires AddressSanitizer")
+
 declare_args() {
   enable_pkeys = is_linux && target_cpu == "x64"
 }
diff --git a/base/memory/raw_ptr.h b/base/memory/raw_ptr.h
index f2273362..4191f0e4 100644
--- a/base/memory/raw_ptr.h
+++ b/base/memory/raw_ptr.h
@@ -817,6 +817,90 @@
 };
 #endif  // BUILDFLAG(USE_ASAN_BACKUP_REF_PTR)
 
+#if BUILDFLAG(USE_ASAN_UNOWNED_PTR)
+
+struct AsanUnownedPtrImpl {
+  // Wraps a pointer.
+  template <typename T>
+  static PA_ALWAYS_INLINE T* WrapRawPtr(T* ptr) {
+    return ptr;
+  }
+
+  // Notifies the allocator when a wrapped pointer is being removed or replaced.
+  template <typename T>
+  static PA_ALWAYS_INLINE void ReleaseWrappedPtr(T* wrapped_ptr) {
+    ProbeForLowSeverityLifetimeIssue(wrapped_ptr);
+  }
+
+  // Unwraps the pointer, while asserting that memory hasn't been freed. The
+  // function is allowed to crash on nullptr.
+  template <typename T>
+  static PA_ALWAYS_INLINE T* SafelyUnwrapPtrForDereference(T* wrapped_ptr) {
+    // ASAN will catch use of dereferenced ptr without additional probing.
+    return wrapped_ptr;
+  }
+
+  // Unwraps the pointer, while asserting that memory hasn't been freed. The
+  // function must handle nullptr gracefully.
+  template <typename T>
+  static PA_ALWAYS_INLINE T* SafelyUnwrapPtrForExtraction(T* wrapped_ptr) {
+    ProbeForLowSeverityLifetimeIssue(wrapped_ptr);
+    return wrapped_ptr;
+  }
+
+  // Unwraps the pointer, without making an assertion on whether memory was
+  // freed or not.
+  template <typename T>
+  static PA_ALWAYS_INLINE T* UnsafelyUnwrapPtrForComparison(T* wrapped_ptr) {
+    return wrapped_ptr;
+  }
+
+  // Upcasts the wrapped pointer.
+  template <typename To, typename From>
+  static PA_ALWAYS_INLINE constexpr To* Upcast(From* wrapped_ptr) {
+    static_assert(std::is_convertible<From*, To*>::value,
+                  "From must be convertible to To.");
+    // Note, this cast may change the address if upcasting to base that lies in
+    // the middle of the derived object.
+    return wrapped_ptr;
+  }
+
+  // Advance the wrapped pointer by `delta_elems`.
+  template <typename T,
+            typename Z,
+            typename = std::enable_if_t<offset_type<Z>, void>>
+  static PA_ALWAYS_INLINE T* Advance(T* wrapped_ptr, Z delta_elems) {
+    return wrapped_ptr + delta_elems;
+  }
+
+  template <typename T>
+  static PA_ALWAYS_INLINE ptrdiff_t GetDeltaElems(T* wrapped_ptr1,
+                                                  T* wrapped_ptr2) {
+    return wrapped_ptr1 - wrapped_ptr2;
+  }
+
+  // Returns a copy of a wrapped pointer, without making an assertion on whether
+  // memory was freed or not.
+  template <typename T>
+  static PA_ALWAYS_INLINE T* Duplicate(T* wrapped_ptr) {
+    return wrapped_ptr;
+  }
+
+  template <typename T>
+  static void ProbeForLowSeverityLifetimeIssue(T* wrapped_ptr) {
+    if (wrapped_ptr) {
+      reinterpret_cast<const volatile uint8_t*>(wrapped_ptr)[0];
+    }
+  }
+
+  // This is for accounting only, used by unit tests.
+  static PA_ALWAYS_INLINE void IncrementSwapCountForTest() {}
+  static PA_ALWAYS_INLINE void IncrementLessCountForTest() {}
+  static PA_ALWAYS_INLINE void IncrementPointerToMemberOperatorCountForTest() {}
+};
+
+#endif  // BUILDFLAG(USE_ASAN_UNOWNED_PTR)
+
 template <class Super>
 struct RawPtrCountingImplWrapperForTest
     : public raw_ptr_traits::RawPtrTypeToImpl<Super>::Impl {
@@ -985,6 +1069,10 @@
   using Impl = internal::BackupRefPtrImpl</*AllowDangling=*/true>;
 #elif BUILDFLAG(USE_ASAN_BACKUP_REF_PTR)
   using Impl = internal::AsanBackupRefPtrImpl;
+#elif BUILDFLAG(USE_ASAN_UNOWNED_PTR)
+  // No special bookkeeping required for this case, just treat these
+  // as ordinary pointers.
+  using Impl = internal::RawPtrNoOpImpl;
 #elif defined(PA_ENABLE_MTE_CHECKED_PTR_SUPPORT_WITH_64_BITS_POINTERS)
   using Impl = internal::MTECheckedPtrImpl<
       internal::MTECheckedPtrImplPartitionAllocSupport>;
@@ -999,6 +1087,8 @@
   using Impl = internal::BackupRefPtrImpl</*AllowDangling=*/false>;
 #elif BUILDFLAG(USE_ASAN_BACKUP_REF_PTR)
   using Impl = internal::AsanBackupRefPtrImpl;
+#elif BUILDFLAG(USE_ASAN_UNOWNED_PTR)
+  using Impl = internal::AsanUnownedPtrImpl;
 #elif defined(PA_ENABLE_MTE_CHECKED_PTR_SUPPORT_WITH_64_BITS_POINTERS)
   using Impl = internal::MTECheckedPtrImpl<
       internal::MTECheckedPtrImplPartitionAllocSupport>;
@@ -1060,7 +1150,7 @@
   static_assert(raw_ptr_traits::IsSupportedType<T>::value,
                 "raw_ptr<T> doesn't work with this kind of pointee type T");
 
-#if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
+#if BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT) || BUILDFLAG(USE_ASAN_UNOWNED_PTR)
   // BackupRefPtr requires a non-trivial default constructor, destructor, etc.
   constexpr PA_ALWAYS_INLINE raw_ptr() noexcept : wrapped_ptr_(nullptr) {}
 
@@ -1103,7 +1193,8 @@
     wrapped_ptr_ = nullptr;
   }
 
-#else  // BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
+#else  // BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT) ||
+       // BUILDFLAG(USE_ASAN_UNOWNED_PTR)
 
   // raw_ptr can be trivially default constructed (leaving |wrapped_ptr_|
   // uninitialized).  This is needed for compatibility with raw pointers.
@@ -1124,7 +1215,8 @@
 
   PA_ALWAYS_INLINE ~raw_ptr() noexcept = default;
 
-#endif  // BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
+#endif  // BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT) ||
+        // BUILDFLAG(USE_ASAN_UNOWNED_PTR)
 
   // Deliberately implicit, because raw_ptr is supposed to resemble raw ptr.
   // NOLINTNEXTLINE(google-explicit-constructor)
diff --git a/base/memory/raw_ptr_unittest.cc b/base/memory/raw_ptr_unittest.cc
index 034232ca..9ce62e1 100644
--- a/base/memory/raw_ptr_unittest.cc
+++ b/base/memory/raw_ptr_unittest.cc
@@ -56,7 +56,8 @@
 static_assert(sizeof(raw_ptr<std::string>) == sizeof(std::string*),
               "raw_ptr shouldn't add memory overhead");
 
-#if !BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT)
+#if !BUILDFLAG(ENABLE_BACKUP_REF_PTR_SUPPORT) && \
+    !BUILDFLAG(USE_ASAN_UNOWNED_PTR)
 // |is_trivially_copyable| assertion means that arrays/vectors of raw_ptr can
 // be copied by memcpy.
 static_assert(std::is_trivially_copyable<raw_ptr<void>>::value,
diff --git a/base/memory/raw_ref.h b/base/memory/raw_ref.h
index 71eb6b0..669acee 100644
--- a/base/memory/raw_ref.h
+++ b/base/memory/raw_ref.h
@@ -83,6 +83,9 @@
 #if BUILDFLAG(USE_ASAN_BACKUP_REF_PTR)
       std::is_same_v<Impl, internal::AsanBackupRefPtrImpl> ||
 #endif  // BUILDFLAG(USE_ASAN_BACKUP_REF_PTR)
+#if BUILDFLAG(USE_ASAN_UNOWNED_PTR)
+      std::is_same_v<Impl, internal::AsanUnownedPtrImpl> ||
+#endif  // BUILDFLAG(USE_ASAN_UNOWNED_PTR)
       std::is_same_v<Impl, internal::RawPtrNoOpImpl>;
 
  public:
diff --git a/base/win/sid.cc b/base/win/sid.cc
index 4e48268c..f0904ac3 100644
--- a/base/win/sid.cc
+++ b/base/win/sid.cc
@@ -7,25 +7,56 @@
 #include <windows.h>
 
 #include <sddl.h>
+#include <stdint.h>
 #include <stdlib.h>
 
+#include <algorithm>
 #include <iterator>
+#include <map>
 #include <utility>
 
 #include "base/check.h"
-#include "base/notreached.h"
+#include "base/no_destructor.h"
 #include "base/rand_util.h"
+#include "base/strings/string_util_win.h"
 #include "base/win/scoped_handle.h"
 #include "base/win/scoped_localalloc.h"
 #include "base/win/windows_version.h"
+#include "third_party/boringssl/src/include/openssl/crypto.h"
+#include "third_party/boringssl/src/include/openssl/sha.h"
 
-namespace base {
-namespace win {
+namespace base::win {
 
 namespace {
 
-absl::optional<long>  // NOLINT(runtime/int)
-WellKnownCapabilityToRid(WellKnownCapability capability) {
+template <typename Iterator>
+Sid FromSubAuthorities(const SID_IDENTIFIER_AUTHORITY& identifier_authority,
+                       size_t sub_authority_count,
+                       Iterator sub_authorities) {
+  DCHECK(sub_authority_count <= SID_MAX_SUB_AUTHORITIES);
+  BYTE sid_buffer[SECURITY_MAX_SID_SIZE];
+  SID* sid = reinterpret_cast<SID*>(sid_buffer);
+  sid->Revision = SID_REVISION;
+  sid->SubAuthorityCount = static_cast<UCHAR>(sub_authority_count);
+  sid->IdentifierAuthority = identifier_authority;
+  for (size_t index = 0; index < sub_authority_count; ++index) {
+    sid->SubAuthority[index] = static_cast<DWORD>(*sub_authorities++);
+  }
+  DCHECK(::IsValidSid(sid));
+  return *Sid::FromPSID(sid);
+}
+
+Sid FromSubAuthorities(const SID_IDENTIFIER_AUTHORITY& identifier_authority,
+                       std::initializer_list<int32_t> sub_authorities) {
+  return FromSubAuthorities(identifier_authority, sub_authorities.size(),
+                            sub_authorities.begin());
+}
+
+Sid FromNtAuthority(std::initializer_list<int32_t> sub_authorities) {
+  return FromSubAuthorities(SECURITY_NT_AUTHORITY, sub_authorities);
+}
+
+int32_t WellKnownCapabilityToRid(WellKnownCapability capability) {
   switch (capability) {
     case WellKnownCapability::kInternetClient:
       return SECURITY_CAPABILITY_INTERNET_CLIENT;
@@ -52,97 +83,6 @@
     case WellKnownCapability::kContacts:
       return SECURITY_CAPABILITY_CONTACTS;
   }
-  return absl::nullopt;
-}
-
-absl::optional<WELL_KNOWN_SID_TYPE> WellKnownSidToEnum(WellKnownSid sid) {
-  switch (sid) {
-    case WellKnownSid::kNull:
-      return WinNullSid;
-    case WellKnownSid::kWorld:
-      return WinWorldSid;
-    case WellKnownSid::kCreatorOwner:
-      return WinCreatorOwnerSid;
-    case WellKnownSid::kNetwork:
-      return WinNetworkSid;
-    case WellKnownSid::kBatch:
-      return WinBatchSid;
-    case WellKnownSid::kInteractive:
-      return WinInteractiveSid;
-    case WellKnownSid::kService:
-      return WinServiceSid;
-    case WellKnownSid::kAnonymous:
-      return WinAnonymousSid;
-    case WellKnownSid::kSelf:
-      return WinSelfSid;
-    case WellKnownSid::kAuthenticatedUser:
-      return WinAuthenticatedUserSid;
-    case WellKnownSid::kRestricted:
-      return WinRestrictedCodeSid;
-    case WellKnownSid::kLocalSystem:
-      return WinLocalSystemSid;
-    case WellKnownSid::kLocalService:
-      return WinLocalServiceSid;
-    case WellKnownSid::kNetworkService:
-      return WinNetworkServiceSid;
-    case WellKnownSid::kBuiltinAdministrators:
-      return WinBuiltinAdministratorsSid;
-    case WellKnownSid::kBuiltinUsers:
-      return WinBuiltinUsersSid;
-    case WellKnownSid::kBuiltinGuests:
-      return WinBuiltinGuestsSid;
-    case WellKnownSid::kUntrustedLabel:
-      return WinUntrustedLabelSid;
-    case WellKnownSid::kLowLabel:
-      return WinLowLabelSid;
-    case WellKnownSid::kMediumLabel:
-      return WinMediumLabelSid;
-    case WellKnownSid::kHighLabel:
-      return WinHighLabelSid;
-    case WellKnownSid::kSystemLabel:
-      return WinSystemLabelSid;
-    case WellKnownSid::kWriteRestricted:
-      return WinWriteRestrictedCodeSid;
-    case WellKnownSid::kCreatorOwnerRights:
-      return WinCreatorOwnerRightsSid;
-    case WellKnownSid::kAllApplicationPackages:
-      return WinBuiltinAnyPackageSid;
-    case WellKnownSid::kAllRestrictedApplicationPackages:
-      // This should be handled by FromKnownSid.
-      NOTREACHED();
-      break;
-  }
-  return absl::nullopt;
-}
-
-absl::optional<Sid> FromSubAuthorities(
-    PSID_IDENTIFIER_AUTHORITY identifier_authority,
-    BYTE sub_authority_count,
-    PDWORD sub_authorities) {
-  BYTE sid[SECURITY_MAX_SID_SIZE];
-  if (!::InitializeSid(sid, identifier_authority, sub_authority_count))
-    return absl::nullopt;
-
-  for (DWORD index = 0; index < sub_authority_count; ++index) {
-    PDWORD sub_authority = ::GetSidSubAuthority(sid, index);
-    *sub_authority = sub_authorities[index];
-  }
-  return Sid::FromPSID(sid);
-}
-
-template <typename T>
-absl::optional<std::vector<Sid>> FromVector(
-    const std::vector<T>& values,
-    absl::optional<Sid> (*create_sid)(T)) {
-  std::vector<Sid> converted_sids;
-  converted_sids.reserve(values.size());
-  for (T value : values) {
-    auto sid = create_sid(value);
-    if (!sid)
-      return absl::nullopt;
-    converted_sids.push_back(std::move(*sid));
-  }
-  return converted_sids;
 }
 
 }  // namespace
@@ -154,94 +94,128 @@
 }
 
 absl::optional<Sid> Sid::FromKnownCapability(WellKnownCapability capability) {
-  absl::optional<long> capability_rid =  // NOLINT(runtime/int)
-      WellKnownCapabilityToRid(capability);
-  if (!capability_rid)
-    return absl::nullopt;
-  SID_IDENTIFIER_AUTHORITY capability_authority = {
-      SECURITY_APP_PACKAGE_AUTHORITY};
-  DWORD sub_authorities[] = {SECURITY_CAPABILITY_BASE_RID,
-                             static_cast<DWORD>(*capability_rid)};
-  return FromSubAuthorities(&capability_authority, std::size(sub_authorities),
-                            sub_authorities);
+  int32_t capability_rid = WellKnownCapabilityToRid(capability);
+  return FromSubAuthorities(SECURITY_APP_PACKAGE_AUTHORITY,
+                            {SECURITY_CAPABILITY_BASE_RID, capability_rid});
 }
 
 absl::optional<Sid> Sid::FromNamedCapability(const wchar_t* capability_name) {
-  DCHECK_GE(GetVersion(), Version::WIN10);
-
-  if (!capability_name || !*capability_name)
-    return absl::nullopt;
-
-  typedef decltype(
-      ::DeriveCapabilitySidsFromName)* DeriveCapabilitySidsFromNameFunc;
-  static const DeriveCapabilitySidsFromNameFunc derive_capability_sids =
-      []() -> DeriveCapabilitySidsFromNameFunc {
-    HMODULE module = GetModuleHandle(L"api-ms-win-security-base-l1-2-2.dll");
-    if (!module)
-      return nullptr;
-
-    return reinterpret_cast<DeriveCapabilitySidsFromNameFunc>(
-        ::GetProcAddress(module, "DeriveCapabilitySidsFromName"));
-  }();
-  if (!derive_capability_sids)
-    return absl::nullopt;
-
-  // Pre-reserve some space for SID deleters.
-  std::vector<ScopedLocalAlloc> deleter_list;
-  deleter_list.reserve(16);
-
-  PSID* capability_groups = nullptr;
-  DWORD capability_group_count = 0;
-  PSID* capability_sids = nullptr;
-  DWORD capability_sid_count = 0;
-
-  if (!derive_capability_sids(capability_name, &capability_groups,
-                              &capability_group_count, &capability_sids,
-                              &capability_sid_count)) {
+  if (!capability_name || !*capability_name) {
     return absl::nullopt;
   }
+  static const base::NoDestructor<std::map<std::wstring, WellKnownCapability>>
+      known_capabilities(
+          {{L"INTERNETCLIENT", WellKnownCapability::kInternetClient},
+           {L"INTERNETCLIENTSERVER",
+            WellKnownCapability::kInternetClientServer},
+           {L"PRIVATENETWORKCLIENTSERVER",
+            WellKnownCapability::kPrivateNetworkClientServer},
+           {L"PICTURESLIBRARY", WellKnownCapability::kPicturesLibrary},
+           {L"VIDEOSLIBRARY", WellKnownCapability::kVideosLibrary},
+           {L"MUSICLIBRARY", WellKnownCapability::kMusicLibrary},
+           {L"DOCUMENTSLIBRARY", WellKnownCapability::kDocumentsLibrary},
+           {L"ENTERPRISEAUTHENTICATION",
+            WellKnownCapability::kEnterpriseAuthentication},
+           {L"SHAREDUSERCERTIFICATES",
+            WellKnownCapability::kSharedUserCertificates},
+           {L"REMOVABLESTORAGE", WellKnownCapability::kRemovableStorage},
+           {L"APPOINTMENTS", WellKnownCapability::kAppointments},
+           {L"CONTACTS", WellKnownCapability::kContacts}});
 
-  deleter_list.emplace_back(capability_groups);
-  deleter_list.emplace_back(capability_sids);
-
-  for (DWORD i = 0; i < capability_group_count; ++i) {
-    deleter_list.emplace_back(capability_groups[i]);
+  std::wstring cap_upper = base::ToUpperASCII(capability_name);
+  auto known_cap = known_capabilities->find(cap_upper);
+  if (known_cap != known_capabilities->end()) {
+    return FromKnownCapability(known_cap->second);
   }
-  for (DWORD i = 0; i < capability_sid_count; ++i) {
-    deleter_list.emplace_back(capability_sids[i]);
-  }
+  CRYPTO_library_init();
+  static_assert((SHA256_DIGEST_LENGTH / sizeof(DWORD)) ==
+                SECURITY_APP_PACKAGE_RID_COUNT);
+  DWORD rids[(SHA256_DIGEST_LENGTH / sizeof(DWORD)) + 2];
+  rids[0] = SECURITY_CAPABILITY_BASE_RID;
+  rids[1] = SECURITY_CAPABILITY_APP_RID;
 
-  if (capability_sid_count < 1)
-    return absl::nullopt;
-
-  return FromPSID(capability_sids[0]);
+  SHA256(reinterpret_cast<const uint8_t*>(cap_upper.c_str()),
+         cap_upper.size() * sizeof(wchar_t),
+         reinterpret_cast<uint8_t*>(&rids[2]));
+  return FromSubAuthorities(SECURITY_APP_PACKAGE_AUTHORITY, std::size(rids),
+                            rids);
 }
 
 absl::optional<Sid> Sid::FromKnownSid(WellKnownSid type) {
-  if (type == WellKnownSid::kAllRestrictedApplicationPackages) {
-    SID_IDENTIFIER_AUTHORITY package_authority = {
-        SECURITY_APP_PACKAGE_AUTHORITY};
-    DWORD sub_authorities[] = {SECURITY_APP_PACKAGE_BASE_RID,
-                               SECURITY_BUILTIN_PACKAGE_ANY_RESTRICTED_PACKAGE};
-    return FromSubAuthorities(&package_authority, 2, sub_authorities);
+  switch (type) {
+    case WellKnownSid::kNull:
+      return FromSubAuthorities(SECURITY_NULL_SID_AUTHORITY,
+                                {SECURITY_NULL_RID});
+    case WellKnownSid::kWorld:
+      return FromSubAuthorities(SECURITY_WORLD_SID_AUTHORITY,
+                                {SECURITY_WORLD_RID});
+    case WellKnownSid::kCreatorOwner:
+      return FromSubAuthorities(SECURITY_CREATOR_SID_AUTHORITY,
+                                {SECURITY_CREATOR_OWNER_RID});
+    case WellKnownSid::kCreatorOwnerRights:
+      return FromSubAuthorities(SECURITY_CREATOR_SID_AUTHORITY,
+                                {SECURITY_CREATOR_OWNER_RIGHTS_RID});
+    case WellKnownSid::kNetwork:
+      return FromNtAuthority({SECURITY_NETWORK_RID});
+    case WellKnownSid::kBatch:
+      return FromNtAuthority({SECURITY_BATCH_RID});
+    case WellKnownSid::kInteractive:
+      return FromNtAuthority({SECURITY_INTERACTIVE_RID});
+    case WellKnownSid::kService:
+      return FromNtAuthority({SECURITY_SERVICE_RID});
+    case WellKnownSid::kAnonymous:
+      return FromNtAuthority({SECURITY_ANONYMOUS_LOGON_RID});
+    case WellKnownSid::kSelf:
+      return FromNtAuthority({SECURITY_PRINCIPAL_SELF_RID});
+    case WellKnownSid::kAuthenticatedUser:
+      return FromNtAuthority({SECURITY_AUTHENTICATED_USER_RID});
+    case WellKnownSid::kRestricted:
+      return FromNtAuthority({SECURITY_RESTRICTED_CODE_RID});
+    case WellKnownSid::kWriteRestricted:
+      return FromNtAuthority({SECURITY_WRITE_RESTRICTED_CODE_RID});
+    case WellKnownSid::kLocalSystem:
+      return FromNtAuthority({SECURITY_LOCAL_SYSTEM_RID});
+    case WellKnownSid::kLocalService:
+      return FromNtAuthority({SECURITY_LOCAL_SERVICE_RID});
+    case WellKnownSid::kNetworkService:
+      return FromNtAuthority({SECURITY_NETWORK_SERVICE_RID});
+    case WellKnownSid::kBuiltinAdministrators:
+      return FromNtAuthority(
+          {SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS});
+    case WellKnownSid::kBuiltinUsers:
+      return FromNtAuthority(
+          {SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_USERS});
+    case WellKnownSid::kBuiltinGuests:
+      return FromNtAuthority(
+          {SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_GUESTS});
+    case WellKnownSid::kUntrustedLabel:
+      return FromIntegrityLevel(SECURITY_MANDATORY_UNTRUSTED_RID);
+    case WellKnownSid::kLowLabel:
+      return FromIntegrityLevel(SECURITY_MANDATORY_LOW_RID);
+    case WellKnownSid::kMediumLabel:
+      return FromIntegrityLevel(SECURITY_MANDATORY_MEDIUM_RID);
+    case WellKnownSid::kHighLabel:
+      return FromIntegrityLevel(SECURITY_MANDATORY_HIGH_RID);
+    case WellKnownSid::kSystemLabel:
+      return FromIntegrityLevel(SECURITY_MANDATORY_SYSTEM_RID);
+    case WellKnownSid::kAllApplicationPackages:
+      return FromSubAuthorities(SECURITY_APP_PACKAGE_AUTHORITY,
+                                {SECURITY_APP_PACKAGE_BASE_RID,
+                                 SECURITY_BUILTIN_PACKAGE_ANY_PACKAGE});
+    case WellKnownSid::kAllRestrictedApplicationPackages:
+      return FromSubAuthorities(
+          SECURITY_APP_PACKAGE_AUTHORITY,
+          {SECURITY_APP_PACKAGE_BASE_RID,
+           SECURITY_BUILTIN_PACKAGE_ANY_RESTRICTED_PACKAGE});
   }
-
-  BYTE sid[SECURITY_MAX_SID_SIZE];
-  DWORD size_sid = SECURITY_MAX_SID_SIZE;
-  absl::optional<WELL_KNOWN_SID_TYPE> known_sid = WellKnownSidToEnum(type);
-  if (!known_sid)
-    return absl::nullopt;
-  if (!::CreateWellKnownSid(*known_sid, nullptr, sid, &size_sid))
-    return absl::nullopt;
-
-  return Sid(sid, size_sid);
 }
 
 absl::optional<Sid> Sid::FromSddlString(const wchar_t* sddl_sid) {
   PSID psid = nullptr;
   if (!::ConvertStringSidToSid(sddl_sid, &psid))
     return absl::nullopt;
-  return FromPSID(TakeLocalAlloc(psid).get());
+  auto psid_alloc = TakeLocalAlloc(psid);
+  return FromPSID(psid_alloc.get());
 }
 
 absl::optional<Sid> Sid::FromPSID(PSID sid) {
@@ -252,37 +226,62 @@
 }
 
 absl::optional<Sid> Sid::GenerateRandomSid() {
-  SID_IDENTIFIER_AUTHORITY package_authority = {SECURITY_NULL_SID_AUTHORITY};
   DWORD sub_authorities[4] = {};
   RandBytes(&sub_authorities, sizeof(sub_authorities));
-  return FromSubAuthorities(&package_authority, _countof(sub_authorities),
-                            sub_authorities);
+  return FromSubAuthorities(SECURITY_NULL_SID_AUTHORITY,
+                            std::size(sub_authorities), sub_authorities);
 }
 
 absl::optional<Sid> Sid::FromIntegrityLevel(DWORD integrity_level) {
-  SID_IDENTIFIER_AUTHORITY package_authority = {
-      SECURITY_MANDATORY_LABEL_AUTHORITY};
-  return FromSubAuthorities(&package_authority, 1, &integrity_level);
+  return FromSubAuthorities(SECURITY_MANDATORY_LABEL_AUTHORITY, 1,
+                            &integrity_level);
 }
 
 absl::optional<std::vector<Sid>> Sid::FromSddlStringVector(
     const std::vector<const wchar_t*>& sddl_sids) {
-  return FromVector(sddl_sids, Sid::FromSddlString);
+  std::vector<Sid> converted_sids;
+  converted_sids.reserve(sddl_sids.size());
+  for (const wchar_t* sddl_sid : sddl_sids) {
+    absl::optional<Sid> sid = FromSddlString(sddl_sid);
+    if (!sid)
+      return absl::nullopt;
+    converted_sids.push_back(std::move(*sid));
+  }
+  return converted_sids;
 }
 
 absl::optional<std::vector<Sid>> Sid::FromNamedCapabilityVector(
     const std::vector<const wchar_t*>& capability_names) {
-  return FromVector(capability_names, Sid::FromNamedCapability);
+  std::vector<Sid> converted_sids;
+  converted_sids.reserve(capability_names.size());
+  for (const wchar_t* name : capability_names) {
+    absl::optional<Sid> sid = FromNamedCapability(name);
+    if (!sid)
+      return absl::nullopt;
+    converted_sids.push_back(std::move(*sid));
+  }
+  return converted_sids;
 }
 
 absl::optional<std::vector<Sid>> Sid::FromKnownCapabilityVector(
     const std::vector<WellKnownCapability>& capabilities) {
-  return FromVector(capabilities, Sid::FromKnownCapability);
+  std::vector<Sid> sids;
+  std::transform(capabilities.cbegin(), capabilities.cend(),
+                 std::back_inserter(sids),
+                 [](WellKnownCapability known_cap) -> Sid {
+                   return *Sid::FromKnownCapability(known_cap);
+                 });
+  return sids;
 }
 
 absl::optional<std::vector<Sid>> Sid::FromKnownSidVector(
-    const std::vector<WellKnownSid>& sids) {
-  return FromVector(sids, Sid::FromKnownSid);
+    const std::vector<WellKnownSid>& known_sids) {
+  std::vector<Sid> sids;
+  std::transform(known_sids.cbegin(), known_sids.cend(),
+                 std::back_inserter(sids), [](WellKnownSid known_sid) -> Sid {
+                   return *Sid::FromKnownSid(known_sid);
+                 });
+  return sids;
 }
 
 Sid::Sid(Sid&& sid) = default;
@@ -299,7 +298,8 @@
   LPWSTR sid = nullptr;
   if (!::ConvertSidToStringSid(GetPSID(), &sid))
     return absl::nullopt;
-  return TakeLocalAlloc(sid).get();
+  auto sid_ptr = TakeLocalAlloc(sid);
+  return sid_ptr.get();
 }
 
 Sid Sid::Clone() const {
@@ -318,5 +318,4 @@
   return !(operator==(sid));
 }
 
-}  // namespace win
-}  // namespace base
+}  // namespace base::win
diff --git a/base/win/sid_unittest.cc b/base/win/sid_unittest.cc
index 70bd201..98ec8b2b 100644
--- a/base/win/sid_unittest.cc
+++ b/base/win/sid_unittest.cc
@@ -74,16 +74,49 @@
   return TestSidVector(Sid::FromSddlStringVector(sddl), sddl);
 }
 
+bool EqualNamedCapSid(const Sid& sid, const std::wstring& capability_name) {
+  typedef decltype(::DeriveCapabilitySidsFromName)*
+      DeriveCapabilitySidsFromNameFunc;
+  static const DeriveCapabilitySidsFromNameFunc derive_capability_sids =
+      []() -> DeriveCapabilitySidsFromNameFunc {
+    HMODULE module = GetModuleHandle(L"api-ms-win-security-base-l1-2-2.dll");
+    CHECK(module);
+    return reinterpret_cast<DeriveCapabilitySidsFromNameFunc>(
+        ::GetProcAddress(module, "DeriveCapabilitySidsFromName"));
+  }();
+  CHECK(derive_capability_sids);
+
+  // Pre-reserve some space for SID deleters.
+  std::vector<base::win::ScopedLocalAlloc> deleter_list;
+  deleter_list.reserve(16);
+
+  PSID* capability_groups = nullptr;
+  DWORD capability_group_count = 0;
+  PSID* capability_sids = nullptr;
+  DWORD capability_sid_count = 0;
+
+  CHECK(derive_capability_sids(capability_name.c_str(), &capability_groups,
+                               &capability_group_count, &capability_sids,
+                               &capability_sid_count));
+  deleter_list.emplace_back(capability_groups);
+  deleter_list.emplace_back(capability_sids);
+
+  for (DWORD i = 0; i < capability_group_count; ++i) {
+    deleter_list.emplace_back(capability_groups[i]);
+  }
+  for (DWORD i = 0; i < capability_sid_count; ++i) {
+    deleter_list.emplace_back(capability_sids[i]);
+  }
+
+  CHECK_GE(capability_sid_count, 1U);
+  return sid.Equal(capability_sids[0]);
+}
+
 struct KnownCapabilityTestEntry {
   WellKnownCapability capability;
   const wchar_t* sddl_sid;
 };
 
-struct NamedCapabilityTestEntry {
-  const wchar_t* capability_name;
-  const wchar_t* sddl_sid;
-};
-
 struct KnownSidTestEntry {
   WellKnownSid sid;
   WELL_KNOWN_SID_TYPE well_known_sid;
@@ -135,28 +168,25 @@
 }
 
 TEST(SidTest, NamedCapability) {
-  if (GetVersion() < Version::WIN10_RS2)
-    return;
+  const std::wstring capabilities[] = {L"InternetClient",
+                                       L"InternetClientServer",
+                                       L"PrivateNetworkClientServer",
+                                       L"PicturesLibrary",
+                                       L"VideosLibrary",
+                                       L"MusicLibrary",
+                                       L"DocumentsLibrary",
+                                       L"EnterpriseAuthentication",
+                                       L"SharedUserCertificates",
+                                       L"RemovableStorage",
+                                       L"Appointments",
+                                       L"Contacts",
+                                       L"registryRead",
+                                       L"lpacCryptoServices"};
 
-  EXPECT_FALSE(Sid::FromNamedCapability(nullptr));
-  EXPECT_FALSE(Sid::FromNamedCapability(L""));
-
-  const NamedCapabilityTestEntry capabilities[] = {
-      {L"internetClient", L"S-1-15-3-1"},
-      {L"internetClientServer", L"S-1-15-3-2"},
-      {L"registryRead",
-       L"S-1-15-3-1024-1065365936-1281604716-3511738428-"
-       "1654721687-432734479-3232135806-4053264122-3456934681"},
-      {L"lpacCryptoServices",
-       L"S-1-15-3-1024-3203351429-2120443784-2872670797-"
-       "1918958302-2829055647-4275794519-765664414-2751773334"},
-      {L"enterpriseAuthentication", L"S-1-15-3-8"},
-      {L"privateNetworkClientServer", L"S-1-15-3-3"}};
-
-  for (auto capability : capabilities) {
-    EXPECT_TRUE(EqualSid(Sid::FromNamedCapability(capability.capability_name),
-                         capability.sddl_sid))
-        << "Named Capability: " << capability.sddl_sid;
+  for (const std::wstring& capability : capabilities) {
+    EXPECT_TRUE(EqualNamedCapSid(*Sid::FromNamedCapability(capability.c_str()),
+                                 capability))
+        << "Named Capability: " << capability;
   }
 }
 
@@ -254,28 +284,24 @@
 }
 
 TEST(SidTest, FromNamedCapabilityVector) {
-  if (GetVersion() < Version::WIN10_RS2)
-    return;
-  std::vector<const wchar_t*> capabilities = {L"internetClient",
-                                              L"internetClientServer",
+  std::vector<const wchar_t*> capabilities = {L"InternetClient",
+                                              L"InternetClientServer",
+                                              L"PrivateNetworkClientServer",
+                                              L"PicturesLibrary",
+                                              L"VideosLibrary",
+                                              L"MusicLibrary",
+                                              L"DocumentsLibrary",
+                                              L"EnterpriseAuthentication",
+                                              L"SharedUserCertificates",
+                                              L"RemovableStorage",
+                                              L"Appointments",
+                                              L"Contacts",
                                               L"registryRead",
-                                              L"lpacCryptoServices",
-                                              L"enterpriseAuthentication",
-                                              L"privateNetworkClientServer"};
-  std::vector<const wchar_t*> sddl_caps = {
-      L"S-1-15-3-1",
-      L"S-1-15-3-2",
-      L"S-1-15-3-1024-1065365936-1281604716-3511738428-1654721687-432734479-"
-      L"3232135806-4053264122-3456934681",
-      L"S-1-15-3-1024-3203351429-2120443784-2872670797-1918958302-2829055647-"
-      L"4275794519-765664414-2751773334",
-      L"S-1-15-3-8",
-      L"S-1-15-3-3"};
-  ASSERT_TRUE(
-      TestSidVector(Sid::FromNamedCapabilityVector(capabilities), sddl_caps));
-  ASSERT_FALSE(Sid::FromNamedCapabilityVector({L""}));
-  ASSERT_FALSE(Sid::FromNamedCapabilityVector({L"abc", nullptr}));
-  ASSERT_TRUE(Sid::FromNamedCapabilityVector({}));
+                                              L"lpacCryptoServices"};
+
+  ASSERT_TRUE(ranges::equal(*Sid::FromNamedCapabilityVector(capabilities),
+                            capabilities, EqualNamedCapSid));
+  EXPECT_EQ(Sid::FromNamedCapabilityVector({})->size(), 0U);
 }
 
 TEST(SidTest, FromKnownCapabilityVector) {
diff --git a/buildtools/deps_revisions.gni b/buildtools/deps_revisions.gni
index 44e04b9..249ad4f2 100644
--- a/buildtools/deps_revisions.gni
+++ b/buildtools/deps_revisions.gni
@@ -5,5 +5,5 @@
 declare_args() {
   # Used to cause full rebuilds on libc++ rolls. This should be kept in sync
   # with the libcxx_revision vars in //DEPS.
-  libcxx_revision = "19ffb9c00636bcabb6c6ce76ccaa4370583d3649"
+  libcxx_revision = "2fc3d704672fbd3e85fad8492d39e02d49412891"
 }
diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni
index 4978bf9..e1cfd7c 100644
--- a/chrome/android/chrome_java_resources.gni
+++ b/chrome/android/chrome_java_resources.gni
@@ -366,6 +366,8 @@
   "java/res/drawable/adaptive_toolbar_preference_header.xml",
   "java/res/drawable/arrow_down.xml",
   "java/res/drawable/arrow_up.xml",
+  "java/res/drawable/bg_circle_new_tab_button_detached.xml",
+  "java/res/drawable/bg_circle_new_tab_button_folio.xml",
   "java/res/drawable/bg_tabstrip_tab_divider.xml",
   "java/res/drawable/bg_white_dialog.xml",
   "java/res/drawable/bookmark_save_flow_ripple.xml",
diff --git a/chrome/android/chrome_test_java_sources.gni b/chrome/android/chrome_test_java_sources.gni
index e507315..63d7231 100644
--- a/chrome/android/chrome_test_java_sources.gni
+++ b/chrome/android/chrome_test_java_sources.gni
@@ -294,7 +294,6 @@
   "javatests/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceRecognitionHandlerTest.java",
   "javatests/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentBottomSheetTest.java",
   "javatests/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentControllerTest.java",
-  "javatests/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentModalTest.java",
   "javatests/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentUiRenderTest.java",
   "javatests/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchServiceRenderTest.java",
   "javatests/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchServiceTest.java",
diff --git a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurface.java b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurface.java
index 63282f9..17d91d5 100644
--- a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurface.java
+++ b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurface.java
@@ -184,6 +184,12 @@
     void setLaunchOrigin(@NewTabPageLaunchOrigin int launchOrigin);
 
     /**
+     * Resets the scroll position. This is called when Start surface is showing but not via back
+     * operations.
+     */
+    void resetScrollPosition();
+
+    /**
      * Called by the TabSwitcherLayout when the system back button is pressed.
      * @return Whether or not the TabSwitcher consumed the event.
      */
diff --git a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurfaceCoordinator.java b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurfaceCoordinator.java
index f51bf5b..d9760264 100644
--- a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurfaceCoordinator.java
+++ b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurfaceCoordinator.java
@@ -508,6 +508,11 @@
     }
 
     @Override
+    public void resetScrollPosition() {
+        mStartSurfaceMediator.resetScrollPosition();
+    }
+
+    @Override
     public boolean onBackPressed() {
         return mStartSurfaceMediator.onBackPressed();
     }
diff --git a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java
index 5e81873..a3a6aeb 100644
--- a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java
+++ b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurfaceMediator.java
@@ -512,14 +512,6 @@
         mIsHomepageShown = true;
         notifyShowStateChange();
 
-        // TODO(crbug.com/1347089): When entering the Start surface by tapping back button or other
-        // back gestures, we shouldn't reset the scrolling position. Maybe we could add a boolean
-        // |mResetPosition| and set it as false only when this method is called because of back
-        // actions.
-        mPropertyModel.set(RESET_TASK_SURFACE_HEADER_SCROLL_POSITION, true);
-        mPropertyModel.set(RESET_FEED_SURFACE_SCROLL_POSITION, true);
-        StartSurfaceUserData.getInstance().saveFeedInstanceState(null);
-
         mIsIncognito = mTabModelSelector.isIncognitoSelected();
         mPropertyModel.set(IS_INCOGNITO, mIsIncognito);
         setMVTilesVisibility(!mIsIncognito);
@@ -680,6 +672,14 @@
         }
     }
 
+    void resetScrollPosition() {
+        if (mPropertyModel == null) return;
+
+        mPropertyModel.set(RESET_TASK_SURFACE_HEADER_SCROLL_POSITION, true);
+        mPropertyModel.set(RESET_FEED_SURFACE_SCROLL_POSITION, true);
+        StartSurfaceUserData.getInstance().saveFeedInstanceState(null);
+    }
+
     // TODO(crbug.com/1115757): After crrev.com/c/2315823, Overview state and Startsurface state are
     // two different things, audit the wording usage and see if we can rename this method to
     // setStartSurfaceStateInternal.
@@ -687,10 +687,7 @@
         if (mStartSurfaceState == StartSurfaceState.SHOWING_HOMEPAGE) {
             // When entering the Start surface by tapping home button or new tab page, we need to
             // reset the scrolling position.
-            mPropertyModel.set(RESET_TASK_SURFACE_HEADER_SCROLL_POSITION, true);
-            mPropertyModel.set(RESET_FEED_SURFACE_SCROLL_POSITION, true);
-            StartSurfaceUserData.getInstance().saveFeedInstanceState(null);
-
+            resetScrollPosition();
         } else if (mStartSurfaceState == StartSurfaceState.SHOWING_TABSWITCHER) {
             maybeDestroyFeedPlaceholder();
             // Set secondary surface visible to make sure tab list recyclerview is updated in time
diff --git a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
index 00bf6708..6db1704f 100644
--- a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
+++ b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java
@@ -667,8 +667,6 @@
     @Feature({"StartSurface"})
     @CommandLineFlags.Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS})
     public void testShow_SingleAsHomepage_ResetScrollPosition() {
-        assumeTrue("https://crbug.com/1385547 - Disable for NoInstant.", mUseInstantStart);
-        
         if (!mImmediateReturn) {
             StartSurfaceTestUtils.pressHomePageButton(mActivityTestRule.getActivity());
         }
@@ -699,6 +697,40 @@
     @Test
     @MediumTest
     @Feature({"StartSurface"})
+    @EnableFeatures(ChromeFeatureList.START_SURFACE_REFACTOR)
+    @CommandLineFlags.Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS})
+    public void testShow_SingleAsHomepage_DoNotResetScrollPositionFromBack() {
+        assumeTrue(mImmediateReturn);
+
+        ChromeTabbedActivity cta = mActivityTestRule.getActivity();
+        StartSurfaceTestUtils.waitForStartSurfaceVisible(
+                mLayoutChangedCallbackHelper, mCurrentlyActiveLayout, cta);
+        TabUiTestHelper.verifyTabModelTabCount(cta, 1, 0);
+
+        // Scroll the toolbar.
+        StartSurfaceTestUtils.scrollToolbarAndVerify(cta);
+        AppBarLayout taskSurfaceHeader = cta.findViewById(R.id.task_surface_header);
+        assertNotEquals(taskSurfaceHeader.getBottom(), taskSurfaceHeader.getHeight());
+
+        // Verifies the case of scrolling Start surface ->  MV tile -> tapping back ->
+        // Start surface. The Start surface should not reset its scroll position.
+        StartSurfaceTestUtils.launchFirstMVTile(cta, 1);
+        Assert.assertEquals("The launched tab should have the launch type FROM_START_SURFACE",
+                TabLaunchType.FROM_START_SURFACE,
+                cta.getActivityTabProvider().get().getLaunchType());
+        StartSurfaceTestUtils.pressBack(mActivityTestRule);
+        // Back gesture on the tab should take us back to the start surface homepage.
+        StartSurfaceTestUtils.waitForStartSurfaceVisible(
+                mLayoutChangedCallbackHelper, mCurrentlyActiveLayout, cta);
+
+        // The Start surface should not reset its scroll position.
+        CriteriaHelper.pollInstrumentationThread(
+                () -> taskSurfaceHeader.getBottom() != taskSurfaceHeader.getHeight());
+    }
+
+    @Test
+    @MediumTest
+    @Feature({"StartSurface"})
     @CommandLineFlags.Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS})
     public void singleAsHomepage_PressHomeButtonWillKeepTab() {
         if (!mImmediateReturn) {
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiThemeProvider.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiThemeProvider.java
index 6b60a8e..89486d6 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiThemeProvider.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiThemeProvider.java
@@ -60,6 +60,15 @@
     }
 
     /**
+     * Returns the semantic color value that corresponds to colorPrimaryContainer.
+     *
+     * @param context {@link Context} used to retrieve color.
+     */
+    public static @ColorInt int getDefaultContainerColor(Context context) {
+        return MaterialColors.getColor(context, R.attr.colorPrimaryContainer, TAG);
+    }
+
+    /**
      * Returns the color for the tab strip background.
      *
      * @param context {@link Context} used to retrieve color.
diff --git a/chrome/android/java/res/drawable/bg_circle_new_tab_button_detached.xml b/chrome/android/java/res/drawable/bg_circle_new_tab_button_detached.xml
new file mode 100644
index 0000000..cad27e1
--- /dev/null
+++ b/chrome/android/java/res/drawable/bg_circle_new_tab_button_detached.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2022 The Chromium Authors
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+<!--  Set New tab button background to 38 dp diameter for tab strip redesign detached-->
+  <size
+    android:width="38dp"
+    android:height="38dp" />
+  <solid android:color="@color/modern_white" />
+</shape>
\ No newline at end of file
diff --git a/chrome/android/java/res/drawable/bg_circle_new_tab_button_folio.xml b/chrome/android/java/res/drawable/bg_circle_new_tab_button_folio.xml
new file mode 100644
index 0000000..a4063dc
--- /dev/null
+++ b/chrome/android/java/res/drawable/bg_circle_new_tab_button_folio.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2022 The Chromium Authors
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+  <!--  Set New tab button background to 36 dp diameter for tab strip redesign folio-->
+  <size
+      android:width="36dp"
+      android:height="36dp" />
+  <solid android:color="@color/modern_white" />
+</shape>
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index d488e73..f340ea6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -2600,6 +2600,9 @@
             layoutTypeToShow = LayoutType.START_SURFACE;
             if (state == StartSurfaceState.SHOWING_PREVIOUS) {
                 ReturnToChromeUtil.recordBackNavigationToStart("FromTab");
+            } else {
+                // Resets the scroll position when Start is showing not via back operations.
+                mStartSurfaceSupplier.get().resetScrollPosition();
             }
         }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/components/CompositorButton.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/components/CompositorButton.java
index 0f73e16..ddb7face 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/components/CompositorButton.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/components/CompositorButton.java
@@ -64,6 +64,7 @@
     private final CompositorOnClickHandler mClickHandler;
 
     protected int mResource;
+    protected int mBackgroundResource;
 
     private int mPressedResource;
     private int mIncognitoResource;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/components/TintedCompositorButton.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/components/TintedCompositorButton.java
index 64e3f50..279fa435 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/components/TintedCompositorButton.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/components/TintedCompositorButton.java
@@ -21,6 +21,10 @@
     private @ColorRes int mPressedTintResource;
     private @ColorRes int mIncognitoTintResource;
     private @ColorRes int mIncognitoPressedTintResource;
+    private @ColorInt int mBackgroundDefaultTint;
+    private @ColorInt int mBackgroundPressedTint;
+    private @ColorInt int mBackgroundIncognitoTint;
+    private @ColorInt int mBackgroundIncognitoPressedTint;
 
     public TintedCompositorButton(
             Context context, float width, float height, CompositorOnClickHandler clickHandler) {
@@ -32,7 +36,6 @@
     public TintedCompositorButton(Context context, float width, float height,
             CompositorOnClickHandler clickHandler, @DrawableRes int resource) {
         super(context, width, height, clickHandler);
-
         mContext = context;
         mResource = resource;
     }
@@ -62,6 +65,20 @@
     }
 
     /**
+     * @param backgroundResource The default Android resource.
+     */
+    public void setBackgroundResourceId(@DrawableRes int backgroundResource) {
+        mBackgroundResource = backgroundResource;
+    }
+
+    /**
+     * @return The Android resource that represents button background.
+     */
+    public int getBackgroundResourceId() {
+        return mBackgroundResource;
+    }
+
+    /**
      * A set of Android resources to supply to the compositor.
      * @param defaultTint           The default tint resource.
      * @param pressedTint           The pressed tint resource.
@@ -77,15 +94,42 @@
     }
 
     /**
-     * @return The tint (color value, NOT the resource Id) depending on the state of the button and
-     *         the tab (incognito or not).
+     * A set of Android color to supply to the compositor.
+     * @param backgroundDefaultTint           The default background tint.
+     * @param backgroundPressedTint           The pressed background tint.
+     * @param backgroundIncognitoTint         The incognito background tint.
+     * @param backgroundIncognitoPressedTint  The incognito pressed background tint.
+     */
+    public void setBackgroundTint(@ColorInt int backgroundDefaultTint,
+            @ColorInt int backgroundPressedTint, @ColorInt int backgroundIncognitoTint,
+            @ColorInt int backgroundIncognitoPressedTint) {
+        mBackgroundDefaultTint = backgroundDefaultTint;
+        mBackgroundPressedTint = backgroundPressedTint;
+        mBackgroundIncognitoTint = backgroundIncognitoTint;
+        mBackgroundIncognitoPressedTint = backgroundIncognitoPressedTint;
+    }
+
+    /**
+     * @return The icon tint (color value, NOT the resource Id) depending on the state of the button
+     *         and the tab (incognito or not).
      */
     public @ColorInt int getTint() {
         int tint = isIncognito() ? mIncognitoTintResource : mDefaultTintResource;
         if (isPressed()) {
             tint = isIncognito() ? mIncognitoPressedTintResource : mPressedTintResource;
         }
-
         return AppCompatResources.getColorStateList(mContext, tint).getDefaultColor();
     }
+
+    /**
+     * @return The button background tint (color value, NOT the resource Id) depending on the state
+     *         of the button and the tab.
+     */
+    public @ColorInt int getBackgroundTint() {
+        int tint = isIncognito() ? mBackgroundIncognitoTint : mBackgroundDefaultTint;
+        if (isPressed()) {
+            tint = isIncognito() ? mBackgroundIncognitoPressedTint : mBackgroundPressedTint;
+        }
+        return tint;
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java
index 23a5836..7923f06 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java
@@ -51,10 +51,14 @@
 import org.chromium.chrome.browser.tabmodel.TabModelUtils;
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
 import org.chromium.chrome.browser.tasks.tab_management.TabUiFeatureUtilities;
+import org.chromium.chrome.browser.tasks.tab_management.TabUiThemeProvider;
+import org.chromium.components.browser_ui.styles.ChromeColors;
+import org.chromium.components.browser_ui.styles.SemanticColorUtils;
 import org.chromium.components.browser_ui.widget.animation.Interpolators;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.ui.base.DeviceFormFactor;
 import org.chromium.ui.base.LocalizationUtils;
+import org.chromium.ui.util.ColorUtils;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -128,14 +132,22 @@
     private static final float REORDER_EDGE_SCROLL_START_MIN_DP = 87.4f;
     private static final float REORDER_EDGE_SCROLL_START_MAX_DP = 18.4f;
     private static final float NEW_TAB_BUTTON_Y_OFFSET_DP = 10.f;
+    private static final float NEW_TAB_BUTTON_BACKGROUND_Y_OFFSET_DP = 0.f;
     private static final float NEW_TAB_BUTTON_CLICK_SLOP_DP = 12.f;
     private static final float NEW_TAB_BUTTON_WIDTH_DP = 24.f;
     private static final float NEW_TAB_BUTTON_HEIGHT_DP = 24.f;
+    private static final float NEW_TAB_BUTTON_BACKGROUND_WIDTH_DP_FOLIO = 36.f;
+    private static final float NEW_TAB_BUTTON_BACKGROUND_HEIGHT_DP_FOLIO = 36.f;
+    private static final float NEW_TAB_BUTTON_BACKGROUND_WIDTH_DP_DETACHED = 38.f;
+    private static final float NEW_TAB_BUTTON_BACKGROUND_HEIGHT_DP_DETACHED = 38.f;
     private static final float NEW_TAB_BUTTON_PADDING_DP = 24.f;
     private static final float NEW_TAB_BUTTON_TOUCH_TARGET_OFFSET = 12.f;
     private static final float FOLIO_ATTACHED_BOTTOM_MARGIN_DP = 0.f;
     private static final float FOLIO_ANIM_INTERMEDIATE_MARGIN_DP = -12.f;
     private static final float FOLIO_DETACHED_BOTTOM_MARGIN_DP = 4.f;
+    private static final float NEW_TAB_BUTTON_DESIRED_TOUCH_TARGET_SIZE = 48.f;
+    private static final float NEW_TAB_BUTTON_DEFAULT_PRESSED_OPACITY = 0.2f;
+    private static final float NEW_TAB_BUTTON_DARK_DETACHED_OPACITY = 0.15f;
     static final float BACKGROUND_TAB_BRIGHTNESS_DEFAULT = 1.f;
     static final float BACKGROUND_TAB_BRIGHTNESS_DIMMED = 0.65f;
     static final float DIVIDER_HIDDEN_OPACITY = 0.f;
@@ -242,7 +254,15 @@
     public StripLayoutHelper(Context context, LayoutUpdateHost updateHost,
             LayoutRenderHost renderHost, boolean incognito, CompositorButton modelSelectorButton) {
         mTabOverlapWidth = TAB_OVERLAP_WIDTH_DP;
-        mNewTabButtonWidth = NEW_TAB_BUTTON_WIDTH_DP;
+        if (ChromeFeatureList.sTabStripRedesign.isEnabled()) {
+            if (TabUiFeatureUtilities.isTabStripFolioEnabled()) {
+                mNewTabButtonWidth = NEW_TAB_BUTTON_BACKGROUND_WIDTH_DP_FOLIO;
+            } else {
+                mNewTabButtonWidth = NEW_TAB_BUTTON_BACKGROUND_WIDTH_DP_DETACHED;
+            }
+        } else {
+            mNewTabButtonWidth = NEW_TAB_BUTTON_WIDTH_DP;
+        }
         mModelSelectorButton = modelSelectorButton;
         mTabStripImpEnabled = ChromeFeatureList.sTabStripImprovements.isEnabled();
 
@@ -264,14 +284,78 @@
                 handleNewTabClick();
             }
         };
-        mNewTabButton = new TintedCompositorButton(context, NEW_TAB_BUTTON_WIDTH_DP,
-                NEW_TAB_BUTTON_HEIGHT_DP, newTabClickHandler, R.drawable.ic_new_tab_button);
 
-        mNewTabButton.setTintResources(R.color.new_tab_button_tint_list,
-                R.color.new_tab_button_pressed_tint_list, R.color.modern_white,
-                R.color.default_icon_color_blue_light);
+        if (ChromeFeatureList.sTabStripRedesign.isEnabled()) {
+            // Set new tab button background resource based on which TSR is enabled, background has
+            // different size.
+            if (TabUiFeatureUtilities.isTabStripFolioEnabled()) {
+                // Tab strip redesign folio enabled, bg size 36 * 36.
+                mNewTabButton = new TintedCompositorButton(context,
+                        NEW_TAB_BUTTON_BACKGROUND_WIDTH_DP_FOLIO,
+                        NEW_TAB_BUTTON_BACKGROUND_HEIGHT_DP_FOLIO, newTabClickHandler,
+                        R.drawable.ic_new_tab_button);
+                mNewTabButton.setBackgroundResourceId(R.drawable.bg_circle_new_tab_button_folio);
+            } else {
+                // Tab strip redesign detached enabled, bg size 38 * 38.
+                mNewTabButton = new TintedCompositorButton(context,
+                        NEW_TAB_BUTTON_BACKGROUND_WIDTH_DP_DETACHED,
+                        NEW_TAB_BUTTON_BACKGROUND_HEIGHT_DP_DETACHED, newTabClickHandler,
+                        R.drawable.ic_new_tab_button);
+                mNewTabButton.setBackgroundResourceId(R.drawable.bg_circle_new_tab_button_detached);
+            }
+
+            // Primary container for default bg color.
+            int defaultBackgroundTint = TabUiThemeProvider.getDefaultContainerColor(context);
+
+            // Primary @ 20% for default pressed bg color.
+            int pressedBackgroundTint = androidx.core.graphics.ColorUtils.setAlphaComponent(
+                    SemanticColorUtils.getDefaultIconColorAccent1(context),
+                    (int) (NEW_TAB_BUTTON_DEFAULT_PRESSED_OPACITY * 255));
+
+            // Surface 2 baseline for folio, surface 3 baseline for detached incognito bg color.
+            int incognitoBackgroundTint = TabUiFeatureUtilities.isTabStripFolioEnabled()
+                    ? context.getResources().getColor(R.color.default_bg_color_dark_elev_2_baseline)
+                    : context.getResources().getColor(
+                            R.color.default_bg_color_dark_elev_3_baseline);
+
+            // surface 5 baseline for incognito pressed bg color
+            int incognitoBackgroundPressedTint =
+                    context.getResources().getColor(R.color.default_bg_color_dark_elev_5_baseline);
+
+            // Tab strip redesign new tab button night mode bg color.
+            if (ColorUtils.inNightMode(context)) {
+                // Surface 1 for folio night mode bg color.
+                if (TabUiFeatureUtilities.isTabStripFolioEnabled()) {
+                    defaultBackgroundTint =
+                            ChromeColors.getSurfaceColor(context, R.dimen.default_elevation_1);
+                } else {
+                    // Primary @ 15% for detached night mode bg color.
+                    defaultBackgroundTint = androidx.core.graphics.ColorUtils.setAlphaComponent(
+                            SemanticColorUtils.getDefaultIconColorAccent1(context),
+                            (int) (NEW_TAB_BUTTON_DARK_DETACHED_OPACITY * 255));
+                }
+                // Surface 5 for pressed night mode bg color.
+                pressedBackgroundTint =
+                        ChromeColors.getSurfaceColor(context, R.dimen.default_elevation_5);
+            }
+            mNewTabButton.setBackgroundTint(defaultBackgroundTint, pressedBackgroundTint,
+                    incognitoBackgroundTint, incognitoBackgroundPressedTint);
+
+            // No pressed state color change for new tab button icon when TSR enabled.
+            mNewTabButton.setTintResources(R.color.default_icon_color_tint_list,
+                    R.color.default_icon_color_tint_list, R.color.modern_white,
+                    R.color.modern_white);
+            mNewTabButton.setY(NEW_TAB_BUTTON_BACKGROUND_Y_OFFSET_DP);
+        } else {
+            // when TSR disabled
+            mNewTabButton = new TintedCompositorButton(context, NEW_TAB_BUTTON_WIDTH_DP,
+                    NEW_TAB_BUTTON_HEIGHT_DP, newTabClickHandler, R.drawable.ic_new_tab_button);
+            mNewTabButton.setTintResources(R.color.new_tab_button_tint_list,
+                    R.color.new_tab_button_pressed_tint_list, R.color.modern_white,
+                    R.color.default_icon_color_blue_light);
+            mNewTabButton.setY(NEW_TAB_BUTTON_Y_OFFSET_DP);
+        }
         mNewTabButton.setIncognito(incognito);
-        mNewTabButton.setY(NEW_TAB_BUTTON_Y_OFFSET_DP);
         mNewTabButton.setClickSlop(NEW_TAB_BUTTON_CLICK_SLOP_DP);
         Resources res = context.getResources();
         mNewTabButton.setAccessibilityDescription(
@@ -352,6 +436,11 @@
      */
     public float getNewTabButtonTouchTargetOffset() {
         boolean isRtl = LocalizationUtils.isLayoutRtl();
+        if (ChromeFeatureList.sTabStripRedesign.isEnabled()) {
+            float newTabButtonTouchTargetOffsetTSR =
+                    (NEW_TAB_BUTTON_DESIRED_TOUCH_TARGET_SIZE - mNewTabButtonWidth) / 2;
+            return isRtl ? newTabButtonTouchTargetOffsetTSR : -newTabButtonTouchTargetOffsetTSR;
+        }
         return isRtl ? NEW_TAB_BUTTON_TOUCH_TARGET_OFFSET : -NEW_TAB_BUTTON_TOUCH_TARGET_OFFSET;
     }
 
@@ -1583,7 +1672,7 @@
         // 2. Get offset from strip stacker.
         float offset = mStripStacker.computeNewTabButtonOffset(mStripTabs, mTabOverlapWidth,
                 mLeftMargin, mRightMargin, mWidth, mNewTabButtonWidth,
-                NEW_TAB_BUTTON_TOUCH_TARGET_OFFSET, mCachedTabWidth, animate);
+                Math.abs(getNewTabButtonTouchTargetOffset()), mCachedTabWidth, animate);
 
         // 3. Hide the new tab button if it's not visible on the screen.
         boolean isRtl = LocalizationUtils.isLayoutRtl();
@@ -1601,7 +1690,6 @@
         } else {
             mNewTabButton.setX(offset);
         }
-
         return null;
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/TabStripSceneLayer.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/TabStripSceneLayer.java
index 99d4af1..94d2b2b9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/TabStripSceneLayer.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/TabStripSceneLayer.java
@@ -133,13 +133,12 @@
         CompositorButton modelSelectorButton = layoutHelper.getModelSelectorButton();
         boolean newTabButtonVisible = newTabButton.isVisible();
         boolean modelSelectorButtonVisible = modelSelectorButton.isVisible();
-
         TabStripSceneLayerJni.get().updateNewTabButton(mNativePtr, TabStripSceneLayer.this,
-                newTabButton.getResourceId(), newTabButton.getX() * mDpToPx,
-                newTabButton.getY() * mDpToPx, newTabButton.getWidth() * mDpToPx,
-                newTabButton.getHeight() * mDpToPx,
+                newTabButton.getResourceId(), newTabButton.getBackgroundResourceId(),
+                newTabButton.getX() * mDpToPx, newTabButton.getY() * mDpToPx,
                 layoutHelper.getNewTabBtnTouchTargetOffset() * mDpToPx, newTabButtonVisible,
-                newTabButton.getTint(), newTabButton.getOpacity(), resourceManager);
+                newTabButton.getTint(), newTabButton.getBackgroundTint(), newTabButton.getOpacity(),
+                resourceManager);
 
         TabStripSceneLayerJni.get().updateModelSelectorButton(mNativePtr, TabStripSceneLayer.this,
                 modelSelectorButton.getResourceId(), modelSelectorButton.getX() * mDpToPx,
@@ -218,8 +217,8 @@
         void updateStripScrim(long nativeTabStripSceneLayer, TabStripSceneLayer caller, float x,
                 float y, float width, float height, int color, float alpha);
         void updateNewTabButton(long nativeTabStripSceneLayer, TabStripSceneLayer caller,
-                int resourceId, float x, float y, float width, float height,
-                float touchTargetOffset, boolean visible, int tint, float buttonAlpha,
+                int resourceId, int backgroundResourceId, float x, float y, float touchTargetOffset,
+                boolean visible, int tint, int backgroundTint, float buttonAlpha,
                 ResourceManager resourceManager);
         void updateModelSelectorButton(long nativeTabStripSceneLayer, TabStripSceneLayer caller,
                 int resourceId, float x, float y, float width, float height, boolean incognito,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/PictureInPictureActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/media/PictureInPictureActivity.java
index a0878cf1..2b17cad 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/PictureInPictureActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/PictureInPictureActivity.java
@@ -365,13 +365,22 @@
 
     private class InitiatorTabObserver extends EmptyTabObserver {
         @Override
+        public void onClosingStateChanged(Tab tab, boolean closing) {
+            if (closing) {
+                PictureInPictureActivity.this.onExitPictureInPicture(/*closeByNative=*/false);
+            }
+        }
+
+        @Override
         public void onDestroyed(Tab tab) {
-            if (tab.isClosing()) PictureInPictureActivity.this.finish();
+            if (tab.isClosing()) {
+                PictureInPictureActivity.this.onExitPictureInPicture(/*closeByNative=*/false);
+            }
         }
 
         @Override
         public void onCrash(Tab tab) {
-            PictureInPictureActivity.this.finish();
+            PictureInPictureActivity.this.onExitPictureInPicture(/*closeByNative=*/false);
         }
     }
 
@@ -465,7 +474,7 @@
         // or InitiatorTab has been destroyed by user or crashed.
         if (mNativeOverlayWindowAndroid != sPendingNativeOverlayWindowAndroid
                 || TabUtils.getActivity(mInitiatorTab) == null) {
-            this.finish();
+            onExitPictureInPicture(/*closeByNative=*/false);
             return;
         }
         sPendingNativeOverlayWindowAndroid = 0;
@@ -497,14 +506,35 @@
     }
 
     @Override
-    public void onStop() {
-        super.onStop();
-        if (mCompositorView != null) mCompositorView.destroy();
+    @RequiresApi(api = Build.VERSION_CODES.O)
+    public void onPictureInPictureModeChanged(
+            boolean isInPictureInPictureMode, Configuration newConfig) {
+        super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
+        if (isInPictureInPictureMode) return;
+        PictureInPictureActivityJni.get().onBackToTab(mNativeOverlayWindowAndroid);
+        onExitPictureInPicture(/*closeByNative=*/false);
     }
 
     @Override
-    public void onDestroy() {
-        super.onDestroy();
+    protected ActivityWindowAndroid createWindowAndroid() {
+        return new ActivityWindowAndroid(
+                this, /* listenToActivityState= */ true, getIntentRequestTracker());
+    }
+
+    @CalledByNative
+    public void close() {
+        onExitPictureInPicture(/*closeByNative=*/true);
+    }
+
+    private void onExitPictureInPicture(boolean closeByNative) {
+        if (!closeByNative && mNativeOverlayWindowAndroid != 0) {
+            PictureInPictureActivityJni.get().destroy(mNativeOverlayWindowAndroid);
+        }
+
+        if (mCompositorView != null) {
+            mCompositorView.destroy();
+            mCompositorView = null;
+        }
 
         if (mMediaSessionReceiver != null) {
             unregisterReceiver(mMediaSessionReceiver);
@@ -516,26 +546,7 @@
             mInitiatorTab = null;
         }
         mTabObserver = null;
-    }
 
-    @Override
-    @RequiresApi(api = Build.VERSION_CODES.O)
-    public void onPictureInPictureModeChanged(
-            boolean isInPictureInPictureMode, Configuration newConfig) {
-        super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
-        if (isInPictureInPictureMode) return;
-        PictureInPictureActivityJni.get().onBackToTab(mNativeOverlayWindowAndroid);
-        this.finish();
-    }
-
-    @Override
-    protected ActivityWindowAndroid createWindowAndroid() {
-        return new ActivityWindowAndroid(
-                this, /* listenToActivityState= */ true, getIntentRequestTracker());
-    }
-
-    @CalledByNative
-    public void close() {
         this.finish();
     }
 
@@ -682,7 +693,7 @@
             PictureInPictureActivity pipActivity = (PictureInPictureActivity) activity;
             if (nativeOverlayWindowAndroid == pipActivity.getNativeOverlayWindowAndroid()) {
                 pipActivity.resetNativeOverlayWindowAndroid();
-                pipActivity.finish();
+                pipActivity.onExitPictureInPicture(/*closeByNative=*/true);
             }
         }
     }
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 3a7c250..032140e70 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
@@ -11,7 +11,10 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Bundle;
+import android.util.ArrayMap;
+import android.util.SizeF;
 import android.widget.RemoteViews;
 
 import androidx.annotation.NonNull;
@@ -32,6 +35,9 @@
 import org.chromium.chrome.browser.ui.searchactivityutils.SearchActivityPreferencesManager.SearchActivityPreferences;
 import org.chromium.components.embedder_support.util.UrlConstants;
 
+import java.util.ArrayList;
+import java.util.Map;
+
 /**
  * {@link AppWidgetProvider} for a widget that provides an entry point for users to quickly perform
  * actions in Chrome.
@@ -45,15 +51,10 @@
             extends QuickActionSearchWidgetProvider {
         @Override
         @NonNull
-        RemoteViews getRemoteViews(@NonNull Context context,
-                @NonNull SearchActivityPreferences prefs, @NonNull AppWidgetManager manager,
-                int widgetId) {
-            Bundle options = manager.getAppWidgetOptions(widgetId);
-            return getDelegate().createSearchWidgetRemoteViews(context, prefs,
-                    getPortraitModeTargetAreaWidth(options),
-                    getPortraitModeTargetAreaHeight(options),
-                    getLandscapeModeTargetAreaWidth(options),
-                    getLandscapeModeTargetAreaHeight(options));
+        RemoteViews createWidget(@NonNull Context context, @NonNull SearchActivityPreferences prefs,
+                int areaWidthDp, int areaHeightDp) {
+            return getDelegate().createSearchWidgetRemoteViews(
+                    context, prefs, areaWidthDp, areaHeightDp);
         }
     }
 
@@ -85,15 +86,10 @@
             extends QuickActionSearchWidgetProvider {
         @Override
         @NonNull
-        RemoteViews getRemoteViews(@NonNull Context context,
-                @NonNull SearchActivityPreferences prefs, @NonNull AppWidgetManager manager,
-                int widgetId) {
-            Bundle options = manager.getAppWidgetOptions(widgetId);
-            return getDelegate().createDinoWidgetRemoteViews(context, prefs,
-                    getPortraitModeTargetAreaWidth(options),
-                    getPortraitModeTargetAreaHeight(options),
-                    getLandscapeModeTargetAreaWidth(options),
-                    getLandscapeModeTargetAreaHeight(options));
+        RemoteViews createWidget(@NonNull Context context, @NonNull SearchActivityPreferences prefs,
+                int areaWidthDp, int areaHeightDp) {
+            return getDelegate().createDinoWidgetRemoteViews(
+                    context, prefs, areaWidthDp, areaHeightDp);
         }
     }
 
@@ -130,8 +126,8 @@
 
         for (int index = 0; index < widgetIds.length; index++) {
             int widgetId = widgetIds[index];
-            manager.updateAppWidget(
-                    widgetId, getRemoteViews(context, preferences, manager, widgetId));
+            Bundle options = manager.getAppWidgetOptions(widgetId);
+            manager.updateAppWidget(widgetId, getRemoteViews(context, preferences, options));
         }
     }
 
@@ -182,18 +178,95 @@
     }
 
     /**
-     * Acquire screen orientation specific layouts that will be applied to the
-     * widget.
-     * The two layouts represent screen orientations in Landscape and Portrait mode.
+     * Construct the widget for specific dimensions.
      *
      * @param context Current context.
-     * @param manager The AppWidgetManager instance to query widget info.
-     * @param widgetId The widget to get the delegate for.
+     * @param prefs Widget settings and feature availability.
+     * @param areaWidthDp The width of the widget area, expressed in Dp.
+     * @param areaHeightDp The height of the widget area, expressed in Dp.
+     * @return RemoteViews description for a single widget layout.
      */
     @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
-    abstract @NonNull RemoteViews getRemoteViews(@NonNull Context context,
-            @NonNull SearchActivityPreferences prefs, @NonNull AppWidgetManager manager,
-            int widgetId);
+    @NonNull
+    abstract RemoteViews createWidget(@NonNull Context context,
+            @NonNull SearchActivityPreferences prefs, int areaWidthDp, int areaHeightDp);
+
+    /**
+     * Acquire the RemoteViews that represent the widget.
+     *
+     * @param context Current context.
+     * @param prefs Widget settings and feature availability.
+     * @param options Options bundle passed by AppWidgetManager.
+     */
+    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+    @NonNull
+    RemoteViews getRemoteViews(@NonNull Context context, @NonNull SearchActivityPreferences prefs,
+            @NonNull Bundle options) {
+        var views = getSizeMappedRemoteViews(context, prefs, options);
+        if (views != null) {
+            return views;
+        }
+        return getOrientationSpecificRemoteViews(context, prefs, options);
+    }
+
+    /**
+     * Acquire screen orientation specific layouts that will be applied to the
+     * widget.
+     *
+     * @param context Current context.
+     * @param prefs Widget settings and feature availability.
+     * @param options Widget parameters passed by the AppWidgetManager.
+     * @return RemoteViews describing widget for landscape and portrait screen orientations.
+     */
+    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+    @NonNull
+    RemoteViews getOrientationSpecificRemoteViews(@NonNull Context context,
+            @NonNull SearchActivityPreferences prefs, @NonNull Bundle options) {
+        var portraitViews = createWidget(context, prefs, getPortraitModeTargetAreaWidth(options),
+                getPortraitModeTargetAreaHeight(options));
+
+        var landscapeViews = createWidget(context, prefs, getLandscapeModeTargetAreaWidth(options),
+                getLandscapeModeTargetAreaHeight(options));
+
+        return new RemoteViews(landscapeViews, portraitViews);
+    }
+
+    /**
+     * Acquire size-specific layouts that will be applied to the widget.
+     *
+     * @param context Current context.
+     * @param prefs Widget settings and feature availability.
+     * @param options Widget parameters passed by the AppWidgetManager.
+     * @return RemoteViews describing widget for all sizes requested by the AppWidgetManager, or
+     *         null, if the AppWidgetManager did not specify the sizes.
+     */
+    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+    @Nullable
+    RemoteViews getSizeMappedRemoteViews(@NonNull Context context,
+            @NonNull SearchActivityPreferences prefs, @NonNull Bundle options) {
+        // On Android S and above, attempt to build widget from supplied array of sizes.
+        // This is reserved to Android S because appropriate RemoteViews constructor may not be
+        // available.
+        // Note that the creation may still fail, if the launcher is unable to offer appropriate
+        // details.
+        // Check for supported system version.
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
+            return null;
+        }
+
+        ArrayList<SizeF> sizes =
+                options.getParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES);
+        if (sizes == null || sizes.isEmpty()) {
+            return null;
+        }
+        Map<SizeF, RemoteViews> mappings = new ArrayMap<>();
+
+        for (var size : sizes) {
+            mappings.put(size,
+                    createWidget(context, prefs, (int) size.getWidth(), (int) size.getHeight()));
+        }
+        return new RemoteViews(mappings);
+    }
 
     /**
      * This function initializes the QuickActionSearchWidgetProvider component. Namely, this
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/share/LensUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/share/LensUtils.java
index 9bd0e52..2219902b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/share/LensUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/share/LensUtils.java
@@ -158,7 +158,7 @@
     public static boolean isGoogleLensFeatureEnabledOnTablet() {
         return ChromeFeatureList.getFieldTrialParamByFeatureAsBoolean(
                 ChromeFeatureList.CONTEXT_MENU_SEARCH_WITH_GOOGLE_LENS, ENABLE_ON_TABLET_PARAM_NAME,
-                false);
+                true);
     }
 
     /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/media/PictureInPictureActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/media/PictureInPictureActivityTest.java
index 6fd7128..4f3e32b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/media/PictureInPictureActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/media/PictureInPictureActivityTest.java
@@ -297,12 +297,22 @@
         testExitOn(activity, () -> activity.close());
     }
 
+    @Test
+    @MediumTest
+    @MinAndroidSdkLevel(Build.VERSION_CODES.O)
+    @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE)
+    public void testNotifyNativeWhenTabClose() throws Throwable {
+        PictureInPictureActivity activity = startPictureInPictureActivity();
+        testExitOn(activity, () -> mTab.setClosing(/*closing=*/true));
+        verify(mNativeMock, times(1)).destroy(NATIVE_OVERLAY);
+    }
+
     private WebContents getWebContents() {
         return mActivityTestRule.getActivity().getCurrentWebContents();
     }
 
     private void testExitOn(Activity activity, Runnable runnable) throws Throwable {
-        runnable.run();
+        TestThreadUtils.runOnUiThreadBlocking(() -> runnable.run());
 
         CriteriaHelper.pollUiThread(() -> {
             Criteria.checkThat(activity == null || activity.isDestroyed(), Matchers.is(true));
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java
index ca0d2d3..455405e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java
@@ -25,7 +25,6 @@
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.CriteriaHelper;
-import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.DoNotBatch;
 import org.chromium.base.test.util.JniMocker;
@@ -223,7 +222,6 @@
      */
     @Test
     @LargeTest
-    @DisableIf.Build(supported_abis_includes = "x86", message = "https://crbug.com/1062055")
     public void testErrorPageNotRecorded() throws Exception {
         runAndWaitForPageLoadMetricsRecorded(
                 () -> mTabbedActivityTestRule.startMainActivityWithURL(mErrorPage));
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentControllerTest.java
index ee759ab..263036b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentControllerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentControllerTest.java
@@ -111,29 +111,11 @@
 
     @Test
     @MediumTest
-    @DisableFeatures(ChromeFeatureList.ASSISTANT_CONSENT_MODAL)
     public void testNoBottomSheetControllerAvailable() {
         ChromeTabbedActivity cta = mActivityTestRule.getActivity();
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            AssistantVoiceSearchConsentController.show(cta.getWindowAndroid(),
-                    mSharedPreferencesManager,
-                    () -> {}, null, cta.getWindowAndroid().getModalDialogManager(), mCallback);
-        });
-        Mockito.verify(mCallback, Mockito.timeout(1000)).onResult(false);
-    }
-
-    @Test
-    @MediumTest
-    @EnableFeatures(ChromeFeatureList.ASSISTANT_CONSENT_MODAL)
-    public void testNoModalDialogManagerAvailable() {
-        ChromeTabbedActivity cta = mActivityTestRule.getActivity();
-        TestThreadUtils.runOnUiThreadBlocking(() -> {
-            AssistantVoiceSearchConsentController.show(cta.getWindowAndroid(),
-                    mSharedPreferencesManager,
-                    ()
-                            -> {},
-                    cta.getRootUiCoordinatorForTesting().getBottomSheetController(), null,
-                    mCallback);
+            AssistantVoiceSearchConsentController.show(
+                    cta.getWindowAndroid(), mSharedPreferencesManager, () -> {}, null, mCallback);
         });
         Mockito.verify(mCallback, Mockito.timeout(1000)).onResult(false);
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentModalTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentModalTest.java
deleted file mode 100644
index 9b47402..0000000
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentModalTest.java
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.omnibox.voice;
-
-import static androidx.test.espresso.Espresso.onView;
-import static androidx.test.espresso.action.ViewActions.click;
-import static androidx.test.espresso.matcher.ViewMatchers.withId;
-
-import androidx.test.filters.MediumTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import org.chromium.base.test.util.CommandLineFlags;
-import org.chromium.chrome.R;
-import org.chromium.chrome.browser.flags.ChromeSwitches;
-import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
-import org.chromium.content_public.browser.test.util.TestThreadUtils;
-import org.chromium.ui.base.WindowAndroid;
-import org.chromium.ui.modaldialog.DialogDismissalCause;
-import org.chromium.ui.modaldialog.ModalDialogManager;
-
-/** Tests for AssistantVoiceSearchConsentDialog */
-@RunWith(ChromeJUnit4ClassRunner.class)
-@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
-public class AssistantVoiceSearchConsentModalTest {
-    @Rule
-    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
-
-    @Rule
-    public MockitoRule mMockitoRule = MockitoJUnit.rule();
-
-    @Mock
-    AssistantVoiceSearchConsentUi.Observer mObserver;
-
-    ModalDialogManager mModalDialogManager;
-    AssistantVoiceSearchConsentModal mModal;
-
-    @Before
-    public void setUp() {
-        mActivityTestRule.startMainActivityOnBlankPage();
-
-        TestThreadUtils.runOnUiThreadBlocking(() -> {
-            WindowAndroid wa = mActivityTestRule.getActivity().getWindowAndroid();
-            mModalDialogManager = wa.getModalDialogManager();
-            mModal = new AssistantVoiceSearchConsentModal(
-                    wa.getContext().get(), mModalDialogManager);
-        });
-    }
-
-    @After
-    public void tearDown() {}
-
-    private void showModal() {
-        TestThreadUtils.runOnUiThreadBlocking(() -> { mModal.show(mObserver); });
-    }
-
-    @Test
-    @MediumTest
-    public void testAcceptButton() {
-        showModal();
-
-        onView(withId(org.chromium.chrome.R.id.positive_button)).perform(click());
-
-        Mockito.verify(mObserver, Mockito.timeout(1000)).onConsentAccepted();
-    }
-
-    @Test
-    @MediumTest
-    public void testRejectButton() {
-        showModal();
-
-        onView(withId(org.chromium.chrome.R.id.negative_button)).perform(click());
-
-        Mockito.verify(mObserver, Mockito.timeout(1000)).onConsentRejected();
-    }
-
-    @Test
-    @MediumTest
-    public void testLearnMoreButton() {
-        showModal();
-
-        onView(withId(R.id.avs_consent_ui_learn_more)).perform(click());
-
-        Mockito.verify(mObserver, Mockito.timeout(1000)).onLearnMoreClicked();
-    }
-
-    @Test
-    @MediumTest
-    public void testCancel() {
-        showModal();
-
-        TestThreadUtils.runOnUiThreadBlocking(() -> {
-            mModalDialogManager.dismissAllDialogs(
-                    DialogDismissalCause.NAVIGATE_BACK_OR_TOUCH_OUTSIDE);
-        });
-
-        Mockito.verify(mObserver, Mockito.timeout(1000)).onConsentCanceled();
-    }
-
-    @Test
-    @MediumTest
-    public void testDismiss() {
-        showModal();
-
-        TestThreadUtils.runOnUiThreadBlocking(() -> { mModal.dismiss(); });
-
-        Mockito.verifyNoMoreInteractions(mObserver);
-    }
-
-    @Test
-    @MediumTest
-    public void testNonUserCancel() {
-        showModal();
-
-        TestThreadUtils.runOnUiThreadBlocking(() -> {
-            mModalDialogManager.dismissAllDialogs(DialogDismissalCause.ACTION_ON_CONTENT);
-        });
-
-        Mockito.verify(mObserver, Mockito.timeout(1000)).onNonUserCancel();
-    }
-}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperTest.java
index 4978ed65..f69c169 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperTest.java
@@ -123,7 +123,8 @@
         when(mModelSelectorBtn.isVisible()).thenReturn(true);
         when(mTabGroupModelFilter.hasOtherRelatedTabs(any())).thenReturn(false);
 
-        mActivity = Robolectric.buildActivity(Activity.class).setup().get();
+        mActivity = Robolectric.setupActivity(Activity.class);
+        mActivity.setTheme(org.chromium.chrome.R.style.Theme_BrowserUI);
         TabUiFeatureUtilities.setTabMinWidthForTesting(190.f);
     }
 
@@ -1450,6 +1451,7 @@
 
     @Test
     @Feature("Tab Groups on Tab Strip")
+    @Features.DisableFeatures(ChromeFeatureList.TAB_STRIP_REDESIGN)
     public void testReorder_ExtraMinScroll() {
         // Mock 3 tabs. Group the first two tabs.
         initializeTest(false, false, true, 0, 3);
@@ -1478,7 +1480,7 @@
 
         // Assert: New tab button position before starting tab closure.
         mStripLayoutHelper.updateLayout(TIMESTAMP);
-        assertEquals("Unexpected starting newTabButton position.", 764.f,
+        assertEquals("Unexpected starting newTabButton position.", 743.f,
                 mStripLayoutHelper.getNewTabButton().getX(), 0.0f);
 
         // Act: Call on close tab button handler.
@@ -1500,7 +1502,7 @@
                 mStripLayoutHelper.getStripLayoutTabs().length);
         assertTrue("MultiStepAnimations should still be running.",
                 mStripLayoutHelper.isMultiStepCloseAnimationsRunning());
-        assertEquals("NewTabButton should not have moved.", 764.f,
+        assertEquals("NewTabButton should not have moved.", 743.f,
                 mStripLayoutHelper.getNewTabButton().getX(), 0.0f);
 
         // Act: End next set of animations to apply final values.
@@ -1508,14 +1510,14 @@
 
         // Assert: Animations completed. The tab width is not resized and drawX does not change.
         float expectedDrawX =
-                -460.f; // Since we are focused on the last tab, start tabs are off screen.
+                -474.f; // Since we are focused on the last tab, start tabs are off screen.
         StripLayoutTab[] updatedTabs = mStripLayoutHelper.getStripLayoutTabs();
         for (StripLayoutTab stripTab : updatedTabs) {
             assertEquals("Unexpected tab width after resize.", 156.f, stripTab.getWidth(), 0.0);
             assertEquals("Unexpected tab position.", expectedDrawX, stripTab.getDrawX(), 0.0);
             expectedDrawX += (TAB_WIDTH_MEDIUM - TAB_OVERLAP_WIDTH);
         }
-        assertEquals("NewTabButton should not have moved.", 764.f,
+        assertEquals("NewTabButton should not have moved.", 743.f,
                 mStripLayoutHelper.getNewTabButton().getX(), 0.0f);
         assertFalse("MultiStepAnimations should have stopped running.",
                 mStripLayoutHelper.isMultiStepCloseAnimationsRunning());
@@ -1535,7 +1537,7 @@
 
         // Assert: New tab button position before starting tab closure.
         mStripLayoutHelper.updateLayout(TIMESTAMP);
-        assertEquals("Unexpected starting newTabButton position.", 764.f,
+        assertEquals("Unexpected starting newTabButton position.", 743.f,
                 mStripLayoutHelper.getNewTabButton().getX(), 0.0f);
 
         // Act: Call on close tab button handler.
@@ -1556,7 +1558,7 @@
         assertEquals(expectedTabCount, mStripLayoutHelper.getStripLayoutTabs().length);
         assertTrue("MultiStepAnimations should still be running.",
                 mStripLayoutHelper.isMultiStepCloseAnimationsRunning());
-        assertEquals("NewTabButton should not have moved.", 764.f,
+        assertEquals("NewTabButton should not have moved.", 743.f,
                 mStripLayoutHelper.getNewTabButton().getX(), 0.0f);
 
         // Act: Set animation time forward by 250ms for next set of animations.
@@ -1565,7 +1567,7 @@
         // Assert: Animations completed. The tab width is resized, tab.drawX is changed and
         // newTabButton.drawX is also changed.
         float expectedDrawX = 0.f;
-        float expectedWidthAfterResize = 265.f;
+        float expectedWidthAfterResize = 262.f;
         StripLayoutTab[] updatedTabs = mStripLayoutHelper.getStripLayoutTabs();
         for (int i = 0; i < updatedTabs.length; i++) {
             StripLayoutTab stripTab = updatedTabs[i];
@@ -1574,7 +1576,7 @@
             assertEquals("Unexpected tab position.", expectedDrawX, stripTab.getDrawX(), 0.0);
             expectedDrawX += (expectedWidthAfterResize - TAB_OVERLAP_WIDTH);
         }
-        assertEquals("NewTabButton position is incorrect.", 759.f,
+        assertEquals("NewTabButton position is incorrect.", 743.f,
                 mStripLayoutHelper.getNewTabButton().getX(), 0.0f);
         assertFalse("MultiStepAnimations should have ended.",
                 mStripLayoutHelper.isMultiStepCloseAnimationsRunning());
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/quickactionsearchwidget/QuickActionSearchWidgetProviderTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/quickactionsearchwidget/QuickActionSearchWidgetProviderTest.java
index 9cdaf6a..3a9587f 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/quickactionsearchwidget/QuickActionSearchWidgetProviderTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/quickactionsearchwidget/QuickActionSearchWidgetProviderTest.java
@@ -6,6 +6,8 @@
 
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -13,8 +15,9 @@
 import android.appwidget.AppWidgetManager;
 import android.content.Context;
 import android.content.Intent;
+import android.os.Build;
 import android.os.Bundle;
-import android.widget.RemoteViews;
+import android.util.SizeF;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.SmallTest;
@@ -25,81 +28,202 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowLog;
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
-import org.chromium.chrome.browser.firstrun.FirstRunStatus;
-import org.chromium.chrome.browser.ui.quickactionsearchwidget.QuickActionSearchWidgetProviderDelegate;
+import org.chromium.chrome.browser.quickactionsearchwidget.QuickActionSearchWidgetProvider.QuickActionSearchWidgetProviderDino;
 import org.chromium.chrome.browser.ui.searchactivityutils.SearchActivityPreferencesManager.SearchActivityPreferences;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+
 /**
  * Tests for the (@link QuickActionSearchWidgetProvider}.
  */
 @RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE, shadows = {ShadowLog.class})
 public class QuickActionSearchWidgetProviderTest {
+    private static final int WIDGET_A_ID = 123;
+    private static final int WIDGET_B_ID = 987;
     // These are random unique identifiers, the value of these numbers have no special meaning.
     // The number of identifiers has no particular meaning either.
-    private static final int[] WIDGET_IDS = {1, 2};
+    private static final int[] WIDGET_IDS = {WIDGET_A_ID, WIDGET_B_ID};
+    public @Rule MockitoRule mMockitoRule = MockitoJUnit.rule();
+    private @Mock AppWidgetManager mAppWidgetManagerMock;
 
-    /**
-     * A sub class of {@link QuickActionSearchWidgetProvider} for testing, since
-     * QuickActionSearchWidgetProvider is abstract.
-     */
-    private class TestProvider extends QuickActionSearchWidgetProvider {
-        @Override
-        RemoteViews getRemoteViews(Context context, SearchActivityPreferences prefs,
-                AppWidgetManager manager, int widgetId) {
-            return mRemoteViews;
-        }
-
-        @Override
-        protected QuickActionSearchWidgetProviderDelegate getDelegate() {
-            return mDelegateMock;
-        }
-    }
-
-    @Rule
-    public MockitoRule mMockitoRule = MockitoJUnit.rule();
-    @Mock
-    private AppWidgetManager mAppWidgetManagerMock;
-    @Mock
-    private QuickActionSearchWidgetProviderDelegate mDelegateMock;
-    @Mock
-    private Bundle mBundleMock;
-    @Mock
-    private RemoteViews mRemoteViews;
-
+    private SearchActivityPreferences mPreferences;
     private QuickActionSearchWidgetProvider mWidgetProvider;
     private Context mContext;
+    private Intent mIntent;
+    private Bundle mOptionsWidgetA;
+    private Bundle mOptionsWidgetB;
 
     @Before
     public void setUp() {
-        FirstRunStatus.setFirstRunFlowComplete(true);
-        MockitoAnnotations.initMocks(this);
+        ShadowLog.stream = System.out;
         mContext = Mockito.spy(ApplicationProvider.getApplicationContext());
+        mOptionsWidgetA = new Bundle();
+        mOptionsWidgetB = new Bundle();
+        mPreferences = new SearchActivityPreferences(
+                "Search Engine", "https://search.engine.com", true, true, true);
 
         // Inflate an actual RemoteViews to avoid stubbing internal methods or making
         // any other assumptions about the class.
-        mWidgetProvider = Mockito.spy(new TestProvider());
+        mWidgetProvider = Mockito.spy(new QuickActionSearchWidgetProviderDino());
         when(mContext.getSystemService(Context.APPWIDGET_SERVICE))
                 .thenReturn(mAppWidgetManagerMock);
-        when(mAppWidgetManagerMock.getAppWidgetOptions(anyInt())).thenReturn(mBundleMock);
+        when(mAppWidgetManagerMock.getAppWidgetOptions(eq(WIDGET_A_ID)))
+                .thenReturn(mOptionsWidgetA);
+        when(mAppWidgetManagerMock.getAppWidgetOptions(eq(WIDGET_B_ID)))
+                .thenReturn(mOptionsWidgetB);
+
+        // Blanket intent that defines which widget IDs we would be testing here.
+        mIntent = new Intent();
+        mIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, WIDGET_IDS);
+    }
+
+    private void updateReportedWidgetSizes(Bundle options, SizeF portrait, SizeF landscape) {
+        // - Portrait mode is narrow and tall, hence MIN_WIDTH and MAX_HEIGHT.
+        // - Landscape mode is wide and short, hence MAX_WIDTH and MIN_HEIGHT.
+        options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, (int) portrait.getWidth());
+        options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, (int) portrait.getHeight());
+        options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, (int) landscape.getWidth());
+        options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, (int) landscape.getHeight());
     }
 
     @Test
     @SmallTest
-    public void testAppWidgetUpdateInvokesUpdateWidgets() {
-        Intent appWidgetUpdateIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
-        appWidgetUpdateIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, WIDGET_IDS);
+    public void testAppWidgetInstallationCreatesWidgets() {
+        mIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
+        updateReportedWidgetSizes(mOptionsWidgetA, new SizeF(80, 80), new SizeF(400, 40));
+        updateReportedWidgetSizes(mOptionsWidgetB, new SizeF(30, 10), new SizeF(100, 30));
+        mWidgetProvider.onReceive(mContext, mIntent);
 
-        mWidgetProvider.onReceive(mContext, appWidgetUpdateIntent);
+        // There are 2 fake widgets to be created, and each should be created for portrait and
+        // landscape screen orientation.
+        // Widget A, Portrait.
+        verify(mWidgetProvider).createWidget(any(), any(), eq(80), eq(80));
+        // Widget A, Landscape.
+        verify(mWidgetProvider).createWidget(any(), any(), eq(400), eq(40));
+        // Widget B, Portrait.
+        verify(mWidgetProvider).createWidget(any(), any(), eq(30), eq(10));
+        // Widget B, Landscape.
+        verify(mWidgetProvider).createWidget(any(), any(), eq(100), eq(30));
+        // 4 total, no more.
+        verify(mWidgetProvider, times(4)).createWidget(any(), any(), anyInt(), anyInt());
+    }
 
-        verify(mWidgetProvider, times(1)).onUpdate(mContext, mAppWidgetManagerMock, WIDGET_IDS);
-        verify(mWidgetProvider, times(1)).onUpdate(any(), any(), any());
+    @Test
+    @SmallTest
+    public void testAppWidgetResizeUpdatesWidgets() {
+        updateReportedWidgetSizes(mOptionsWidgetA, new SizeF(80, 80), new SizeF(400, 40));
+        updateReportedWidgetSizes(mOptionsWidgetB, new SizeF(30, 10), new SizeF(100, 30));
+        mIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
+        mWidgetProvider.onReceive(mContext, mIntent);
+
+        verify(mWidgetProvider).createWidget(any(), any(), eq(80), eq(80));
+        verify(mWidgetProvider).createWidget(any(), any(), eq(400), eq(40));
+        verify(mWidgetProvider).createWidget(any(), any(), eq(30), eq(10));
+        verify(mWidgetProvider).createWidget(any(), any(), eq(100), eq(30));
+        verify(mWidgetProvider, times(4)).createWidget(any(), any(), anyInt(), anyInt());
+
+        clearInvocations(mWidgetProvider);
+
+        // OPTIONS_CHANGED specifies which particular widget needs to be updated. Make sure we
+        // reflect that here. Below, we flip the sizes so that Widget B uses sizes of Widget A and
+        // vice versa.
+        when(mAppWidgetManagerMock.getAppWidgetOptions(eq(WIDGET_A_ID)))
+                .thenReturn(mOptionsWidgetB);
+        when(mAppWidgetManagerMock.getAppWidgetOptions(eq(WIDGET_B_ID)))
+                .thenReturn(mOptionsWidgetA);
+
+        // First, resize Widget A with old Widget B size specs.
+        mIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED);
+        mIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, WIDGET_A_ID);
+        mIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, mOptionsWidgetB);
+        mWidgetProvider.onReceive(mContext, mIntent);
+
+        verify(mWidgetProvider).createWidget(any(), any(), eq(30), eq(10));
+        verify(mWidgetProvider).createWidget(any(), any(), eq(100), eq(30));
+        verify(mWidgetProvider, times(2)).createWidget(any(), any(), anyInt(), anyInt());
+        clearInvocations(mWidgetProvider);
+
+        // Next, resize Widget B with old Widget A size specs.
+        mIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, WIDGET_B_ID);
+        mIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, mOptionsWidgetA);
+        mWidgetProvider.onReceive(mContext, mIntent);
+        verify(mWidgetProvider).createWidget(any(), any(), eq(80), eq(80));
+        verify(mWidgetProvider).createWidget(any(), any(), eq(400), eq(40));
+        verify(mWidgetProvider, times(2)).createWidget(any(), any(), anyInt(), anyInt());
+    }
+
+    @Test
+    @SmallTest
+    @Config(sdk = Build.VERSION_CODES.S)
+    public void testCreateWidgetsFromFallbackValues_missingSizes() {
+        updateReportedWidgetSizes(mOptionsWidgetA, new SizeF(80, 80), new SizeF(400, 40));
+        mWidgetProvider.getRemoteViews(mContext, mPreferences, mOptionsWidgetA);
 
         // There are 2 fake widgets that we work with, so expect both being evaluated
-        verify(mWidgetProvider, times(2)).getRemoteViews(any(), any(), any(), anyInt());
+        // One for portrait mode (max_width, min_height) and
+        // One for landscape mode (min_width, max_height).
+        verify(mWidgetProvider).createWidget(any(), any(), eq(400), eq(40));
+        verify(mWidgetProvider).createWidget(any(), any(), eq(80), eq(80));
+        verify(mWidgetProvider, times(2)).createWidget(any(), any(), anyInt(), anyInt());
+    }
+
+    @Test
+    @SmallTest
+    @Config(sdk = Build.VERSION_CODES.S)
+    public void testCreateWidgetFromFallbackValues_emptySizes() {
+        updateReportedWidgetSizes(mOptionsWidgetA, new SizeF(80, 80), new SizeF(400, 40));
+        // Lastly, set the empty array of sizes.
+        mOptionsWidgetA.putParcelableArrayList(
+                AppWidgetManager.OPTION_APPWIDGET_SIZES, new ArrayList<SizeF>());
+        mWidgetProvider.getRemoteViews(mContext, mPreferences, mOptionsWidgetA);
+
+        // There are 2 fake widgets that we work with, so expect both being evaluated
+        // One for portrait mode (max_width, min_height) and
+        // One for landscape mode (min_width, max_height).
+        verify(mWidgetProvider).createWidget(any(), any(), eq(400), eq(40));
+        verify(mWidgetProvider).createWidget(any(), any(), eq(80), eq(80));
+        verify(mWidgetProvider, times(2)).createWidget(any(), any(), anyInt(), anyInt());
+    }
+
+    @Test
+    @SmallTest
+    @Config(sdk = Build.VERSION_CODES.S)
+    public void testCreateWidgetFromSizeSpecs() {
+        updateReportedWidgetSizes(mOptionsWidgetA, new SizeF(80, 80), new SizeF(400, 40));
+        // Lastly, set a different array of sizes and confirm it is used instead.
+        mOptionsWidgetA.putParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES,
+                new ArrayList<SizeF>(Arrays.asList(new SizeF(50, 50))));
+        mWidgetProvider.getRemoteViews(mContext, mPreferences, mOptionsWidgetA);
+
+        // Only one call is expected here, because we declare only one size in our list.
+        verify(mWidgetProvider).createWidget(any(), any(), eq(50), eq(50));
+        verify(mWidgetProvider, times(1)).createWidget(any(), any(), anyInt(), anyInt());
+    }
+
+    @Test
+    @SmallTest
+    @Config(sdk = Build.VERSION_CODES.R)
+    public void testCreateWidgetFromLegacyMeasurements() {
+        updateReportedWidgetSizes(mOptionsWidgetA, new SizeF(80, 80), new SizeF(400, 40));
+        // Define new sizes. These must be ignored, because RemoteViews constructor is not
+        // supported.
+        mOptionsWidgetA.putParcelableArrayList(AppWidgetManager.OPTION_APPWIDGET_SIZES,
+                new ArrayList<SizeF>(Arrays.asList(new SizeF(50, 50))));
+        mWidgetProvider.getRemoteViews(mContext, mPreferences, mOptionsWidgetA);
+
+        // There are 2 fake widgets that we work with, so expect both being evaluated
+        // One for portrait mode (max_width, min_height) and
+        // One for landscape mode (min_width, max_height).
+        verify(mWidgetProvider).createWidget(any(), any(), eq(400), eq(40));
+        verify(mWidgetProvider).createWidget(any(), any(), eq(80), eq(80));
+        verify(mWidgetProvider, times(2)).createWidget(any(), any(), anyInt(), anyInt());
     }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/share/LensUtilsTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/share/LensUtilsTest.java
index 1d63bb67..812ce7f 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/share/LensUtilsTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/share/LensUtilsTest.java
@@ -114,9 +114,9 @@
     }
 
     @Test
-    public void isGoogleLensFeatureEnabled_tabletDisabled() {
+    public void isGoogleLensFeatureEnabled_tabletEnabledByDefault() {
         configureFeature(ChromeFeatureList.CONTEXT_MENU_SEARCH_WITH_GOOGLE_LENS);
-        Assert.assertFalse("Feature incorrectly enabled when Lens on tablet was disabled",
+        Assert.assertTrue("Feature incorrectly disabled when Lens on tablet was enabled",
                 LensUtils.isGoogleLensFeatureEnabledOnTablet());
     }
 
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 5eccb867..19b282f 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -6674,6 +6674,9 @@
       <message name="IDS_NTP_CUSTOMIZE_NO_BACKGROUND_LABEL" desc="The label for the 'Classic Chrome' tile in the customization menu on the New Tab Page">
         Classic Chrome
       </message>
+      <message name="IDS_NTP_CUSTOMIZE_UPLOADED_IMAGE_LABEL" desc="The label for the 'Uploaded image' tile in the customization menu on the New Tab Page side panel.">
+        Uploaded image
+      </message>
       <message name="IDS_NTP_CUSTOMIZE_UPLOAD_FROM_DEVICE_LABEL" desc="The label for the 'Upload from device' tile in the customization menu on the New Tab Page">
         Upload from device
       </message>
@@ -13069,6 +13072,12 @@
       <message name="IDS_WEBAUTHN_ERROR_NO_TRANSPORTS_DESCRIPTION" desc="Description in the dialog shown when the user could not sign in to a web site, because their computer did not support any of the hardware-based authentication mechanisms desired by the web site.">
         This device doesn't support the type of security key requested by this website
       </message>
+      <message name="IDS_WEBAUTHN_ERROR_NO_PASSKEYS_TITLE" desc="Title of the dialog shown when the user could not sign in to a web site because their computer did not have any of the passkeys registered with the site.">
+        No passkeys available
+      </message>
+      <message name="IDS_WEBAUTHN_ERROR_NO_PASSKEYS_DESCRIPTION" desc="Description in the dialog shown when the user could not sign in to a web site because their computer did not have any of the passkeys registered with the site.">
+        There aren't any passkeys for <ph name="APP_NAME">$1<ex>google.com</ex></ph> on this device
+      </message>
       <message name="IDS_WEBAUTHN_BLUETOOTH_POWER_ON_AUTO_TITLE" desc="Title of the dialog informing the user that Chrome needs to turn on Bluetooth by itself so that security keys can be used over Bluetooth.">
         Turn on Bluetooth?
       </message>
diff --git a/chrome/app/generated_resources_grd/IDS_NTP_CUSTOMIZE_UPLOADED_IMAGE_LABEL.png.sha1 b/chrome/app/generated_resources_grd/IDS_NTP_CUSTOMIZE_UPLOADED_IMAGE_LABEL.png.sha1
new file mode 100644
index 0000000..ee9f695
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_NTP_CUSTOMIZE_UPLOADED_IMAGE_LABEL.png.sha1
@@ -0,0 +1 @@
+e91b85af596088e04af1f5e35e42e39dd5173dac
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_WEBAUTHN_ERROR_NO_PASSKEYS_DESCRIPTION.png.sha1 b/chrome/app/generated_resources_grd/IDS_WEBAUTHN_ERROR_NO_PASSKEYS_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..68e115f
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_WEBAUTHN_ERROR_NO_PASSKEYS_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+d66e3b8f10c2f6b6ecb34b20b33ff19a3028450d
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_WEBAUTHN_ERROR_NO_PASSKEYS_TITLE.png.sha1 b/chrome/app/generated_resources_grd/IDS_WEBAUTHN_ERROR_NO_PASSKEYS_TITLE.png.sha1
new file mode 100644
index 0000000..68e115f
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_WEBAUTHN_ERROR_NO_PASSKEYS_TITLE.png.sha1
@@ -0,0 +1 @@
+d66e3b8f10c2f6b6ecb34b20b33ff19a3028450d
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 44c56811..a6f5f77d 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -5590,6 +5590,8 @@
       "lacros/task_manager_lacros.h",
       "lacros/ui_metric_recorder_lacros.cc",
       "lacros/ui_metric_recorder_lacros.h",
+      "lacros/views_text_services_context_menu_lacros.cc",
+      "lacros/views_text_services_context_menu_lacros.h",
       "lacros/vpn_extension_tracker_lacros.cc",
       "lacros/vpn_extension_tracker_lacros.h",
       "lacros/web_app_provider_bridge_lacros.cc",
@@ -5661,6 +5663,7 @@
       "//ui/chromeos/strings:strings_grit",
       "//ui/chromeos/styles:cros_styles_views",
       "//ui/platform_window",
+      "//ui/strings:ui_strings_grit",
     ]
 
     if (enable_plugins) {
@@ -6393,6 +6396,8 @@
       "enterprise/idle/idle_service.h",
       "enterprise/idle/idle_service_factory.cc",
       "enterprise/idle/idle_service_factory.h",
+      "enterprise/idle/idle_timeout_policy_handler.cc",
+      "enterprise/idle/idle_timeout_policy_handler.h",
       "enterprise/remote_commands/rotate_attestation_credential_job.cc",
       "enterprise/remote_commands/rotate_attestation_credential_job.h",
       "enterprise/signals/signals_aggregator_factory.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index a4b2e2e..0810df9 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -4190,9 +4190,6 @@
      flag_descriptions::kVoiceButtonInTopToolbarName,
      flag_descriptions::kVoiceButtonInTopToolbarDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(chrome::android::kVoiceButtonInTopToolbar)},
-    {"assistant-consent-modal", flag_descriptions::kAssistantConsentModalName,
-     flag_descriptions::kAssistantConsentModalDescription, kOsAndroid,
-     FEATURE_VALUE_TYPE(chrome::android::kAssistantConsentModal)},
     {"assistant-consent-simplified-text",
      flag_descriptions::kAssistantConsentSimplifiedTextName,
      flag_descriptions::kAssistantConsentSimplifiedTextDescription, kOsAndroid,
@@ -9642,6 +9639,11 @@
               entry.internal_name)) {
     return true;
   }
+  // Skip lacros-selection if it is controlled by LacrosSelection policy.
+  if (!strcmp(kLacrosSelectionInternalName, entry.internal_name)) {
+    return crosapi::browser_util::GetCachedLacrosSelectionPolicy() !=
+           crosapi::browser_util::LacrosSelectionPolicy::kUserChoice;
+  }
 
   if (!strcmp(kPreferDcheckInternalName, entry.internal_name)) {
     return !crosapi::browser_util::IsLacrosAllowedToBeEnabled();
diff --git a/chrome/browser/accessibility/page_colors_factory.cc b/chrome/browser/accessibility/page_colors_factory.cc
index 429b3166..cbc932a 100644
--- a/chrome/browser/accessibility/page_colors_factory.cc
+++ b/chrome/browser/accessibility/page_colors_factory.cc
@@ -47,10 +47,11 @@
   return true;
 }
 
-KeyedService* PageColorsFactory::BuildServiceInstanceFor(
+std::unique_ptr<KeyedService>
+PageColorsFactory::BuildServiceInstanceForBrowserContext(
     content::BrowserContext* context) const {
-  std::unique_ptr<PageColors> page_colors = std::make_unique<PageColors>(
+  auto page_colors = std::make_unique<PageColors>(
       Profile::FromBrowserContext(context)->GetPrefs());
   page_colors->Init();
-  return page_colors.release();
+  return page_colors;
 }
diff --git a/chrome/browser/accessibility/page_colors_factory.h b/chrome/browser/accessibility/page_colors_factory.h
index e560f3a..1022b84a 100644
--- a/chrome/browser/accessibility/page_colors_factory.h
+++ b/chrome/browser/accessibility/page_colors_factory.h
@@ -28,8 +28,8 @@
   // BrowserContextKeyedServiceFactory:
   content::BrowserContext* GetBrowserContextToUse(
       content::BrowserContext* context) const override;
-  KeyedService* BuildServiceInstanceFor(
-      content::BrowserContext* profile) const override;
+  std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext(
+      content::BrowserContext* context) const override;
   bool ServiceIsCreatedWithBrowserContext() const override;
   void RegisterProfilePrefs(
       user_prefs::PrefRegistrySyncable* registry) override;
diff --git a/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.cc b/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.cc
index af57558..881ef38 100644
--- a/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.cc
+++ b/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.cc
@@ -18,6 +18,7 @@
 
 using base::android::JavaParamRef;
 using base::android::JavaRef;
+bool tab_strip_redesign_enabled;
 
 namespace android {
 
@@ -28,12 +29,14 @@
       scrollable_strip_layer_(cc::Layer::Create()),
       scrim_layer_(cc::SolidColorLayer::Create()),
       new_tab_button_(cc::UIResourceLayer::Create()),
+      new_tab_button_background_(cc::UIResourceLayer::Create()),
       left_fade_(cc::UIResourceLayer::Create()),
       right_fade_(cc::UIResourceLayer::Create()),
       model_selector_button_(cc::UIResourceLayer::Create()),
       write_index_(0),
       content_tree_(nullptr) {
   new_tab_button_->SetIsDrawable(true);
+  new_tab_button_background_->SetIsDrawable(true);
   model_selector_button_->SetIsDrawable(true);
   left_fade_->SetIsDrawable(true);
   right_fade_->SetIsDrawable(true);
@@ -45,6 +48,8 @@
   scrollable_strip_layer_->SetIsDrawable(true);
   const bool tab_strip_improvements_enabled =
       base::FeatureList::IsEnabled(chrome::android::kTabStripImprovements);
+  tab_strip_redesign_enabled =
+      base::FeatureList::IsEnabled(chrome::android::kTabStripRedesign);
   if (!tab_strip_improvements_enabled) {
     scrollable_strip_layer_->AddChild(new_tab_button_);
   }
@@ -56,6 +61,9 @@
   tab_strip_layer_->AddChild(right_fade_);
   tab_strip_layer_->AddChild(model_selector_button_);
   if (tab_strip_improvements_enabled) {
+    if (tab_strip_redesign_enabled) {
+      tab_strip_layer_->AddChild(new_tab_button_background_);
+    }
     tab_strip_layer_->AddChild(new_tab_button_);
   }
   tab_strip_layer_->AddChild(scrim_layer_);
@@ -163,13 +171,13 @@
     JNIEnv* env,
     const JavaParamRef<jobject>& jobj,
     jint resource_id,
+    jint bg_resource_id,
     jfloat x,
     jfloat y,
-    jfloat width,
-    jfloat height,
     jfloat touch_target_offset,
     jboolean visible,
     jint tint,
+    jint background_tint,
     jfloat button_alpha,
     const JavaParamRef<jobject>& jresource_manager) {
   ui::ResourceManager* resource_manager =
@@ -178,16 +186,40 @@
       resource_manager->GetStaticResourceWithTint(resource_id, tint);
 
   new_tab_button_->SetUIResourceId(button_resource->ui_resource()->id());
-  float left_offset = (width - button_resource->size().width()) / 2;
-  float top_offset = (height - button_resource->size().height()) / 2;
   // The touch target for the new tab button is skewed towards the end of the
   // strip. This ensures that the view itself is correctly aligned without
   // adjusting the touch target.
-  left_offset += touch_target_offset;
-  new_tab_button_->SetPosition(gfx::PointF(x + left_offset, y + top_offset));
+  float left_offset = touch_target_offset;
+
   new_tab_button_->SetBounds(button_resource->size());
   new_tab_button_->SetHideLayerAndSubtree(!visible);
   new_tab_button_->SetOpacity(button_alpha);
+
+  // Set Tab Strip Redesign new tab button background
+  if (tab_strip_redesign_enabled) {
+    ui::Resource* button_background_resource =
+        resource_manager->GetStaticResourceWithTint(bg_resource_id,
+                                                    background_tint, true);
+    float background_left_offset = (button_background_resource->size().width() -
+                                    button_resource->size().width()) /
+                                   2;
+    float background_top_offset = (button_background_resource->size().height() -
+                                   button_resource->size().height()) /
+                                  2;
+    new_tab_button_background_->SetUIResourceId(
+        button_background_resource->ui_resource()->id());
+    new_tab_button_background_->SetPosition(gfx::PointF(x + left_offset, y));
+
+    new_tab_button_background_->SetBounds(button_background_resource->size());
+    new_tab_button_background_->SetHideLayerAndSubtree(!visible);
+    new_tab_button_background_->SetOpacity(button_alpha);
+    new_tab_button_->SetPosition(
+        gfx::PointF(background_left_offset, background_top_offset));
+    new_tab_button_background_->AddChild(new_tab_button_);
+  } else {
+    // Only show new tab button icon when TSR is disabled
+    new_tab_button_->SetPosition(gfx::PointF(x + left_offset, y));
+  }
 }
 
 void TabStripSceneLayer::UpdateModelSelectorButton(
diff --git a/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.h b/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.h
index ef96dce..6a84473 100644
--- a/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.h
+++ b/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.h
@@ -69,13 +69,13 @@
       JNIEnv* env,
       const base::android::JavaParamRef<jobject>& jobj,
       jint resource_id,
+      jint bg_resource_id,
       jfloat x,
       jfloat y,
-      jfloat width,
-      jfloat height,
       jfloat touch_target_offset,
       jboolean visible,
       jint tint,
+      jint background_tint,
       jfloat button_alpha,
       const base::android::JavaParamRef<jobject>& jresource_manager);
 
@@ -152,6 +152,7 @@
   scoped_refptr<cc::Layer> scrollable_strip_layer_;
   scoped_refptr<cc::SolidColorLayer> scrim_layer_;
   scoped_refptr<cc::UIResourceLayer> new_tab_button_;
+  scoped_refptr<cc::UIResourceLayer> new_tab_button_background_;
   scoped_refptr<cc::UIResourceLayer> left_fade_;
   scoped_refptr<cc::UIResourceLayer> right_fade_;
   scoped_refptr<cc::UIResourceLayer> model_selector_button_;
diff --git a/chrome/browser/ash/BUILD.gn b/chrome/browser/ash/BUILD.gn
index 77f999a..5de5844 100644
--- a/chrome/browser/ash/BUILD.gn
+++ b/chrome/browser/ash/BUILD.gn
@@ -523,6 +523,8 @@
     "arc/session/arc_demo_mode_preference_handler.h",
     "arc/session/arc_disk_space_monitor.cc",
     "arc/session/arc_disk_space_monitor.h",
+    "arc/session/arc_initial_optin_notifier.cc",
+    "arc/session/arc_initial_optin_notifier.h",
     "arc/session/arc_play_store_enabled_preference_handler.cc",
     "arc/session/arc_play_store_enabled_preference_handler.h",
     "arc/session/arc_provisioning_result.cc",
@@ -2315,6 +2317,8 @@
     "policy/handlers/device_wifi_allowed_handler.h",
     "policy/handlers/lacros_availability_policy_handler.cc",
     "policy/handlers/lacros_availability_policy_handler.h",
+    "policy/handlers/lacros_selection_policy_handler.cc",
+    "policy/handlers/lacros_selection_policy_handler.h",
     "policy/handlers/lock_to_single_user_manager.cc",
     "policy/handlers/lock_to_single_user_manager.h",
     "policy/handlers/minimum_version_policy_handler.cc",
diff --git a/chrome/browser/ash/app_list/arc/DEPS b/chrome/browser/ash/app_list/arc/DEPS
new file mode 100644
index 0000000..b118d9f
--- /dev/null
+++ b/chrome/browser/ash/app_list/arc/DEPS
@@ -0,0 +1,5 @@
+specific_include_rules = {
+  "arc_app_list_prefs\.cc": [
+     "+ash",
+  ],
+}
diff --git a/chrome/browser/ash/app_list/arc/arc_app_list_prefs.cc b/chrome/browser/ash/app_list/arc/arc_app_list_prefs.cc
index 0faafbfc..56f8471 100644
--- a/chrome/browser/ash/app_list/arc/arc_app_list_prefs.cc
+++ b/chrome/browser/ash/app_list/arc/arc_app_list_prefs.cc
@@ -18,6 +18,8 @@
 #include "ash/components/arc/session/connection_holder.h"
 #include "ash/constants/ash_features.h"
 #include "ash/constants/ash_switches.h"
+#include "ash/metrics/login_unlock_throughput_recorder.h"
+#include "ash/shell.h"
 #include "base/bind.h"
 #include "base/check.h"
 #include "base/containers/contains.h"
@@ -43,6 +45,7 @@
 #include "chrome/browser/ash/login/session/user_session_manager.h"
 #include "chrome/browser/image_decoder/image_decoder.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/ash/shelf/chrome_shelf_controller.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/grit/generated_resources.h"
@@ -376,6 +379,44 @@
   update.Get().Remove(kDeprecatePackagePrefsSystem);
 }
 
+ash::LoginUnlockThroughputRecorder* GetLoginRecorder() {
+  return ash::Shell::HasInstance()
+             ? ash::Shell::Get()->login_unlock_throughput_recorder()
+             : nullptr;
+}
+
+void OnArcAppListRefreshed(Profile* profile) {
+  if (!arc::IsArcPlayStoreEnabledForProfile(profile))
+    return;
+
+  ash::LoginUnlockThroughputRecorder* throughput_recorder = GetLoginRecorder();
+  if (!throughput_recorder || !throughput_recorder->NeedReportArcAppListReady())
+    return;
+
+  DCHECK_EQ(ProfileManager::GetPrimaryUserProfile(), profile);
+  auto* prefs = ArcAppListPrefs::Get(profile);
+  if (!prefs)
+    return;
+
+  const std::vector<std::string> app_ids = prefs->GetAppIds();
+  int launchable = 0;
+  int ready = 0;
+  int error = 0;
+  for (const auto& app_id : app_ids) {
+    std::unique_ptr<ArcAppListPrefs::AppInfo> app_info = prefs->GetApp(app_id);
+    if (app_info) {
+      if (app_info->launchable)
+        ++launchable;
+
+      if (app_info->ready)
+        ++ready;
+    } else {
+      ++error;
+    }
+  }
+  if (ready + error >= launchable)
+    throughput_recorder->OnArcAppListReady();
+}
 }  // namespace
 
 // static
@@ -1589,6 +1630,7 @@
       }
     }
   }
+  OnArcAppListRefreshed(profile_);
 }
 
 void ArcAppListPrefs::RemoveApp(const std::string& app_id) {
@@ -1784,6 +1826,7 @@
       MaybeSetDefaultAppLoadingTimeout();
     }
   }
+  OnArcAppListRefreshed(profile_);
 }
 
 void ArcAppListPrefs::DetectDefaultAppAvailability() {
diff --git a/chrome/browser/ash/arc/session/arc_initial_optin_notifier.cc b/chrome/browser/ash/arc/session/arc_initial_optin_notifier.cc
new file mode 100644
index 0000000..ea28e28
--- /dev/null
+++ b/chrome/browser/ash/arc/session/arc_initial_optin_notifier.cc
@@ -0,0 +1,107 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/arc/session/arc_initial_optin_notifier.h"
+
+#include <string>
+
+#include "ash/components/arc/arc_util.h"
+#include "ash/metrics/login_unlock_throughput_recorder.h"
+#include "ash/shell.h"
+#include "base/bind.h"
+#include "base/logging.h"
+#include "chrome/browser/ash/arc/arc_util.h"
+#include "chrome/browser/ash/arc/session/arc_session_manager.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace arc {
+
+namespace {
+
+class ArcInitialOptInNotifierFactory
+    : public BrowserContextKeyedServiceFactory {
+ public:
+  ArcInitialOptInNotifierFactory();
+
+  ArcInitialOptInNotifierFactory(const ArcInitialOptInNotifierFactory&) =
+      delete;
+  ArcInitialOptInNotifierFactory& operator=(
+      const ArcInitialOptInNotifierFactory&) = delete;
+
+  ~ArcInitialOptInNotifierFactory() override = default;
+
+  KeyedService* BuildServiceInstanceFor(
+      content::BrowserContext* browser_context) const override {
+    return new ArcInitialOptInNotifier(browser_context);
+  }
+
+  // static
+  static ArcInitialOptInNotifier* GetForBrowserContext(
+      content::BrowserContext* context) {
+    return static_cast<ArcInitialOptInNotifier*>(
+        GetInstance()->GetServiceForBrowserContext(context, true));
+  }
+
+  // static
+  static ArcInitialOptInNotifierFactory* GetInstance() {
+    return base::Singleton<ArcInitialOptInNotifierFactory>::get();
+  }
+};
+
+ArcInitialOptInNotifierFactory::ArcInitialOptInNotifierFactory()
+    : BrowserContextKeyedServiceFactory(
+          "ArcInitialOptInNotifierFactory",
+          BrowserContextDependencyManager::GetInstance()) {}
+
+}  // anonymous namespace
+
+// static
+ArcInitialOptInNotifier* ArcInitialOptInNotifier::GetForProfile(
+    Profile* profile) {
+  return ArcInitialOptInNotifierFactory::GetForBrowserContext(profile);
+}
+
+ArcInitialOptInNotifier::ArcInitialOptInNotifier(
+    content::BrowserContext* context) {
+  ArcSessionManager* arc_session_manager = arc::ArcSessionManager::Get();
+  // arc::ArcSessionManager might not be set in tests.
+  if (arc_session_manager)
+    arc_session_manager->AddObserver(this);
+}
+
+ArcInitialOptInNotifier::~ArcInitialOptInNotifier() {
+  ArcSessionManager* arc_session_manager = arc::ArcSessionManager::Get();
+  // arc::ArcSessionManager may be released first.
+  if (arc_session_manager)
+    arc_session_manager->RemoveObserver(this);
+}
+
+void ArcInitialOptInNotifier::OnArcInitialStart() {
+  if (!IsArcPlayAutoInstallDisabled())
+    return;
+
+  LOG(WARNING) << "kArcDisablePlayAutoInstall flag is set. Force Arc apps "
+                  "loaded metric.";
+  ash::LoginUnlockThroughputRecorder* throughput_recorder =
+      ash::Shell::HasInstance()
+          ? ash::Shell::Get()->login_unlock_throughput_recorder()
+          : nullptr;
+  if (throughput_recorder) {
+    throughput_recorder->OnArcAppListReady();
+  }
+}
+
+void ArcInitialOptInNotifier::OnArcOptInUserAction() {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  ash::LoginUnlockThroughputRecorder* throughput_recorder =
+      ash::Shell::HasInstance()
+          ? ash::Shell::Get()->login_unlock_throughput_recorder()
+          : nullptr;
+  if (throughput_recorder)
+    throughput_recorder->OnArcOptedIn();
+}
+
+}  // namespace arc
diff --git a/chrome/browser/ash/arc/session/arc_initial_optin_notifier.h b/chrome/browser/ash/arc/session/arc_initial_optin_notifier.h
new file mode 100644
index 0000000..84b23b8
--- /dev/null
+++ b/chrome/browser/ash/arc/session/arc_initial_optin_notifier.h
@@ -0,0 +1,45 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ASH_ARC_SESSION_ARC_INITIAL_OPTIN_NOTIFIER_H_
+#define CHROME_BROWSER_ASH_ARC_SESSION_ARC_INITIAL_OPTIN_NOTIFIER_H_
+
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/ash/arc/session/arc_session_manager_observer.h"
+#include "components/keyed_service/core/keyed_service.h"
+
+class Profile;
+
+namespace content {
+class BrowserContext;
+}
+
+namespace arc {
+
+// Observes Arc session manager opt-in status, and notifies metrics.
+class ArcInitialOptInNotifier : public ArcSessionManagerObserver,
+                                public KeyedService {
+ public:
+  // Returns singleton instance for the given Profile.
+  static ArcInitialOptInNotifier* GetForProfile(Profile* profile);
+
+  explicit ArcInitialOptInNotifier(content::BrowserContext* context);
+
+  ArcInitialOptInNotifier(const ArcInitialOptInNotifier&) = delete;
+  ArcInitialOptInNotifier& operator=(const ArcInitialOptInNotifier&) = delete;
+
+  ~ArcInitialOptInNotifier() override;
+
+  // ArcSessionManagerObserver:
+  void OnArcInitialStart() override;
+  void OnArcOptInUserAction() override;
+
+ private:
+  // Must be the last member.
+  base::WeakPtrFactory<ArcInitialOptInNotifier> weak_ptr_factory_{this};
+};
+
+}  // namespace arc
+
+#endif  // CHROME_BROWSER_ASH_ARC_SESSION_ARC_INITIAL_OPTIN_NOTIFIER_H_
diff --git a/chrome/browser/ash/arc/session/arc_service_launcher.cc b/chrome/browser/ash/arc/session/arc_service_launcher.cc
index 00859b20..9be86d5 100644
--- a/chrome/browser/ash/arc/session/arc_service_launcher.cc
+++ b/chrome/browser/ash/arc/session/arc_service_launcher.cc
@@ -83,6 +83,7 @@
 #include "chrome/browser/ash/arc/screen_capture/arc_screen_capture_bridge.h"
 #include "chrome/browser/ash/arc/session/arc_demo_mode_preference_handler.h"
 #include "chrome/browser/ash/arc/session/arc_disk_space_monitor.h"
+#include "chrome/browser/ash/arc/session/arc_initial_optin_notifier.h"
 #include "chrome/browser/ash/arc/session/arc_play_store_enabled_preference_handler.h"
 #include "chrome/browser/ash/arc/session/arc_session_manager.h"
 #include "chrome/browser/ash/arc/sharesheet/arc_sharesheet_bridge.h"
@@ -310,6 +311,7 @@
   apps::ArcAppsFactory::GetForProfile(profile);
   ash::ApkWebAppService::Get(profile);
   ash::app_restore::AppRestoreArcTaskHandler::GetForProfile(profile);
+  ArcInitialOptInNotifier::GetForProfile(profile);
 
   if (arc::IsArcVmEnabled()) {
     // ARCVM-only services.
diff --git a/chrome/browser/ash/arc/session/arc_session_manager.cc b/chrome/browser/ash/arc/session/arc_session_manager.cc
index 0988602..5385f13 100644
--- a/chrome/browser/ash/arc/session/arc_session_manager.cc
+++ b/chrome/browser/ash/arc/session/arc_session_manager.cc
@@ -1243,6 +1243,9 @@
   switch (result) {
     case ArcRequirementChecker::RequirementCheckResult::kOk:
       VLOG(1) << "Starting ARC for first sign in.";
+      for (auto& observer : observer_list_)
+        observer.OnArcOptInUserAction();
+
       StartArc();
       break;
     case ArcRequirementChecker::RequirementCheckResult::
diff --git a/chrome/browser/ash/arc/session/arc_session_manager_observer.h b/chrome/browser/ash/arc/session/arc_session_manager_observer.h
index a4bcda3..f13f7f2 100644
--- a/chrome/browser/ash/arc/session/arc_session_manager_observer.h
+++ b/chrome/browser/ash/arc/session/arc_session_manager_observer.h
@@ -18,6 +18,11 @@
   // is represented by "arc.enabled" preference, is updated.
   virtual void OnArcPlayStoreEnabledChanged(bool enabled) {}
 
+  // Called with when user opted-in for ARC and ARC is going to be created for
+  // this user. I.e. successful user action triggered ARC user instance
+  // initialization.
+  virtual void OnArcOptInUserAction() {}
+
   // Called to notify that checking of Android management status started
   // during the opt-in flow.
   virtual void OnArcOptInManagementCheckStarted() {}
diff --git a/chrome/browser/ash/crosapi/browser_loader.cc b/chrome/browser/ash/crosapi/browser_loader.cc
index 90a0b2c5..839ccdb 100644
--- a/chrome/browser/ash/crosapi/browser_loader.cc
+++ b/chrome/browser/ash/crosapi/browser_loader.cc
@@ -206,21 +206,19 @@
     return;
   }
 
-  // If the user has specified to force using stateful or rootfs lacros-chrome
-  // binary, force the selection.
-  const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
-  if (cmdline->HasSwitch(browser_util::kLacrosSelectionSwitch)) {
+  // If the LacrosSelection policy or the user have specified to force using
+  // stateful or rootfs lacros-chrome binary, force the selection.
+  if (absl::optional<browser_util::LacrosSelection> lacros_selection =
+          browser_util::DetermineLacrosSelection()) {
     // TODO(crbug.com/1293250): We should check the version compatibility here,
     // too.
-    auto value =
-        cmdline->GetSwitchValueASCII(browser_util::kLacrosSelectionSwitch);
-    if (value == browser_util::kLacrosSelectionRootfs) {
-      LoadRootfsLacros(std::move(callback));
-      return;
-    }
-    if (value == browser_util::kLacrosSelectionStateful) {
-      LoadStatefulLacros(std::move(callback));
-      return;
+    switch (lacros_selection.value()) {
+      case browser_util::LacrosSelection::kRootfs:
+        LoadRootfsLacros(std::move(callback));
+        return;
+      case browser_util::LacrosSelection::kStateful:
+        LoadStatefulLacros(std::move(callback));
+        return;
     }
   }
 
diff --git a/chrome/browser/ash/crosapi/browser_loader_unittest.cc b/chrome/browser/ash/crosapi/browser_loader_unittest.cc
index 034d6cafa22..fc6c330 100644
--- a/chrome/browser/ash/crosapi/browser_loader_unittest.cc
+++ b/chrome/browser/ash/crosapi/browser_loader_unittest.cc
@@ -10,13 +10,17 @@
 #include "base/run_loop.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/bind.h"
+#include "base/test/scoped_command_line.h"
 #include "base/test/scoped_feature_list.h"
+#include "base/test/test_future.h"
 #include "chrome/browser/ash/crosapi/browser_util.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/component_updater/fake_cros_component_manager.h"
 #include "chrome/test/base/browser_process_platform_part_test_api_chromeos.h"
 #include "chromeos/ash/components/dbus/upstart/fake_upstart_client.h"
 #include "components/component_updater/mock_component_updater_service.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/policy_constants.h"
 #include "components/update_client/update_client.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -32,6 +36,33 @@
 constexpr char kLacrosMounterUpstartJob[] = "lacros_2dmounter";
 constexpr char kLacrosUnmounterUpstartJob[] = "lacros_2dunmounter";
 
+// This implementation of RAII for LacrosSelection is to make it easy reset
+// the state between runs.
+class ScopedLacrosSelectionCache {
+ public:
+  explicit ScopedLacrosSelectionCache(
+      browser_util::LacrosSelectionPolicy lacros_selection) {
+    SetLacrosSelection(lacros_selection);
+  }
+  ScopedLacrosSelectionCache(const ScopedLacrosSelectionCache&) = delete;
+  ScopedLacrosSelectionCache& operator=(const ScopedLacrosSelectionCache&) =
+      delete;
+  ~ScopedLacrosSelectionCache() {
+    browser_util::ClearLacrosSelectionCacheForTest();
+  }
+
+ private:
+  void SetLacrosSelection(
+      browser_util::LacrosSelectionPolicy lacros_selection) {
+    policy::PolicyMap policy;
+    policy.Set(policy::key::kLacrosSelection, policy::POLICY_LEVEL_MANDATORY,
+               policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_CLOUD,
+               base::Value(GetLacrosSelectionPolicyName(lacros_selection)),
+               /*external_data_fetcher=*/nullptr);
+    browser_util::CacheLacrosSelection(policy);
+  }
+};
+
 }  // namespace
 
 class BrowserLoaderTest : public testing::Test {
@@ -229,4 +260,70 @@
   EXPECT_TRUE(callback_called);
 }
 
+TEST_F(BrowserLoaderTest, OnLoadSelectionPolicyIsRootfs) {
+  ScopedLacrosSelectionCache cache(
+      browser_util::LacrosSelectionPolicy::kRootfs);
+  base::test::ScopedCommandLine command_line;
+  command_line.GetProcessCommandLine()->AppendSwitchASCII(
+      browser_util::kLacrosSelectionSwitch,
+      browser_util::kLacrosSelectionStateful);
+
+  base::test::TestFuture<base::FilePath, LacrosSelection, base::Version> future;
+  browser_loader_->Load(future.GetCallback<const base::FilePath&,
+                                           LacrosSelection, base::Version>());
+
+  const LacrosSelection selection = future.Get<1>();
+  EXPECT_EQ(selection, LacrosSelection::kRootfs);
+}
+
+TEST_F(BrowserLoaderTest, OnLoadSelectionPolicyIsStateful) {
+  ScopedLacrosSelectionCache cache(
+      browser_util::LacrosSelectionPolicy::kStateful);
+  base::test::ScopedCommandLine command_line;
+  command_line.GetProcessCommandLine()->AppendSwitchASCII(
+      browser_util::kLacrosSelectionSwitch,
+      browser_util::kLacrosSelectionRootfs);
+
+  base::test::TestFuture<base::FilePath, LacrosSelection, base::Version> future;
+  browser_loader_->Load(future.GetCallback<const base::FilePath&,
+                                           LacrosSelection, base::Version>());
+
+  const LacrosSelection selection = future.Get<1>();
+  EXPECT_EQ(selection, LacrosSelection::kStateful);
+}
+
+TEST_F(BrowserLoaderTest,
+       OnLoadSelectionPolicyIsUserChoiceAndCommandLineIsRootfs) {
+  ScopedLacrosSelectionCache cache(
+      browser_util::LacrosSelectionPolicy::kUserChoice);
+  base::test::ScopedCommandLine command_line;
+  command_line.GetProcessCommandLine()->AppendSwitchASCII(
+      browser_util::kLacrosSelectionSwitch,
+      browser_util::kLacrosSelectionRootfs);
+
+  base::test::TestFuture<base::FilePath, LacrosSelection, base::Version> future;
+  browser_loader_->Load(future.GetCallback<const base::FilePath&,
+                                           LacrosSelection, base::Version>());
+
+  const LacrosSelection selection = future.Get<1>();
+  EXPECT_EQ(selection, LacrosSelection::kRootfs);
+}
+
+TEST_F(BrowserLoaderTest,
+       OnLoadSelectionPolicyIsUserChoiceAndCommandLineIsStateful) {
+  ScopedLacrosSelectionCache cache(
+      browser_util::LacrosSelectionPolicy::kUserChoice);
+  base::test::ScopedCommandLine command_line;
+  command_line.GetProcessCommandLine()->AppendSwitchASCII(
+      browser_util::kLacrosSelectionSwitch,
+      browser_util::kLacrosSelectionStateful);
+
+  base::test::TestFuture<base::FilePath, LacrosSelection, base::Version> future;
+  browser_loader_->Load(future.GetCallback<const base::FilePath&,
+                                           LacrosSelection, base::Version>());
+
+  const LacrosSelection selection = future.Get<1>();
+  EXPECT_EQ(selection, LacrosSelection::kStateful);
+}
+
 }  // namespace crosapi
diff --git a/chrome/browser/ash/crosapi/browser_util.cc b/chrome/browser/ash/crosapi/browser_util.cc
index f1e7a51..1e9ac8b 100644
--- a/chrome/browser/ash/crosapi/browser_util.cc
+++ b/chrome/browser/ash/crosapi/browser_util.cc
@@ -61,12 +61,16 @@
 absl::optional<LacrosDataBackwardMigrationMode>
     g_lacros_data_backward_migration_mode;
 
+// At session start the value for LacrosSelection logic is applied and the
+// result is stored in this variable which is used after that as a cache.
+absl::optional<LacrosSelectionPolicy> g_lacros_selection_cache;
+
 // The rootfs lacros-chrome metadata keys.
 constexpr char kLacrosMetadataContentKey[] = "content";
 constexpr char kLacrosMetadataVersionKey[] = "version";
 
 // The conversion map for LacrosAvailability policy data. The values must match
-// the ones from policy_templates.json.
+// the ones from LacrosAvailability.yaml.
 constexpr auto kLacrosAvailabilityMap =
     base::MakeFixedFlatMap<base::StringPiece, LacrosAvailability>({
         {"user_choice", LacrosAvailability::kUserChoice},
@@ -90,6 +94,15 @@
          LacrosDataBackwardMigrationMode::kKeepAll},
     });
 
+// The conversion map for LacrosSelection policy data. The values must match
+// the ones from LacrosSelection.yaml.
+constexpr auto kLacrosSelectionPolicyMap =
+    base::MakeFixedFlatMap<base::StringPiece, LacrosSelectionPolicy>({
+        {"user_choice", LacrosSelectionPolicy::kUserChoice},
+        {"rootfs", LacrosSelectionPolicy::kRootfs},
+        {"stateful", LacrosSelectionPolicy::kStateful},
+    });
+
 // Some account types require features that aren't yet supported by lacros.
 // See https://crbug.com/1080693
 bool IsUserTypeAllowed(const User* user) {
@@ -904,6 +917,51 @@
       value ? value->GetString() : base::StringPiece());
 }
 
+void CacheLacrosSelection(const policy::PolicyMap& map) {
+  if (g_lacros_selection_cache.has_value()) {
+    // Some browser tests might call this multiple times.
+    LOG(ERROR) << "Trying to cache LacrosSelection and the value was set";
+    return;
+  }
+
+  const base::Value* value =
+      map.GetValue(policy::key::kLacrosSelection, base::Value::Type::STRING);
+  g_lacros_selection_cache = ParseLacrosSelectionPolicy(
+      value ? value->GetString() : base::StringPiece());
+}
+
+LacrosSelectionPolicy GetCachedLacrosSelectionPolicy() {
+  if (g_lacros_selection_cache)
+    return g_lacros_selection_cache.value();
+
+  return LacrosSelectionPolicy::kUserChoice;
+}
+
+absl::optional<LacrosSelection> DetermineLacrosSelection() {
+  switch (GetCachedLacrosSelectionPolicy()) {
+    case LacrosSelectionPolicy::kRootfs:
+      return LacrosSelection::kRootfs;
+    case LacrosSelectionPolicy::kStateful:
+      return LacrosSelection::kStateful;
+    case LacrosSelectionPolicy::kUserChoice:
+      break;
+  }
+
+  const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
+
+  if (!cmdline->HasSwitch(browser_util::kLacrosSelectionSwitch))
+    return absl::nullopt;
+
+  auto value =
+      cmdline->GetSwitchValueASCII(browser_util::kLacrosSelectionSwitch);
+  if (value == browser_util::kLacrosSelectionRootfs)
+    return LacrosSelection::kRootfs;
+  if (value == browser_util::kLacrosSelectionStateful)
+    return LacrosSelection::kStateful;
+
+  return absl::nullopt;
+}
+
 ComponentInfo GetLacrosComponentInfoForChannel(version_info::Channel channel) {
   // We default to the Dev component for UNKNOWN channels.
   static const auto kChannelToComponentInfoMap =
@@ -978,6 +1036,10 @@
   g_lacros_data_backward_migration_mode.reset();
 }
 
+void ClearLacrosSelectionCacheForTest() {
+  g_lacros_selection_cache.reset();
+}
+
 MigrationMode GetMigrationMode(const user_manager::User* user,
                                PolicyInitState policy_init_state) {
   if (base::FeatureList::IsEnabled(
@@ -1110,6 +1172,16 @@
   return absl::nullopt;
 }
 
+absl::optional<LacrosSelectionPolicy> ParseLacrosSelectionPolicy(
+    base::StringPiece value) {
+  auto* it = kLacrosSelectionPolicyMap.find(value);
+  if (it != kLacrosSelectionPolicyMap.end())
+    return it->second;
+
+  LOG(ERROR) << "Unknown LacrosSelection policy value is passed: " << value;
+  return absl::nullopt;
+}
+
 base::StringPiece GetLacrosAvailabilityPolicyName(LacrosAvailability value) {
   for (const auto& entry : kLacrosAvailabilityMap) {
     if (entry.second == value)
@@ -1142,6 +1214,16 @@
   return base::StringPiece();
 }
 
+base::StringPiece GetLacrosSelectionPolicyName(LacrosSelectionPolicy value) {
+  for (const auto& entry : kLacrosSelectionPolicyMap) {
+    if (entry.second == value)
+      return entry.first;
+  }
+
+  NOTREACHED();
+  return base::StringPiece();
+}
+
 bool IsAshBrowserSyncEnabled() {
   // Turn off sync from Ash if Lacros is enabled and Ash web browser is
   // disabled.
diff --git a/chrome/browser/ash/crosapi/browser_util.h b/chrome/browser/ash/crosapi/browser_util.h
index 5113159..4dfec38 100644
--- a/chrome/browser/ash/crosapi/browser_util.h
+++ b/chrome/browser/ash/crosapi/browser_util.h
@@ -80,6 +80,20 @@
   kLacrosOnly = 4
 };
 
+// Represents the policy indicating which Lacros browser to launch, named
+// LacrosSelection. The values shall be consistent with the controlling
+// policy. Unlike `LacrosSelection` representing which lacros to select,
+// `LacrosSelectionPolicy` represents how to decide which lacros to select.
+enum class LacrosSelectionPolicy {
+  // Indicates that the user decides which Lacros browser to launch: rootfs or
+  // stateful.
+  kUserChoice = 0,
+  // Indicates that rootfs Lacros will always be launched.
+  kRootfs = 1,
+  // Indicates that stateful Lacros will always be launched.
+  kStateful = 2,
+};
+
 // Represents the different options available for lacros selection.
 enum class LacrosSelection {
   kRootfs = 0,
@@ -351,6 +365,20 @@
 // LacrosDataBackwardMigrationMode policy.
 void CacheLacrosDataBackwardMigrationMode(const policy::PolicyMap& map);
 
+// To be called at primary user login, to cache the policy value for
+// LacrosSelection policy. The effective value of the policy does not
+// change for the duration of the user session, so cached value shall be
+// checked.
+void CacheLacrosSelection(const policy::PolicyMap& map);
+
+// Returns cached value of LacrosSelection policy. See `CacheLacrosSelection`
+// for details.
+LacrosSelectionPolicy GetCachedLacrosSelectionPolicy();
+
+// Returns lacros selection option according to LarcrosSelectionPolicy and
+// lacros-selection flag. Returns nullopt if there is no preference.
+absl::optional<LacrosSelection> DetermineLacrosSelection();
+
 // Returns the lacros ComponentInfo for a given channel.
 ComponentInfo GetLacrosComponentInfoForChannel(version_info::Channel channel);
 
@@ -385,6 +413,9 @@
 // Clears the cached value for LacrosDataBackwardMigrationMode.
 void ClearLacrosDataBackwardMigrationModeCacheForTest();
 
+// Clears the cached value for LacrosSelection policy.
+void ClearLacrosSelectionCacheForTest();
+
 bool IsProfileMigrationEnabled(const AccountId& account_id);
 
 // Returns true if the profile migration can run, but not yet completed.
@@ -458,7 +489,13 @@
 absl::optional<LacrosAvailability> ParseLacrosAvailability(
     base::StringPiece value);
 
-// Returns the policy value name from the given value.
+// Parses the string representation of LacrosSelection policy value into the
+// enum value. Returns nullopt on unknown value.
+absl::optional<LacrosSelectionPolicy> ParseLacrosSelectionPolicy(
+    base::StringPiece value);
+
+// Returns the policy LacrosAvailability value name from the given value.
+// Returned StringPiece is guaranteed to never be invalidated
 base::StringPiece GetLacrosAvailabilityPolicyName(LacrosAvailability value);
 
 // Parses the string representation of LacrosDataBackwardMigrationMode policy
@@ -470,6 +507,10 @@
 base::StringPiece GetLacrosDataBackwardMigrationModeName(
     LacrosDataBackwardMigrationMode value);
 
+// Returns the LacrosSelection policy value name from the given value. Returned
+// StringPiece is guaranteed to never be invalidated.
+base::StringPiece GetLacrosSelectionPolicyName(LacrosSelectionPolicy value);
+
 // Stores that "Go to files button" on the migration error screen is clicked.
 void SetGotoFilesClicked(PrefService* local_state,
                          const std::string& user_id_hash);
diff --git a/chrome/browser/ash/crosapi/browser_util_unittest.cc b/chrome/browser/ash/crosapi/browser_util_unittest.cc
index 36be553..0cd2a067 100644
--- a/chrome/browser/ash/crosapi/browser_util_unittest.cc
+++ b/chrome/browser/ash/crosapi/browser_util_unittest.cc
@@ -68,6 +68,33 @@
   }
 };
 
+// This implementation of RAII for LacrosSelection is to make it easy reset
+// the state between runs.
+class ScopedLacrosSelectionCache {
+ public:
+  explicit ScopedLacrosSelectionCache(
+      browser_util::LacrosSelectionPolicy lacros_selection) {
+    SetLacrosSelection(lacros_selection);
+  }
+  ScopedLacrosSelectionCache(const ScopedLacrosSelectionCache&) = delete;
+  ScopedLacrosSelectionCache& operator=(const ScopedLacrosSelectionCache&) =
+      delete;
+  ~ScopedLacrosSelectionCache() {
+    browser_util::ClearLacrosSelectionCacheForTest();
+  }
+
+ private:
+  void SetLacrosSelection(
+      browser_util::LacrosSelectionPolicy lacros_selection) {
+    policy::PolicyMap policy;
+    policy.Set(policy::key::kLacrosSelection, policy::POLICY_LEVEL_MANDATORY,
+               policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_CLOUD,
+               base::Value(GetLacrosSelectionPolicyName(lacros_selection)),
+               /*external_data_fetcher=*/nullptr);
+    browser_util::CacheLacrosSelection(policy);
+  }
+};
+
 }  // namespace
 
 class BrowserUtilTest : public testing::Test {
@@ -1116,4 +1143,64 @@
   EXPECT_EQ(serial_number.value(), expected_serial_number);
 }
 
+TEST_F(BrowserUtilTest, LacrosSelection) {
+  // Neither policy nor command line have any preference on Lacros selection.
+  EXPECT_FALSE(browser_util::DetermineLacrosSelection());
+
+  {
+    // LacrosSelection policy has precedence over command line.
+    ScopedLacrosSelectionCache cache(
+        browser_util::LacrosSelectionPolicy::kRootfs);
+    base::test::ScopedCommandLine cmd_line;
+    cmd_line.GetProcessCommandLine()->AppendSwitchASCII(
+        browser_util::kLacrosSelectionSwitch,
+        browser_util::kLacrosSelectionStateful);
+    EXPECT_EQ(browser_util::DetermineLacrosSelection(),
+              LacrosSelection::kRootfs);
+  }
+
+  {
+    // LacrosSelection policy has precedence over command line.
+    ScopedLacrosSelectionCache cache(
+        browser_util::LacrosSelectionPolicy::kStateful);
+    base::test::ScopedCommandLine cmd_line;
+    cmd_line.GetProcessCommandLine()->AppendSwitchASCII(
+        browser_util::kLacrosSelectionSwitch,
+        browser_util::kLacrosSelectionRootfs);
+    EXPECT_EQ(browser_util::DetermineLacrosSelection(),
+              LacrosSelection::kStateful);
+  }
+
+  {
+    // LacrosSelection allows command line check, but command line is not set.
+    ScopedLacrosSelectionCache cache(
+        browser_util::LacrosSelectionPolicy::kUserChoice);
+    EXPECT_FALSE(browser_util::DetermineLacrosSelection());
+  }
+
+  {
+    // LacrosSelection allows command line check.
+    ScopedLacrosSelectionCache cache(
+        browser_util::LacrosSelectionPolicy::kUserChoice);
+    base::test::ScopedCommandLine cmd_line;
+    cmd_line.GetProcessCommandLine()->AppendSwitchASCII(
+        browser_util::kLacrosSelectionSwitch,
+        browser_util::kLacrosSelectionRootfs);
+    EXPECT_EQ(browser_util::DetermineLacrosSelection(),
+              LacrosSelection::kRootfs);
+  }
+
+  {
+    // LacrosSelection allows command line check.
+    ScopedLacrosSelectionCache cache(
+        browser_util::LacrosSelectionPolicy::kUserChoice);
+    base::test::ScopedCommandLine cmd_line;
+    cmd_line.GetProcessCommandLine()->AppendSwitchASCII(
+        browser_util::kLacrosSelectionSwitch,
+        browser_util::kLacrosSelectionStateful);
+    EXPECT_EQ(browser_util::DetermineLacrosSelection(),
+              LacrosSelection::kStateful);
+  }
+}
+
 }  // namespace crosapi
diff --git a/chrome/browser/ash/net/network_diagnostics/http_request_manager.cc b/chrome/browser/ash/net/network_diagnostics/http_request_manager.cc
index 2bebce7..d951c08 100644
--- a/chrome/browser/ash/net/network_diagnostics/http_request_manager.cc
+++ b/chrome/browser/ash/net/network_diagnostics/http_request_manager.cc
@@ -31,16 +31,18 @@
       semantics {
         sender: "NetworkDiagnosticsRoutines"
         description: "Routines send network traffic (http requests) to "
-        "hosts in order to validate the internet connection on a device."
-        trigger:
-            "A routine makes an http request."
+          "hosts in order to validate the internet connection on a device."
+        trigger: "A routine makes an http request."
         data:
-            "No data other than the path is sent. No user identifier is "
-            "sent along with the data."
+          "No data other than the path is sent. No user identifier is "
+          "sent along with the data."
         destination: WEBSITE
       }
       policy {
         cookies_allowed: NO
+        policy_exception_justification:
+          "No policy defined to enable/disable or limit this request as this "
+          "is on-demand user initiated operation to do the network diagnostics."
       }
   )");
 }
diff --git a/chrome/browser/ash/policy/handlers/lacros_selection_policy_handler.cc b/chrome/browser/ash/policy/handlers/lacros_selection_policy_handler.cc
new file mode 100644
index 0000000..c8bbf43
--- /dev/null
+++ b/chrome/browser/ash/policy/handlers/lacros_selection_policy_handler.cc
@@ -0,0 +1,54 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/policy/handlers/lacros_selection_policy_handler.h"
+
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
+#error This file shall only be used in ash.
+#endif
+
+#include "chrome/common/pref_names.h"
+#include "components/policy/core/browser/policy_error_map.h"
+#include "components/policy/policy_constants.h"
+#include "components/prefs/pref_value_map.h"
+#include "components/strings/grit/components_strings.h"
+
+namespace policy {
+
+LacrosSelectionPolicyHandler::LacrosSelectionPolicyHandler()
+    : TypeCheckingPolicyHandler(key::kLacrosSelection,
+                                base::Value::Type::STRING) {}
+
+bool LacrosSelectionPolicyHandler::CheckPolicySettings(
+    const PolicyMap& policies,
+    PolicyErrorMap* errors) {
+  return GetValue(policies, errors).has_value();
+}
+
+void LacrosSelectionPolicyHandler::ApplyPolicySettings(
+    const PolicyMap& policies,
+    PrefValueMap* prefs) {
+  auto enum_value = GetValue(policies, nullptr);
+  if (enum_value.has_value())
+    prefs->SetInteger(prefs::kLacrosSelection, static_cast<int>(*enum_value));
+}
+
+absl::optional<crosapi::browser_util::LacrosSelectionPolicy>
+LacrosSelectionPolicyHandler::GetValue(const PolicyMap& policies,
+                                       PolicyErrorMap* errors) {
+  const base::Value* value;
+  const bool value_found = CheckAndGetValue(policies, errors, &value) && value;
+  if (!value_found)
+    return absl::nullopt;
+
+  auto parsed =
+      crosapi::browser_util::ParseLacrosSelectionPolicy(value->GetString());
+  if (!parsed.has_value() && errors) {
+    errors->AddError(policy_name(), IDS_POLICY_INVALID_SELECTION_ERROR,
+                     "LacrosSelection value");
+  }
+  return parsed;
+}
+
+}  // namespace policy
diff --git a/chrome/browser/ash/policy/handlers/lacros_selection_policy_handler.h b/chrome/browser/ash/policy/handlers/lacros_selection_policy_handler.h
new file mode 100644
index 0000000..cc867d3
--- /dev/null
+++ b/chrome/browser/ash/policy/handlers/lacros_selection_policy_handler.h
@@ -0,0 +1,40 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ASH_POLICY_HANDLERS_LACROS_SELECTION_POLICY_HANDLER_H_
+#define CHROME_BROWSER_ASH_POLICY_HANDLERS_LACROS_SELECTION_POLICY_HANDLER_H_
+
+#include "build/chromeos_buildflags.h"
+#include "chrome/browser/ash/crosapi/browser_util.h"
+#include "components/policy/core/browser/configuration_policy_handler.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+class PrefValueMap;
+
+namespace policy {
+
+class PolicyMap;
+
+// The handler for LacrosSelection selection policy which maps string-enum
+// policy values to `LacrosSelectionPolicy` enum stored in `PolicyMap`.
+class LacrosSelectionPolicyHandler : public TypeCheckingPolicyHandler {
+ public:
+  LacrosSelectionPolicyHandler();
+
+  // ConfigurationPolicyHandler:
+  bool CheckPolicySettings(const PolicyMap& policies,
+                           PolicyErrorMap* errors) override;
+
+  void ApplyPolicySettings(const PolicyMap& policies,
+                           PrefValueMap* prefs) override;
+
+ private:
+  absl::optional<crosapi::browser_util::LacrosSelectionPolicy> GetValue(
+      const PolicyMap& policies,
+      PolicyErrorMap* errors);
+};
+
+}  // namespace policy
+
+#endif  // CHROME_BROWSER_ASH_POLICY_HANDLERS_LACROS_SELECTION_POLICY_HANDLER_H_
diff --git a/chrome/browser/ash/preferences.cc b/chrome/browser/ash/preferences.cc
index fdc83ae..5b32656 100644
--- a/chrome/browser/ash/preferences.cc
+++ b/chrome/browser/ash/preferences.cc
@@ -146,6 +146,10 @@
   registry->RegisterIntegerPref(
       ::prefs::kLacrosLaunchSwitch,
       static_cast<int>(crosapi::browser_util::LacrosAvailability::kUserChoice));
+  registry->RegisterIntegerPref(
+      ::prefs::kLacrosSelection,
+      static_cast<int>(
+          crosapi::browser_util::LacrosSelectionPolicy::kUserChoice));
   registry->RegisterStringPref(::prefs::kLacrosDataBackwardMigrationMode, "");
   registry->RegisterBooleanPref(prefs::kDeviceSystemWideTracingEnabled, true);
   registry->RegisterBooleanPref(
diff --git a/chrome/browser/ash/system_web_apps/system_web_app_manager.cc b/chrome/browser/ash/system_web_apps/system_web_app_manager.cc
index e74a539..89c62b2 100644
--- a/chrome/browser/ash/system_web_apps/system_web_app_manager.cc
+++ b/chrome/browser/ash/system_web_apps/system_web_app_manager.cc
@@ -368,19 +368,9 @@
   // or `SetSystemAppsForTesting()` has been called.
   if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType) &&
       skip_app_installation_in_test_) {
-    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
-        FROM_HERE,
-        base::BindOnce(
-            &SystemWebAppManager::OnAppsSynchronized,
-            weak_ptr_factory_.GetWeakPtr(), should_force_install_apps,
-            install_start_time,
-            /*install_results=*/
-            std::map<GURL,
-                     web_app::ExternallyManagedAppManager::InstallResult>(),
-            /*uninstall_results=*/std::map<GURL, bool>()));
-
-    return;
+    install_options_list.clear();
   }
+
   provider_->externally_managed_app_manager().SynchronizeInstalledApps(
       std::move(install_options_list),
       web_app::ExternalInstallSource::kSystemInstalled,
diff --git a/chrome/browser/background/background_application_list_model_unittest.cc b/chrome/browser/background/background_application_list_model_unittest.cc
index 0b62606f..83af0e8 100644
--- a/chrome/browser/background/background_application_list_model_unittest.cc
+++ b/chrome/browser/background/background_application_list_model_unittest.cc
@@ -101,9 +101,9 @@
   std::string error;
   scoped_refptr<Extension> extension;
 
-  extension = Extension::Create(bogus_file_pathname(name),
-                                extensions::mojom::ManifestLocation::kInternal,
-                                manifest, Extension::NO_FLAGS, &error);
+  extension = Extension::Create(
+      bogus_file_pathname(name), extensions::mojom::ManifestLocation::kInternal,
+      manifest.GetDict(), Extension::NO_FLAGS, &error);
 
   // Cannot ASSERT_* here because that attempts an illegitimate return.
   // Cannot EXPECT_NE here because that assumes non-pointers unlike EXPECT_EQ
diff --git a/chrome/browser/background/background_contents_service.cc b/chrome/browser/background/background_contents_service.cc
index f9d2020..a6a2b9d 100644
--- a/chrome/browser/background/background_contents_service.cc
+++ b/chrome/browser/background/background_contents_service.cc
@@ -641,17 +641,18 @@
   // already an entry for this application, no need to do anything.
   // TODO(atwilson): Verify that this is the desired behavior based on developer
   // feedback (http://crbug.com/47118).
-  DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents);
-  base::Value* pref = update.Get();
+  ScopedDictPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents);
+  base::Value::Dict& pref = update.Get();
   const std::string& appid = GetParentApplicationId(background_contents);
-  if (pref->FindDictKey(appid))
+  if (pref.FindDict(appid)) {
     return;
+  }
 
   // No entry for this application yet, so add one.
-  base::Value dict(base::Value::Type::DICTIONARY);
-  dict.SetStringKey(kUrlKey, background_contents->GetURL().spec());
-  dict.SetStringKey(kFrameNameKey, contents_map_[appid].frame_name);
-  pref->SetKey(appid, std::move(dict));
+  base::Value::Dict dict;
+  dict.Set(kUrlKey, background_contents->GetURL().spec());
+  dict.Set(kFrameNameKey, contents_map_[appid].frame_name);
+  pref.Set(appid, std::move(dict));
 }
 
 bool BackgroundContentsService::HasRegisteredBackgroundContents(
@@ -669,8 +670,8 @@
     return;
   DCHECK(IsTracked(background_contents));
   const std::string& appid = GetParentApplicationId(background_contents);
-  DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents);
-  update.Get()->RemoveKey(appid);
+  ScopedDictPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents);
+  update->Remove(appid);
 }
 
 void BackgroundContentsService::ShutdownAssociatedBackgroundContents(
diff --git a/chrome/browser/chrome_browser_interface_binders.cc b/chrome/browser/chrome_browser_interface_binders.cc
index 778626ea..0b97572 100644
--- a/chrome/browser/chrome_browser_interface_binders.cc
+++ b/chrome/browser/chrome_browser_interface_binders.cc
@@ -1034,6 +1034,9 @@
     RegisterWebUIControllerInterfaceBinder<
         metrics_reporter::mojom::PageMetricsHost, TabSearchUI, NewTabPageUI>(
         map);
+  } else {
+    RegisterWebUIControllerInterfaceBinder<
+        metrics_reporter::mojom::PageMetricsHost, NewTabPageUI>(map);
   }
 
   RegisterWebUIControllerInterfaceBinder<
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 03210d3..10cf5bb 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -3298,18 +3298,21 @@
 }
 
 bool ChromeContentBrowserClient::IsSharedStorageAllowed(
+    content::BrowserContext* browser_context,
     content::RenderFrameHost* rfh,
     const url::Origin& top_frame_origin,
     const url::Origin& accessing_origin) {
-  Profile* profile = Profile::FromBrowserContext(rfh->GetBrowserContext());
+  Profile* profile = Profile::FromBrowserContext(browser_context);
   auto* privacy_sandbox_settings =
       PrivacySandboxSettingsFactory::GetForProfile(profile);
   DCHECK(privacy_sandbox_settings);
   bool allowed = privacy_sandbox_settings->IsSharedStorageAllowed(
       top_frame_origin, accessing_origin);
-  content_settings::PageSpecificContentSettings::BrowsingDataAccessed(
-      rfh, blink::StorageKey(accessing_origin),
-      BrowsingDataModel::StorageType::kSharedStorage, !allowed);
+  if (rfh) {
+    content_settings::PageSpecificContentSettings::BrowsingDataAccessed(
+        rfh, blink::StorageKey(accessing_origin),
+        BrowsingDataModel::StorageType::kSharedStorage, !allowed);
+  }
   return allowed;
 }
 
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index 3284619..ead5dfd 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -321,7 +321,8 @@
       const url::Origin* impression_origin,
       const url::Origin* conversion_origin,
       const url::Origin* reporting_origin) override;
-  bool IsSharedStorageAllowed(content::RenderFrameHost* rfh,
+  bool IsSharedStorageAllowed(content::BrowserContext* browser_context,
+                              content::RenderFrameHost* rfh,
                               const url::Origin& top_frame_origin,
                               const url::Origin& accessing_origin) override;
   bool IsPrivateAggregationAllowed(
diff --git a/chrome/browser/devtools/protocol/cast_handler_unittest.cc b/chrome/browser/devtools/protocol/cast_handler_unittest.cc
index b6cbc67..b0bd35c 100644
--- a/chrome/browser/devtools/protocol/cast_handler_unittest.cc
+++ b/chrome/browser/devtools/protocol/cast_handler_unittest.cc
@@ -83,8 +83,6 @@
 
  protected:
   void EnableHandler() {
-    EXPECT_CALL(*router_, RegisterMediaRoutesObserver(_))
-        .WillOnce(SaveArg<0>(&routes_observer_));
     EXPECT_CALL(*router_, RegisterMediaSinksObserver(_))
         .WillRepeatedly(
             WithArg<0>([this](media_router::MediaSinksObserver* observer) {
@@ -102,7 +100,6 @@
 
   std::unique_ptr<CastHandler> handler_;
   media_router::MockMediaRouter* router_ = nullptr;
-  media_router::MediaRoutesObserver* routes_observer_ = nullptr;
   media_router::MediaSinksObserver* desktop_sinks_observer_ = nullptr;
   media_router::MediaSinksObserver* sinks_observer_ = nullptr;
 };
@@ -201,14 +198,14 @@
 
 TEST_F(CastHandlerTest, StopCasting) {
   sinks_observer_->OnSinksUpdated({sink1, sink2}, {});
-  routes_observer_->OnRoutesUpdated({Route1()});
+  router_->routes_observers().begin()->OnRoutesUpdated({Route1()});
   EXPECT_CALL(*router_, TerminateRoute(kRouteId1));
   EXPECT_TRUE(handler_->StopCasting(kSinkName1).IsSuccess());
 }
 
 TEST_F(CastHandlerTest, StopCastingWithInvalidName) {
   sinks_observer_->OnSinksUpdated({sink1, sink2}, {});
-  routes_observer_->OnRoutesUpdated({Route1()});
+  router_->routes_observers().begin()->OnRoutesUpdated({Route1()});
   // Attempting to stop casting to a sink without a route should fail.
   EXPECT_TRUE(handler_->StopCasting(kSinkName2).IsError());
 }
diff --git a/chrome/browser/download/bubble/download_bubble_controller.cc b/chrome/browser/download/bubble/download_bubble_controller.cc
index c13525e9..4aec509 100644
--- a/chrome/browser/download/bubble/download_bubble_controller.cc
+++ b/chrome/browser/download/bubble/download_bubble_controller.cc
@@ -391,10 +391,12 @@
     case DownloadCommands::DISCARD:
       DownloadItemWarningData::AddWarningActionEvent(
           model->GetDownloadItem(),
-          is_main_view ? DownloadItemWarningData::BUBBLE_MAINPAGE
-                       : DownloadItemWarningData::BUBBLE_SUBPAGE,
-          command == DownloadCommands::KEEP ? DownloadItemWarningData::PROCEED
-                                            : DownloadItemWarningData::DISCARD);
+          is_main_view
+              ? DownloadItemWarningData::WarningSurface::BUBBLE_MAINPAGE
+              : DownloadItemWarningData::WarningSurface::BUBBLE_SUBPAGE,
+          command == DownloadCommands::KEEP
+              ? DownloadItemWarningData::WarningAction::PROCEED
+              : DownloadItemWarningData::WarningAction::DISCARD);
       commands.ExecuteCommand(command);
       break;
     case DownloadCommands::REVIEW:
diff --git a/chrome/browser/download/download_danger_prompt.cc b/chrome/browser/download/download_danger_prompt.cc
index 58f7169d4..8dc335d7 100644
--- a/chrome/browser/download/download_danger_prompt.cc
+++ b/chrome/browser/download/download_danger_prompt.cc
@@ -69,15 +69,16 @@
   DownloadItemWarningData::WarningAction warning_action;
   switch (action) {
     case Action::ACCEPT:
-      warning_action = DownloadItemWarningData::PROCEED;
+      warning_action = DownloadItemWarningData::WarningAction::PROCEED;
       break;
     case Action::CANCEL:
-      warning_action = DownloadItemWarningData::CANCEL;
+      warning_action = DownloadItemWarningData::WarningAction::CANCEL;
       break;
     case Action::DISMISS:
-      warning_action = DownloadItemWarningData::CLOSE;
+      warning_action = DownloadItemWarningData::WarningAction::CLOSE;
       break;
   }
   DownloadItemWarningData::AddWarningActionEvent(
-      download, DownloadItemWarningData::DOWNLOAD_PROMPT, warning_action);
+      download, DownloadItemWarningData::WarningSurface::DOWNLOAD_PROMPT,
+      warning_action);
 }
diff --git a/chrome/browser/download/download_item_warning_data.cc b/chrome/browser/download/download_item_warning_data.cc
index 3c729956..f20a3a5 100644
--- a/chrome/browser/download/download_item_warning_data.cc
+++ b/chrome/browser/download/download_item_warning_data.cc
@@ -46,6 +46,10 @@
   base::UmaHistogramEnumeration(
       "Download.WarningData.SurfaceWithoutWarningShown", surface);
 }
+
+void RecordWarningActionAdded(WarningAction action) {
+  base::UmaHistogramEnumeration("Download.WarningData.ActionAdded", action);
+}
 }  // namespace
 
 // static
@@ -81,6 +85,7 @@
     if (data->warning_first_shown_time_.is_null()) {
       RecordAddWarningActionEventOutcome(
           AddWarningActionEventOutcome::ADDED_WARNING_FIRST_SHOWN);
+      RecordWarningActionAdded(action);
       data->warning_first_shown_time_ = base::Time::Now();
     } else {
       RecordAddWarningActionEventOutcome(
@@ -102,12 +107,15 @@
   int64_t action_latency =
       (base::Time::Now() - data->warning_first_shown_time_).InMilliseconds();
   bool is_terminal_action =
-      (action == PROCEED || action == DISCARD) ? true : false;
+      (action == WarningAction::PROCEED || action == WarningAction::DISCARD)
+          ? true
+          : false;
   DCHECK_NE(WarningAction::SHOWN, action);
   data->action_events_.emplace_back(surface, action, action_latency,
                                     is_terminal_action);
   RecordAddWarningActionEventOutcome(
       AddWarningActionEventOutcome::ADDED_WARNING_ACTION);
+  RecordWarningActionAdded(action);
 }
 
 // static
@@ -116,57 +124,57 @@
     const WarningActionEvent& event) {
   ClientSafeBrowsingReportRequest::DownloadWarningAction action;
   switch (event.surface) {
-    case DownloadItemWarningData::BUBBLE_MAINPAGE:
+    case DownloadItemWarningData::WarningSurface::BUBBLE_MAINPAGE:
       action.set_surface(ClientSafeBrowsingReportRequest::
                              DownloadWarningAction::BUBBLE_MAINPAGE);
       break;
-    case DownloadItemWarningData::BUBBLE_SUBPAGE:
+    case DownloadItemWarningData::WarningSurface::BUBBLE_SUBPAGE:
       action.set_surface(ClientSafeBrowsingReportRequest::
                              DownloadWarningAction::BUBBLE_SUBPAGE);
       break;
-    case DownloadItemWarningData::DOWNLOADS_PAGE:
+    case DownloadItemWarningData::WarningSurface::DOWNLOADS_PAGE:
       action.set_surface(ClientSafeBrowsingReportRequest::
                              DownloadWarningAction::DOWNLOADS_PAGE);
       break;
-    case DownloadItemWarningData::DOWNLOAD_PROMPT:
+    case DownloadItemWarningData::WarningSurface::DOWNLOAD_PROMPT:
       action.set_surface(ClientSafeBrowsingReportRequest::
                              DownloadWarningAction::DOWNLOAD_PROMPT);
       break;
   }
   switch (event.action) {
-    case DownloadItemWarningData::PROCEED:
+    case DownloadItemWarningData::WarningAction::PROCEED:
       action.set_action(
           ClientSafeBrowsingReportRequest::DownloadWarningAction::PROCEED);
       break;
-    case DownloadItemWarningData::DISCARD:
+    case DownloadItemWarningData::WarningAction::DISCARD:
       action.set_action(
           ClientSafeBrowsingReportRequest::DownloadWarningAction::DISCARD);
       break;
-    case DownloadItemWarningData::KEEP:
+    case DownloadItemWarningData::WarningAction::KEEP:
       action.set_action(
           ClientSafeBrowsingReportRequest::DownloadWarningAction::KEEP);
       break;
-    case DownloadItemWarningData::CLOSE:
+    case DownloadItemWarningData::WarningAction::CLOSE:
       action.set_action(
           ClientSafeBrowsingReportRequest::DownloadWarningAction::CLOSE);
       break;
-    case DownloadItemWarningData::CANCEL:
+    case DownloadItemWarningData::WarningAction::CANCEL:
       action.set_action(
           ClientSafeBrowsingReportRequest::DownloadWarningAction::CANCEL);
       break;
-    case DownloadItemWarningData::DISMISS:
+    case DownloadItemWarningData::WarningAction::DISMISS:
       action.set_action(
           ClientSafeBrowsingReportRequest::DownloadWarningAction::DISMISS);
       break;
-    case DownloadItemWarningData::BACK:
+    case DownloadItemWarningData::WarningAction::BACK:
       action.set_action(
           ClientSafeBrowsingReportRequest::DownloadWarningAction::BACK);
       break;
-    case DownloadItemWarningData::OPEN_SUBPAGE:
+    case DownloadItemWarningData::WarningAction::OPEN_SUBPAGE:
       action.set_action(
           ClientSafeBrowsingReportRequest::DownloadWarningAction::OPEN_SUBPAGE);
       break;
-    case DownloadItemWarningData::SHOWN:
+    case DownloadItemWarningData::WarningAction::SHOWN:
       NOTREACHED();
       break;
   }
diff --git a/chrome/browser/download/download_item_warning_data.h b/chrome/browser/download/download_item_warning_data.h
index 84f8838..92c2716 100644
--- a/chrome/browser/download/download_item_warning_data.h
+++ b/chrome/browser/download/download_item_warning_data.h
@@ -24,8 +24,8 @@
   // go/chrome-download-warning-surfaces for details.
   // These values are persisted to logs. Entries should not be renumbered and
   // numeric values should never be reused.
-  enum WarningSurface {
-    // Applicable actions: DISCARD
+  enum class WarningSurface {
+    // Applicable actions: DISCARD, OPEN_SUBPAGE
     BUBBLE_MAINPAGE = 1,
     // Applicable actions: PROCEED, DISCARD, DISMISS, CLOSE, BACK
     BUBBLE_SUBPAGE = 2,
@@ -37,7 +37,9 @@
   };
 
   // Users action on the warning surface.
-  enum WarningAction {
+  // These values are persisted to logs. Entries should not be renumbered and
+  // numeric values should never be reused.
+  enum class WarningAction {
     // The warning is shown. This is a special action that may not be triggered
     // by user. We will use this action as the anchor to track the latency of
     // other actions.
@@ -61,7 +63,8 @@
     // to the bubble main page.
     BACK = 7,
     // The user has opened the subpage from the main page.
-    OPEN_SUBPAGE = 8
+    OPEN_SUBPAGE = 8,
+    kMaxValue = OPEN_SUBPAGE
   };
 
   struct WarningActionEvent {
diff --git a/chrome/browser/download/download_item_warning_data_unittest.cc b/chrome/browser/download/download_item_warning_data_unittest.cc
index ebd0af6..47b130b8 100644
--- a/chrome/browser/download/download_item_warning_data_unittest.cc
+++ b/chrome/browser/download/download_item_warning_data_unittest.cc
@@ -39,13 +39,14 @@
     if (actual_event.surface != expected_surface) {
       success = false;
       ADD_FAILURE() << "Warning action event should have surface "
-                    << expected_surface << ", but found "
-                    << actual_event.surface;
+                    << static_cast<int>(expected_surface) << ", but found "
+                    << static_cast<int>(actual_event.surface);
     }
     if (actual_event.action != expected_action) {
       success = false;
       ADD_FAILURE() << "Warning action event should have action "
-                    << expected_action << ", but found " << actual_event.action;
+                    << static_cast<int>(expected_action) << ", but found "
+                    << static_cast<int>(actual_event.action);
     }
     if (actual_event.action_latency_msec != expected_latency) {
       success = false;
diff --git a/chrome/browser/download/download_ui_model.cc b/chrome/browser/download/download_ui_model.cc
index 123b67a..46de7ef 100644
--- a/chrome/browser/download/download_ui_model.cc
+++ b/chrome/browser/download/download_ui_model.cc
@@ -1780,3 +1780,33 @@
 void DownloadUIModel::DetermineAndSetShouldPreferOpeningInBrowser(
     const base::FilePath& target_path,
     bool is_filetype_handled_safely) {}
+
+std::u16string DownloadUIModel::GetInProgressAccessibleAlertText() const {
+  // Prefer to announce the time remaining, if known.
+  base::TimeDelta remaining;
+  if (TimeRemaining(&remaining)) {
+    // If complete, skip this round: a completion status update is coming soon.
+    if (remaining.is_zero())
+      return std::u16string();
+
+    const std::u16string remaining_string =
+        ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_REMAINING,
+                               ui::TimeFormat::LENGTH_SHORT, remaining);
+    return l10n_util::GetStringFUTF16(
+        IDS_DOWNLOAD_STATUS_TIME_REMAINING_ACCESSIBLE_ALERT, remaining_string);
+  }
+
+  // Time remaining is unknown, try to announce percent remaining.
+  if (PercentComplete() > 0) {
+    DCHECK_LE(PercentComplete(), 100);
+    return l10n_util::GetStringFUTF16Int(
+        IDS_DOWNLOAD_STATUS_PERCENT_COMPLETE_ACCESSIBLE_ALERT,
+        100 - PercentComplete());
+  }
+
+  // Percent remaining is also unknown, announce bytes to download.
+  return l10n_util::GetStringFUTF16(
+      IDS_DOWNLOAD_STATUS_IN_PROGRESS_ACCESSIBLE_ALERT,
+      ui::FormatBytes(GetTotalBytes()),
+      GetFileNameToReportUser().LossyDisplayName());
+}
diff --git a/chrome/browser/download/download_ui_model.h b/chrome/browser/download/download_ui_model.h
index 69103e5..d289774e 100644
--- a/chrome/browser/download/download_ui_model.h
+++ b/chrome/browser/download/download_ui_model.h
@@ -536,6 +536,10 @@
       const base::FilePath& target_path,
       bool is_filetype_handled_safely);
 
+  // Returns the accessible alert text that should be announced when the
+  // download is in progress.
+  virtual std::u16string GetInProgressAccessibleAlertText() const;
+
  protected:
   // Returns the MIME type of the download.
   virtual std::string GetMimeType() const;
diff --git a/chrome/browser/enterprise/idle/action.cc b/chrome/browser/enterprise/idle/action.cc
index 235da753..71d89a7 100644
--- a/chrome/browser/enterprise/idle/action.cc
+++ b/chrome/browser/enterprise/idle/action.cc
@@ -30,10 +30,11 @@
   CloseBrowsersAction() : Action(ActionType::kCloseBrowsers) {}
 
   void Run(Profile* profile, Continuation continuation) override {
-    int minutes = profile->GetPrefs()->GetInteger(prefs::kIdleTimeout);
+    base::TimeDelta timeout =
+        profile->GetPrefs()->GetTimeDelta(prefs::kIdleTimeout);
     continuation_ = std::move(continuation);
     subscription_ = BrowserCloser::GetInstance()->ShowDialogAndCloseBrowsers(
-        profile, base::Minutes(minutes),
+        profile, timeout,
         base::BindOnce(&CloseBrowsersAction::OnCloseFinished,
                        base::Unretained(this)));
   }
diff --git a/chrome/browser/enterprise/idle/idle_service.cc b/chrome/browser/enterprise/idle/idle_service.cc
index 0e749d7..8d112b08 100644
--- a/chrome/browser/enterprise/idle/idle_service.cc
+++ b/chrome/browser/enterprise/idle/idle_service.cc
@@ -33,10 +33,11 @@
 IdleService::~IdleService() = default;
 
 void IdleService::OnIdleTimeoutPrefChanged() {
-  int minutes = profile_->GetPrefs()->GetInteger(prefs::kIdleTimeout);
-  if (minutes > 0) {
+  base::TimeDelta timeout =
+      profile_->GetPrefs()->GetTimeDelta(prefs::kIdleTimeout);
+  if (timeout.is_positive()) {
     // `is_idle_` will auto-update in 1 second, no need to set it here.
-    idle_threshold_ = base::Minutes(minutes);
+    idle_threshold_ = timeout;
     if (!polling_service_observation_.IsObserving()) {
       polling_service_observation_.Observe(
           ui::IdlePollingService::GetInstance());
diff --git a/chrome/browser/enterprise/idle/idle_service_factory.cc b/chrome/browser/enterprise/idle/idle_service_factory.cc
index 625a5c6..cced806 100644
--- a/chrome/browser/enterprise/idle/idle_service_factory.cc
+++ b/chrome/browser/enterprise/idle/idle_service_factory.cc
@@ -36,8 +36,7 @@
 
 void IdleServiceFactory::RegisterProfilePrefs(
     user_prefs::PrefRegistrySyncable* registry) {
-  // TODO(crbug.com/1316551): Use TimeDeltaPref instead.
-  registry->RegisterIntegerPref(prefs::kIdleTimeout, 0);
+  registry->RegisterTimeDeltaPref(prefs::kIdleTimeout, base::TimeDelta());
   registry->RegisterListPref(prefs::kIdleTimeoutActions);
 }
 
diff --git a/chrome/browser/enterprise/idle/idle_timeout_policy_handler.cc b/chrome/browser/enterprise/idle/idle_timeout_policy_handler.cc
new file mode 100644
index 0000000..770e073
--- /dev/null
+++ b/chrome/browser/enterprise/idle/idle_timeout_policy_handler.cc
@@ -0,0 +1,139 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/enterprise/idle/idle_timeout_policy_handler.h"
+
+#include <string>
+
+#include "base/json/values_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "chrome/browser/enterprise/idle/action.h"
+#include "chrome/browser/prefs/session_startup_pref.h"
+#include "chrome/common/pref_names.h"
+#include "components/policy/core/browser/configuration_policy_handler.h"
+#include "components/policy/core/browser/policy_error_map.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/schema.h"
+#include "components/policy/policy_constants.h"
+#include "components/prefs/pref_value_map.h"
+#include "components/strings/grit/components_strings.h"
+
+namespace enterprise_idle {
+
+namespace {
+
+// If `other_policy_name` is unset, adds an error to `errors` and returns false.
+bool CheckOtherPolicySet(const policy::PolicyMap& policies,
+                         const std::string& this_policy_name,
+                         const std::string& other_policy_name,
+                         policy::PolicyErrorMap* errors) {
+  if (policies.GetValueUnsafe(other_policy_name))
+    return true;
+
+  errors->AddError(this_policy_name, IDS_POLICY_DEPENDENCY_ERROR_ANY_VALUE,
+                   other_policy_name);
+  return false;
+}
+
+}  // namespace
+
+IdleTimeoutPolicyHandler::IdleTimeoutPolicyHandler()
+    : policy::IntRangePolicyHandler(policy::key::kIdleTimeout,
+                                    prefs::kIdleTimeout,
+                                    1,
+                                    INT_MAX,
+                                    true) {}
+
+IdleTimeoutPolicyHandler::~IdleTimeoutPolicyHandler() = default;
+
+void IdleTimeoutPolicyHandler::ApplyPolicySettings(
+    const policy::PolicyMap& policies,
+    PrefValueMap* prefs) {
+  const base::Value* value =
+      policies.GetValue(policy::key::kIdleTimeout, base::Value::Type::INTEGER);
+  DCHECK(value);
+
+  // Apply a minimum of 1.
+  base::TimeDelta time_delta = base::Minutes(std::max(value->GetInt(), 1));
+  prefs->SetValue(prefs::kIdleTimeout, base::TimeDeltaToValue(time_delta));
+}
+
+bool IdleTimeoutPolicyHandler::CheckPolicySettings(
+    const policy::PolicyMap& policies,
+    policy::PolicyErrorMap* errors) {
+  // Nothing to do if unset.
+  if (!policies.GetValueUnsafe(policy::key::kIdleTimeout))
+    return false;
+
+  // Check that it's an integer, and that it's >= 1.
+  if (!policy::IntRangePolicyHandler::CheckPolicySettings(policies, errors))
+    return false;
+
+  // If IdleTimeoutActions is unset, add an error and do nothing.
+  if (!CheckOtherPolicySet(policies, policy::key::kIdleTimeout,
+                           policy::key::kIdleTimeoutActions, errors)) {
+    return false;
+  }
+
+  return true;
+}
+
+IdleTimeoutActionsPolicyHandler::IdleTimeoutActionsPolicyHandler(
+    policy::Schema schema)
+    : policy::SchemaValidatingPolicyHandler(
+          policy::key::kIdleTimeoutActions,
+          schema.GetKnownProperty(policy::key::kIdleTimeoutActions),
+          policy::SCHEMA_ALLOW_UNKNOWN_AND_INVALID_LIST_ENTRY) {}
+
+IdleTimeoutActionsPolicyHandler::~IdleTimeoutActionsPolicyHandler() = default;
+
+void IdleTimeoutActionsPolicyHandler::ApplyPolicySettings(
+    const policy::PolicyMap& policies,
+    PrefValueMap* prefs) {
+  const base::Value* policy_value = policies.GetValue(
+      policy::key::kIdleTimeoutActions, base::Value::Type::LIST);
+  DCHECK(policy_value);
+
+  // Convert strings to integers (from the ActionType enum).
+  base::Value::List converted_actions;
+  for (const base::Value& action : policy_value->GetList()) {
+    if (!action.is_string())
+      continue;
+    const std::string& name = action.GetString();
+    if (name == "close_browsers")
+      converted_actions.Append(static_cast<int>(ActionType::kCloseBrowsers));
+    else if (name == "show_profile_picker")
+      converted_actions.Append(
+          static_cast<int>(ActionType::kShowProfilePicker));
+    // Silently drop unsupported values.
+  }
+  prefs->SetValue(prefs::kIdleTimeoutActions,
+                  base::Value(std::move(converted_actions)));
+}
+
+bool IdleTimeoutActionsPolicyHandler::CheckPolicySettings(
+    const policy::PolicyMap& policies,
+    policy::PolicyErrorMap* errors) {
+  // Nothing to do if unset.
+  if (!policies.GetValueUnsafe(policy::key::kIdleTimeoutActions))
+    return false;
+
+  // Check that it's a list of strings, and that they're supported enum values.
+  // Unsupported enum values are dropped, with a warning on chrome://policy.
+  if (!policy::SchemaValidatingPolicyHandler::CheckPolicySettings(policies,
+                                                                  errors)) {
+    return false;
+  }
+
+  // If IdleTimeout is unset, add an error and do nothing.
+  if (!CheckOtherPolicySet(policies, policy::key::kIdleTimeoutActions,
+                           policy::key::kIdleTimeout, errors)) {
+    return false;
+  }
+
+  return true;
+}
+}  // namespace enterprise_idle
diff --git a/chrome/browser/enterprise/idle/idle_timeout_policy_handler.h b/chrome/browser/enterprise/idle/idle_timeout_policy_handler.h
new file mode 100644
index 0000000..c40759c
--- /dev/null
+++ b/chrome/browser/enterprise/idle/idle_timeout_policy_handler.h
@@ -0,0 +1,58 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ENTERPRISE_IDLE_IDLE_TIMEOUT_POLICY_HANDLER_H_
+#define CHROME_BROWSER_ENTERPRISE_IDLE_IDLE_TIMEOUT_POLICY_HANDLER_H_
+
+#include "components/policy/core/browser/configuration_policy_handler.h"
+
+class PrefValueMap;
+
+namespace policy {
+class PolicyErrorMap;
+class PolicyMap;
+}  // namespace policy
+
+namespace enterprise_idle {
+
+// Handles IdleTimeout policy.
+class IdleTimeoutPolicyHandler : public policy::IntRangePolicyHandler {
+ public:
+  IdleTimeoutPolicyHandler();
+
+  IdleTimeoutPolicyHandler(const IdleTimeoutPolicyHandler&) = delete;
+  IdleTimeoutPolicyHandler& operator=(const IdleTimeoutPolicyHandler&) = delete;
+
+  ~IdleTimeoutPolicyHandler() override;
+
+  // ConfigurationPolicyHandler methods:
+  void ApplyPolicySettings(const policy::PolicyMap& policies,
+                           PrefValueMap* prefs) override;
+  bool CheckPolicySettings(const policy::PolicyMap& policies,
+                           policy::PolicyErrorMap* errors) override;
+};
+
+// Handles IdleTimeoutActions policy.
+class IdleTimeoutActionsPolicyHandler
+    : public policy::SchemaValidatingPolicyHandler {
+ public:
+  explicit IdleTimeoutActionsPolicyHandler(policy::Schema schema);
+
+  IdleTimeoutActionsPolicyHandler(const IdleTimeoutActionsPolicyHandler&) =
+      delete;
+  IdleTimeoutActionsPolicyHandler& operator=(
+      const IdleTimeoutActionsPolicyHandler&) = delete;
+
+  ~IdleTimeoutActionsPolicyHandler() override;
+
+  // ConfigurationPolicyHandler methods:
+  void ApplyPolicySettings(const policy::PolicyMap& policies,
+                           PrefValueMap* prefs) override;
+  bool CheckPolicySettings(const policy::PolicyMap& policies,
+                           policy::PolicyErrorMap* errors) override;
+};
+
+}  // namespace enterprise_idle
+
+#endif  // CHROME_BROWSER_ENTERPRISE_IDLE_IDLE_TIMEOUT_POLICY_HANDLER_H_
diff --git a/chrome/browser/enterprise/idle/idle_timeout_policy_handler_unittest.cc b/chrome/browser/enterprise/idle/idle_timeout_policy_handler_unittest.cc
new file mode 100644
index 0000000..f6b3583
--- /dev/null
+++ b/chrome/browser/enterprise/idle/idle_timeout_policy_handler_unittest.cc
@@ -0,0 +1,272 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/enterprise/idle/idle_timeout_policy_handler.h"
+
+#include <string>
+
+#include "base/json/values_util.h"
+#include "base/ranges/algorithm.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "chrome/browser/enterprise/idle/action.h"
+#include "chrome/common/pref_names.h"
+#include "components/policy/core/browser/configuration_policy_handler.h"
+#include "components/policy/core/browser/policy_error_map.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/core/common/policy_types.h"
+#include "components/policy/policy_constants.h"
+#include "components/prefs/pref_value_map.h"
+#include "components/strings/grit/components_strings.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace enterprise_idle {
+
+using base::UTF8ToUTF16;
+
+class IdleTimeoutPolicyHandlerTest : public testing::Test {
+ protected:
+  void SetPolicyValue(const std::string& policy, base::Value value) {
+    policies_.Set(policy, policy::POLICY_LEVEL_MANDATORY,
+                  policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_PLATFORM,
+                  std::move(value), nullptr);
+  }
+
+  bool CheckPolicySettings() {
+    bool results[] = {
+        timeout_handler_.CheckPolicySettings(policies_, &errors_),
+        actions_handler_.CheckPolicySettings(policies_, &errors_),
+    };
+    return base::ranges::all_of(base::span(results), std::identity{});
+  }
+
+  void ApplyPolicySettings() {
+    timeout_handler_.ApplyPolicySettings(policies_, &prefs_);
+    actions_handler_.ApplyPolicySettings(policies_, &prefs_);
+  }
+
+  void CheckAndApplyPolicySettings() {
+    if (CheckPolicySettings())
+      ApplyPolicySettings();
+  }
+
+  policy::PolicyErrorMap& errors() { return errors_; }
+  PrefValueMap& prefs() { return prefs_; }
+
+ private:
+  policy::PolicyMap policies_;
+  policy::PolicyErrorMap errors_;
+  PrefValueMap prefs_;
+  policy::Schema schema_ = policy::Schema::Wrap(policy::GetChromeSchemaData());
+  IdleTimeoutPolicyHandler timeout_handler_;
+  IdleTimeoutActionsPolicyHandler actions_handler_ =
+      IdleTimeoutActionsPolicyHandler(schema_);
+};
+
+TEST_F(IdleTimeoutPolicyHandlerTest, PoliciesNotSet) {
+  CheckAndApplyPolicySettings();
+
+  // Shouldn't error.
+  EXPECT_EQ(errors().size(), 0U);
+
+  // Prefs should not be set.
+  const base::Value* pref_value;
+  EXPECT_FALSE(prefs().GetValue(prefs::kIdleTimeout, &pref_value));
+  EXPECT_FALSE(prefs().GetValue(prefs::kIdleTimeoutActions, &pref_value));
+}
+
+TEST_F(IdleTimeoutPolicyHandlerTest, JustTimeout) {
+  // IdleTimeout is set, but not IdleTimeoutActions.
+  SetPolicyValue(policy::key::kIdleTimeout, base::Value(15));
+
+  CheckAndApplyPolicySettings();
+
+  // Should have an error.
+  auto expected_error =
+      l10n_util::GetStringFUTF16(IDS_POLICY_DEPENDENCY_ERROR_ANY_VALUE,
+                                 UTF8ToUTF16(policy::key::kIdleTimeoutActions));
+  EXPECT_EQ(errors().size(), 1U);
+  EXPECT_EQ(errors().begin()->second.message, expected_error);
+
+  // Prefs should not be set.
+  const base::Value* pref_value;
+  EXPECT_FALSE(prefs().GetValue(prefs::kIdleTimeout, &pref_value));
+  EXPECT_FALSE(prefs().GetValue(prefs::kIdleTimeoutActions, &pref_value));
+}
+
+TEST_F(IdleTimeoutPolicyHandlerTest, JustActions) {
+  // IdleTimeoutActions is set, but not IdleTimeout.
+  SetPolicyValue(policy::key::kIdleTimeoutActions,
+                 base::Value(base::Value::List()));
+
+  CheckAndApplyPolicySettings();
+
+  // Should have an error.
+  auto expected_error =
+      l10n_util::GetStringFUTF16(IDS_POLICY_DEPENDENCY_ERROR_ANY_VALUE,
+                                 UTF8ToUTF16(policy::key::kIdleTimeout));
+  EXPECT_EQ(errors().size(), 1U);
+  EXPECT_EQ(errors().begin()->second.message, expected_error);
+
+  // Prefs should not be set.
+  const base::Value* pref_value;
+  EXPECT_FALSE(prefs().GetValue(prefs::kIdleTimeout, &pref_value));
+  EXPECT_FALSE(prefs().GetValue(prefs::kIdleTimeoutActions, &pref_value));
+}
+
+TEST_F(IdleTimeoutPolicyHandlerTest, InvalidTimeoutPolicyType) {
+  // Give an integer to a string policy.
+  SetPolicyValue(policy::key::kIdleTimeout, base::Value("invalid"));
+  SetPolicyValue(policy::key::kIdleTimeoutActions,
+                 base::Value(base::Value::List()));
+
+  CheckAndApplyPolicySettings();
+
+  // Should have an error.
+  auto expected_error = l10n_util::GetStringFUTF16(
+      IDS_POLICY_TYPE_ERROR,
+      UTF8ToUTF16(base::Value::GetTypeName(base::Value::Type::INTEGER)));
+  EXPECT_EQ(errors().size(), 1U);
+  EXPECT_EQ(errors().begin()->second.message, expected_error);
+
+  // Prefs should not be set.
+  const base::Value* pref_value;
+  EXPECT_FALSE(prefs().GetValue(prefs::kIdleTimeout, &pref_value));
+  EXPECT_FALSE(prefs().GetValue(prefs::kIdleTimeoutActions, &pref_value));
+}
+
+TEST_F(IdleTimeoutPolicyHandlerTest, InvalidActionsPolicyType) {
+  // Give a string to a string-enum policy.
+  SetPolicyValue(policy::key::kIdleTimeout, base::Value(5));
+  SetPolicyValue(policy::key::kIdleTimeoutActions, base::Value("invalid"));
+
+  CheckAndApplyPolicySettings();
+
+  // Should have an error.
+  auto expected_error = l10n_util::GetStringFUTF16(
+      IDS_POLICY_SCHEMA_VALIDATION_ERROR,
+      u"Policy type mismatch: expected: \"list\", actual: \"string\".");
+  EXPECT_EQ(errors().size(), 1U);
+  EXPECT_EQ(errors().begin()->second.message, expected_error);
+
+  // Prefs should not be set.
+  const base::Value* pref_value;
+  EXPECT_FALSE(prefs().GetValue(prefs::kIdleTimeout, &pref_value));
+  EXPECT_FALSE(prefs().GetValue(prefs::kIdleTimeoutActions, &pref_value));
+}
+
+TEST_F(IdleTimeoutPolicyHandlerTest, InvalidActionWrongType) {
+  // IdleTimeoutActions is a list, but one of the elements is not even a string.
+  SetPolicyValue(policy::key::kIdleTimeout, base::Value(5));
+  base::Value::List list;
+  list.Append("close_browsers");
+  list.Append(34);
+  SetPolicyValue(policy::key::kIdleTimeoutActions,
+                 base::Value(std::move(list)));
+
+  CheckAndApplyPolicySettings();
+
+  // Should have an error.
+  auto expected_error = l10n_util::GetStringFUTF16(
+      IDS_POLICY_ERROR_WITH_PATH,
+      UTF8ToUTF16(policy::key::kIdleTimeoutActions) + u"[1]",
+      l10n_util::GetStringFUTF16(
+          IDS_POLICY_SCHEMA_VALIDATION_ERROR,
+          u"Policy type mismatch: expected: \"string\", actual: \"integer\"."));
+  EXPECT_EQ(errors().size(), 1U);
+  EXPECT_EQ(errors().begin()->second.message, expected_error);
+
+  // Prefs should not be set.
+  const base::Value* pref_value;
+  EXPECT_TRUE(prefs().GetValue(prefs::kIdleTimeout, &pref_value));
+  EXPECT_TRUE(prefs().GetValue(prefs::kIdleTimeoutActions, &pref_value));
+}
+
+TEST_F(IdleTimeoutPolicyHandlerTest, ValidConfiguration) {
+  SetPolicyValue(policy::key::kIdleTimeout, base::Value(15));
+  base::Value::List list;
+  list.Append("close_browsers");
+  list.Append("show_profile_picker");
+  SetPolicyValue(policy::key::kIdleTimeoutActions,
+                 base::Value(std::move(list)));
+
+  CheckAndApplyPolicySettings();
+
+  // Should have no errors.
+  EXPECT_TRUE(errors().empty());
+
+  // Prefs should be set.
+  const base::Value* pref_value;
+  EXPECT_TRUE(prefs().GetValue(prefs::kIdleTimeout, &pref_value));
+  ASSERT_THAT(pref_value, testing::NotNull());
+  EXPECT_EQ(base::TimeDeltaToValue(base::Minutes(15)), *pref_value);
+
+  EXPECT_TRUE(prefs().GetValue(prefs::kIdleTimeoutActions, &pref_value));
+  ASSERT_THAT(pref_value, testing::NotNull());
+  EXPECT_TRUE(pref_value->is_list());
+  EXPECT_THAT(
+      pref_value->GetList(),
+      testing::ElementsAre(static_cast<int>(ActionType::kCloseBrowsers),
+                           static_cast<int>(ActionType::kShowProfilePicker)));
+}
+
+TEST_F(IdleTimeoutPolicyHandlerTest, OneMinuteMinimum) {
+  // Set the policy to 0, which should clamp the pref to 1.
+  SetPolicyValue(policy::key::kIdleTimeout, base::Value(0));
+  SetPolicyValue(policy::key::kIdleTimeoutActions,
+                 base::Value(base::Value::List()));
+
+  CheckAndApplyPolicySettings();
+
+  // Should have an error.
+  auto expected_error =
+      l10n_util::GetStringFUTF16(IDS_POLICY_OUT_OF_RANGE_ERROR, u"0");
+  EXPECT_EQ(errors().size(), 1U);
+  EXPECT_EQ(errors().begin()->second.message, expected_error);
+
+  // Prefs should be set.
+  const base::Value* pref_value;
+  EXPECT_TRUE(prefs().GetValue(prefs::kIdleTimeout, &pref_value));
+  ASSERT_THAT(pref_value, testing::NotNull());
+  EXPECT_EQ(base::TimeDeltaToValue(base::Minutes(1)), *pref_value);
+}
+
+TEST_F(IdleTimeoutPolicyHandlerTest, ActionNotRecognized) {
+  // IdleTimeoutActions is a list, but one of the elements is not recognized
+  // as a valid option. Recognized actions are applied, but not the others.
+  SetPolicyValue(policy::key::kIdleTimeout, base::Value(5));
+  base::Value::List list;
+  list.Append("close_browsers");
+  list.Append("show_profile_picker");
+  list.Append("added_in_future_version_of_chrome");
+  SetPolicyValue(policy::key::kIdleTimeoutActions,
+                 base::Value(std::move(list)));
+
+  CheckAndApplyPolicySettings();
+
+  // Should have an error.
+  auto expected_error = l10n_util::GetStringFUTF16(
+      IDS_POLICY_ERROR_WITH_PATH,
+      UTF8ToUTF16(policy::key::kIdleTimeoutActions) + u"[2]",
+      l10n_util::GetStringFUTF16(IDS_POLICY_SCHEMA_VALIDATION_ERROR,
+                                 u"Invalid value for string"));
+  EXPECT_EQ(errors().size(), 1U);
+  EXPECT_EQ(errors().begin()->second.message, expected_error);
+
+  // Prefs should not be set.
+  const base::Value* pref_value;
+  EXPECT_TRUE(prefs().GetValue(prefs::kIdleTimeout, &pref_value));
+  EXPECT_TRUE(prefs().GetValue(prefs::kIdleTimeoutActions, &pref_value));
+  ASSERT_THAT(pref_value, testing::NotNull());
+  EXPECT_TRUE(pref_value->is_list());
+  EXPECT_THAT(
+      pref_value->GetList(),
+      testing::ElementsAre(static_cast<int>(ActionType::kCloseBrowsers),
+                           static_cast<int>(ActionType::kShowProfilePicker)));
+}
+
+}  // namespace enterprise_idle
diff --git a/chrome/browser/extensions/api/debugger/debugger_apitest.cc b/chrome/browser/extensions/api/debugger/debugger_apitest.cc
index 3417a0681..6a45147 100644
--- a/chrome/browser/extensions/api/debugger/debugger_apitest.cc
+++ b/chrome/browser/extensions/api/debugger/debugger_apitest.cc
@@ -148,10 +148,9 @@
       extension_function_test_utils::RunFunctionAndReturnSingleResult(
           get_targets_function.get(), "[]", browser()));
   EXPECT_TRUE(value->is_list());
-  const base::ListValue& targets = base::Value::AsListValue(*value);
 
   std::string debugger_target_id;
-  for (const base::Value& target_value : targets.GetList()) {
+  for (const base::Value& target_value : value->GetList()) {
     EXPECT_TRUE(target_value.is_dict());
     absl::optional<int> id = target_value.FindIntKey("tabId");
     if (id == tab_id) {
diff --git a/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_unittest.cc b/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_unittest.cc
index d3f04c72..aae884a 100644
--- a/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_unittest.cc
+++ b/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_unittest.cc
@@ -375,11 +375,10 @@
             function.get(), "[]" /* args */, browser_context());
     ASSERT_TRUE(result);
     ASSERT_TRUE(result->is_list());
-    const base::ListValue& ids_value = base::Value::AsListValue(*result);
 
     std::u16string error;
     std::vector<std::string> actual_ids;
-    for (const auto& val : ids_value.GetList())
+    for (const auto& val : result->GetList())
       actual_ids.push_back(val.GetString());
 
     EXPECT_THAT(expected_ids, UnorderedElementsAreArray(actual_ids));
diff --git a/chrome/browser/extensions/api/messaging/native_messaging_launch_from_native_unittest.cc b/chrome/browser/extensions/api/messaging/native_messaging_launch_from_native_unittest.cc
index 389c17f2..99582ef 100644
--- a/chrome/browser/extensions/api/messaging/native_messaging_launch_from_native_unittest.cc
+++ b/chrome/browser/extensions/api/messaging/native_messaging_launch_from_native_unittest.cc
@@ -107,11 +107,9 @@
     EXPECT_TRUE(base::PathService::Get(DIR_TEST_DATA, &path));
 
     std::string error;
-    scoped_refptr<Extension> extension(
-        Extension::Create(path, mojom::ManifestLocation::kInternal,
-                          base::Value::AsDictionaryValue(
-                              base::Value(manifest_builder.BuildDict())),
-                          Extension::NO_FLAGS, &error));
+    scoped_refptr<Extension> extension(Extension::Create(
+        path, mojom::ManifestLocation::kInternal, manifest_builder.BuildDict(),
+        Extension::NO_FLAGS, &error));
     ASSERT_TRUE(extension.get()) << error;
     ExtensionRegistry::Get(&profile_)->AddEnabled(extension);
     extension_id_ = extension->id();
diff --git a/chrome/browser/extensions/api/webrtc_audio_private/webrtc_audio_private_browsertest.cc b/chrome/browser/extensions/api/webrtc_audio_private/webrtc_audio_private_browsertest.cc
index 02ee5ccf..0f7a550 100644
--- a/chrome/browser/extensions/api/webrtc_audio_private/webrtc_audio_private_browsertest.cc
+++ b/chrome/browser/extensions/api/webrtc_audio_private/webrtc_audio_private_browsertest.cc
@@ -142,20 +142,20 @@
   GetAudioDeviceDescriptions(false, &devices);
 
   std::unique_ptr<base::Value> result = InvokeGetSinks();
-  const base::ListValue& sink_list = base::Value::AsListValue(*result);
+  const base::Value::List& sink_list = result->GetList();
 
   std::string result_string;
   JSONWriter::Write(*result, &result_string);
   VLOG(2) << result_string;
 
-  EXPECT_EQ(devices.size(), sink_list.GetList().size());
+  EXPECT_EQ(devices.size(), sink_list.size());
 
   // Iterate through both lists in lockstep and compare. The order
   // should be identical.
   size_t ix = 0;
   AudioDeviceDescriptions::const_iterator it = devices.begin();
-  for (; ix < sink_list.GetList().size() && it != devices.end(); ++ix, ++it) {
-    const base::Value& value = sink_list.GetList()[ix];
+  for (; ix < sink_list.size() && it != devices.end(); ++ix, ++it) {
+    const base::Value& value = sink_list[ix];
     EXPECT_TRUE(value.is_dict());
     const base::Value::Dict& dict = value.GetDict();
     const std::string* sink_id = dict.FindString("sinkId");
diff --git a/chrome/browser/extensions/extension_install_prompt.cc b/chrome/browser/extensions/extension_install_prompt.cc
index f711ce3..855896ac 100644
--- a/chrome/browser/extensions/extension_install_prompt.cc
+++ b/chrome/browser/extensions/extension_install_prompt.cc
@@ -37,6 +37,7 @@
 #include "extensions/common/manifest_constants.h"
 #include "extensions/common/manifest_handlers/icons_handler.h"
 #include "extensions/common/permissions/permission_set.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/resource/resource_bundle.h"
 #include "ui/base/ui_base_types.h"
@@ -476,30 +477,27 @@
 // static
 scoped_refptr<Extension>
 ExtensionInstallPrompt::GetLocalizedExtensionForDisplay(
-    const base::DictAdapterForMigration manifest,
+    const base::Value::Dict& manifest,
     int flags,
     const std::string& id,
     const std::string& localized_name,
     const std::string& localized_description,
     std::string* error) {
-  std::unique_ptr<base::DictionaryValue> localized_manifest;
+  absl::optional<base::Value::Dict> localized_manifest;
   if (!localized_name.empty() || !localized_description.empty()) {
-    localized_manifest = base::DictionaryValue::From(
-        base::Value::ToUniquePtrValue(base::Value(manifest.Clone())));
+    localized_manifest = manifest.Clone();
     if (!localized_name.empty()) {
-      localized_manifest->SetStringKey(extensions::manifest_keys::kName,
-                                       localized_name);
+      localized_manifest->Set(extensions::manifest_keys::kName, localized_name);
     }
     if (!localized_description.empty()) {
-      localized_manifest->SetStringKey(extensions::manifest_keys::kDescription,
-                                       localized_description);
+      localized_manifest->Set(extensions::manifest_keys::kDescription,
+                              localized_description);
     }
   }
 
   return Extension::Create(
       base::FilePath(), extensions::mojom::ManifestLocation::kInternal,
-      localized_manifest.get() ? *localized_manifest : manifest, flags, id,
-      error);
+      localized_manifest ? *localized_manifest : manifest, flags, id, error);
 }
 
 ExtensionInstallPrompt::ExtensionInstallPrompt(content::WebContents* contents)
diff --git a/chrome/browser/extensions/extension_install_prompt.h b/chrome/browser/extensions/extension_install_prompt.h
index e67259d..a8feacb 100644
--- a/chrome/browser/extensions/extension_install_prompt.h
+++ b/chrome/browser/extensions/extension_install_prompt.h
@@ -285,7 +285,7 @@
   // Creates a dummy extension from the |manifest|, replacing the name and
   // description with the localizations if provided.
   static scoped_refptr<extensions::Extension> GetLocalizedExtensionForDisplay(
-      const base::DictAdapterForMigration manifest,
+      const base::Value::Dict& manifest,
       int flags,  // Extension::InitFromValueFlags
       const std::string& id,
       const std::string& localized_name,
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index b2e5bcd..f792675 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -2290,7 +2290,7 @@
   {
     "name": "enable-external-keyboards-in-diagnostics-app",
     "owners": [ "//ash/webui/diagnostics_ui/OWNERS" ],
-    "expiry_milestone": 110
+    "expiry_milestone": 120
   },
   {
     "name": "enable-fake-keyboard-heuristic",
@@ -2627,7 +2627,7 @@
   {
     "name": "enable-log-controller-for-diagnostics-app",
     "owners": [ "//ash/webui/diagnostics_ui/OWNERS" ],
-    "expiry_milestone": 110
+    "expiry_milestone": 120
   },
   {
     "name": "enable-logging-js-console-messages",
@@ -3189,7 +3189,7 @@
   {
     "name": "enable-touchpads-in-diagnostics-app",
     "owners": [ "//ash/webui/diagnostics_ui/OWNERS" ],
-    "expiry_milestone": 110
+    "expiry_milestone": 120
   },
   {
     "name": "enable-touchscreen-calibration",
@@ -3201,7 +3201,7 @@
   {
     "name": "enable-touchscreens-in-diagnostics-app",
     "owners": [ "//ash/webui/diagnostics_ui/OWNERS" ],
-    "expiry_milestone": 110
+    "expiry_milestone": 120
   },
   {
     "name": "enable-translate-sub-frames",
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 8872845..64d529d 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -289,10 +289,6 @@
     "grouped into processes based on their URL's site or origin. The default "
     "grouping when enabled is per-site.";
 
-const char kAssistantConsentModalName[] = "AssistantConsentModal";
-const char kAssistantConsentModalDescription[] =
-    "Enables the modal version of the Assistant voice search consent dialog.";
-
 const char kAssistantConsentSimplifiedTextName[] =
     "AssistantConsentSimplifiedText";
 const char kAssistantConsentSimplifiedTextDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 752a9f4c..8acf4ea 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -168,9 +168,6 @@
 extern const char kUseCustomMessagesDomainName[];
 extern const char kUseCustomMessagesDomainDescription[];
 
-extern const char kAssistantConsentModalName[];
-extern const char kAssistantConsentModalDescription[];
-
 extern const char kAssistantConsentSimplifiedTextName[];
 extern const char kAssistantConsentSimplifiedTextDescription[];
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index 1a39009..d704d449 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -174,7 +174,6 @@
     &kAllowNewIncognitoTabIntents,
     &kAndroidScrollOptimizations,
     &kAndroidSearchEngineChoiceNotification,
-    &kAssistantConsentModal,
     &kAssistantConsentSimplifiedText,
     &kAssistantConsentV2,
     &kAssistantIntentExperimentId,
@@ -476,10 +475,6 @@
              "AndroidSearchEngineChoiceNotification",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-BASE_FEATURE(kAssistantConsentModal,
-             "AssistantConsentModal",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 BASE_FEATURE(kAssistantConsentSimplifiedText,
              "AssistantConsentSimplifiedText",
              base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h
index c58a6f0..8e91bda3 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -20,7 +20,6 @@
 BASE_DECLARE_FEATURE(kAllowNewIncognitoTabIntents);
 BASE_DECLARE_FEATURE(kAndroidScrollOptimizations);
 BASE_DECLARE_FEATURE(kAndroidSearchEngineChoiceNotification);
-BASE_DECLARE_FEATURE(kAssistantConsentModal);
 BASE_DECLARE_FEATURE(kAssistantConsentSimplifiedText);
 BASE_DECLARE_FEATURE(kAssistantConsentV2);
 BASE_DECLARE_FEATURE(kAssistantIntentExperimentId);
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index fbfe38a..c4ab08ed 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -195,7 +195,6 @@
     public static final String ANONYMOUS_UPDATE_CHECKS = "AnonymousUpdateChecks";
     public static final String APP_LANGUAGE_PROMPT = "AppLanguagePrompt";
     public static final String APP_LANGUAGE_PROMPT_ULP = "AppLanguagePromptULP";
-    public static final String ASSISTANT_CONSENT_MODAL = "AssistantConsentModal";
     public static final String ASSISTANT_CONSENT_SIMPLIFIED_TEXT = "AssistantConsentSimplifiedText";
     public static final String ASSISTANT_CONSENT_V2 = "AssistantConsentV2";
     public static final String ASSISTANT_INTENT_EXPERIMENT_ID = "AssistantIntentExperimentId";
@@ -660,7 +659,7 @@
     public static final CachedFlag sInstantStart = new CachedFlag(INSTANT_START, false);
     public static final CachedFlag sInterestFeedV2 = new CachedFlag(INTEREST_FEED_V2, true);
     public static final CachedFlag sLensCameraAssistedSearch =
-            new CachedFlag(LENS_CAMERA_ASSISTED_SEARCH, false);
+            new CachedFlag(LENS_CAMERA_ASSISTED_SEARCH, true);
     public static final CachedFlag sNewWindowAppMenu = new CachedFlag(NEW_WINDOW_APP_MENU, true);
     public static final CachedFlag sOmahaMinSdkVersionAndroid =
             new CachedFlag(OMAHA_MIN_SDK_VERSION_ANDROID, false);
diff --git a/chrome/browser/lacros/chrome_browser_main_extra_parts_lacros.cc b/chrome/browser/lacros/chrome_browser_main_extra_parts_lacros.cc
index 71eab4c..be3e1fd 100644
--- a/chrome/browser/lacros/chrome_browser_main_extra_parts_lacros.cc
+++ b/chrome/browser/lacros/chrome_browser_main_extra_parts_lacros.cc
@@ -34,6 +34,7 @@
 #include "chrome/browser/lacros/sync/sync_crosapi_manager_lacros.h"
 #include "chrome/browser/lacros/task_manager_lacros.h"
 #include "chrome/browser/lacros/ui_metric_recorder_lacros.h"
+#include "chrome/browser/lacros/views_text_services_context_menu_lacros.h"
 #include "chrome/browser/lacros/vpn_extension_tracker_lacros.h"
 #include "chrome/browser/lacros/web_app_provider_bridge_lacros.h"
 #include "chrome/browser/lacros/web_page_info_lacros.h"
@@ -49,6 +50,7 @@
 #include "chromeos/startup/browser_params_proxy.h"
 #include "components/arc/common/intent_helper/arc_icon_cache_delegate.h"
 #include "extensions/common/features/feature_session_type.h"
+#include "ui/views/controls/views_text_services_context_menu_chromeos.h"
 
 namespace {
 
@@ -231,4 +233,13 @@
     chrome_kiosk_launch_controller_ =
         std::make_unique<ChromeKioskLaunchControllerLacros>(*profile);
   }
+
+  views::ViewsTextServicesContextMenuChromeos::SetImplFactory(
+      base::BindRepeating(
+          [](ui::SimpleMenuModel* menu_model, views::Textfield* textfield)
+              -> std::unique_ptr<views::ViewsTextServicesContextMenu> {
+            return std::make_unique<
+                crosapi::ViewsTextServicesContextMenuLacros>(menu_model,
+                                                             textfield);
+          }));
 }
diff --git a/chrome/browser/lacros/standalone_browser_test_controller.cc b/chrome/browser/lacros/standalone_browser_test_controller.cc
index e848e1bc..313f529 100644
--- a/chrome/browser/lacros/standalone_browser_test_controller.cc
+++ b/chrome/browser/lacros/standalone_browser_test_controller.cc
@@ -131,8 +131,7 @@
   std::string error;
   auto extension = extensions::Extension::Create(
       base::FilePath(), extensions::mojom::ManifestLocation::kUnpacked,
-      base::Value::AsDictionaryValue(
-          base::Value(CreateVpnExtensionManifest(extension_name))),
+      CreateVpnExtensionManifest(extension_name),
       extensions::Extension::NO_FLAGS, &error);
   if (!error.empty()) {
     std::move(callback).Run(error);
diff --git a/chrome/browser/lacros/views_text_services_context_menu_lacros.cc b/chrome/browser/lacros/views_text_services_context_menu_lacros.cc
new file mode 100644
index 0000000..6a4f95ce
--- /dev/null
+++ b/chrome/browser/lacros/views_text_services_context_menu_lacros.cc
@@ -0,0 +1,123 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/lacros/views_text_services_context_menu_lacros.h"
+
+#include "chrome/app/chrome_command_ids.h"
+#include "chromeos/crosapi/mojom/clipboard_history.mojom.h"
+#include "chromeos/lacros/lacros_service.h"
+#include "ui/base/clipboard/clipboard.h"
+#include "ui/base/data_transfer_policy/data_transfer_endpoint.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/pointer/touch_editing_controller.h"
+#include "ui/strings/grit/ui_strings.h"
+#include "ui/views/controls/textfield/textfield.h"
+#include "ui/views/controls/views_text_services_context_menu_base.h"
+
+namespace {
+
+bool IsClipboardHistoryLacrosServiceAvailable() {
+  auto* const service = chromeos::LacrosService::Get();
+  return service && service->IsAvailable<crosapi::mojom::ClipboardHistory>();
+}
+
+bool IsClipboardHistoryEmpty() {
+  ui::DataTransferEndpoint dte(ui::EndpointType::kClipboardHistory);
+  std::vector<std::u16string> types;
+  ui::Clipboard::GetForCurrentThread()->ReadAvailableTypes(
+      ui::ClipboardBuffer::kCopyPaste, &dte, &types);
+  return types.empty();
+}
+
+}  // namespace
+
+namespace crosapi {
+
+ViewsTextServicesContextMenuLacros::ViewsTextServicesContextMenuLacros(
+    ui::SimpleMenuModel* menu,
+    views::Textfield* client)
+    : views::ViewsTextServicesContextMenuBase(menu, client) {
+  if (!IsClipboardHistoryLacrosServiceAvailable())
+    return;
+
+  // If the menu has a paste option, add a clipboard history option as well.
+  const absl::optional<size_t> paste_index =
+      menu->GetIndexOfCommandId(ui::TouchEditable::kPaste);
+
+  if (!paste_index.has_value())
+    return;
+
+  const size_t target_index = paste_index.value() + 1;
+  menu->InsertItemAt(target_index, IDS_APP_SHOW_CLIPBOARD_HISTORY,
+                     l10n_util::GetStringUTF16(IDS_APP_SHOW_CLIPBOARD_HISTORY));
+}
+
+ViewsTextServicesContextMenuLacros::~ViewsTextServicesContextMenuLacros() =
+    default;
+
+bool ViewsTextServicesContextMenuLacros::GetAcceleratorForCommandId(
+    int command_id,
+    ui::Accelerator* accelerator) const {
+  if (command_id == IDS_APP_SHOW_CLIPBOARD_HISTORY) {
+    *accelerator = ui::Accelerator(ui::VKEY_V, ui::EF_COMMAND_DOWN);
+    return true;
+  }
+
+  return ViewsTextServicesContextMenuBase::GetAcceleratorForCommandId(
+      command_id, accelerator);
+}
+
+bool ViewsTextServicesContextMenuLacros::IsCommandIdChecked(
+    int command_id) const {
+  if (command_id == IDS_APP_SHOW_CLIPBOARD_HISTORY)
+    return true;
+
+  return ViewsTextServicesContextMenuBase::IsCommandIdChecked(command_id);
+}
+
+bool ViewsTextServicesContextMenuLacros::IsCommandIdEnabled(
+    int command_id) const {
+  if (command_id == IDS_APP_SHOW_CLIPBOARD_HISTORY) {
+    return IsClipboardHistoryLacrosServiceAvailable() &&
+           !IsClipboardHistoryEmpty();
+  }
+
+  return ViewsTextServicesContextMenuBase::IsCommandIdEnabled(command_id);
+}
+
+void ViewsTextServicesContextMenuLacros::ExecuteCommand(int command_id,
+                                                        int event_flags) {
+  if (command_id == IDS_APP_SHOW_CLIPBOARD_HISTORY) {
+    if (!IsClipboardHistoryLacrosServiceAvailable())
+      return;
+
+    // Calculate the menu source type from `event_flags`.
+    ui::MenuSourceType source_type;
+    if (event_flags & ui::EF_LEFT_MOUSE_BUTTON) {
+      source_type = ui::MENU_SOURCE_MOUSE;
+    } else if (event_flags & ui::EF_FROM_TOUCH) {
+      source_type = ui::MENU_SOURCE_TOUCH;
+    } else {
+      source_type = ui::MENU_SOURCE_KEYBOARD;
+    }
+
+    chromeos::LacrosService::Get()
+        ->GetRemote<mojom::ClipboardHistory>()
+        ->ShowClipboard(
+            client()->GetCaretBounds(), source_type,
+            mojom::ClipboardHistoryControllerShowSource::kTextfieldContextMenu);
+    return;
+  }
+
+  ViewsTextServicesContextMenuBase::ExecuteCommand(command_id, event_flags);
+}
+
+bool ViewsTextServicesContextMenuLacros::SupportsCommand(int command_id) const {
+  if (command_id == IDS_APP_SHOW_CLIPBOARD_HISTORY)
+    return true;
+
+  return ViewsTextServicesContextMenuBase::SupportsCommand(command_id);
+}
+
+}  // namespace crosapi
diff --git a/chrome/browser/lacros/views_text_services_context_menu_lacros.h b/chrome/browser/lacros/views_text_services_context_menu_lacros.h
new file mode 100644
index 0000000..4342bc6b
--- /dev/null
+++ b/chrome/browser/lacros/views_text_services_context_menu_lacros.h
@@ -0,0 +1,45 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_LACROS_VIEWS_TEXT_SERVICES_CONTEXT_MENU_LACROS_H_
+#define CHROME_BROWSER_LACROS_VIEWS_TEXT_SERVICES_CONTEXT_MENU_LACROS_H_
+
+#include "ui/views/controls/views_text_services_context_menu_base.h"
+
+namespace ui {
+class SimpleMenuModel;
+}  // namespace ui
+
+namespace views {
+class Textfield;
+}  // namespace views
+
+namespace crosapi {
+
+// This class implements support for adding and handling text service items in
+// lacros-chrome browser native textfields (i.e., the omnibox but not the WebUI
+// embedded in the browser).
+class ViewsTextServicesContextMenuLacros
+    : public views::ViewsTextServicesContextMenuBase {
+ public:
+  ViewsTextServicesContextMenuLacros(ui::SimpleMenuModel* menu,
+                                     views::Textfield* client);
+  ViewsTextServicesContextMenuLacros(
+      const ViewsTextServicesContextMenuLacros&) = delete;
+  ViewsTextServicesContextMenuLacros& operator=(
+      const ViewsTextServicesContextMenuLacros&) = delete;
+  ~ViewsTextServicesContextMenuLacros() override;
+
+  // ViewsTextServicesContextMenuBase:
+  bool GetAcceleratorForCommandId(int command_id,
+                                  ui::Accelerator* accelerator) const override;
+  bool IsCommandIdChecked(int command_id) const override;
+  bool IsCommandIdEnabled(int command_id) const override;
+  void ExecuteCommand(int command_id, int event_flags) override;
+  bool SupportsCommand(int command_id) const override;
+};
+
+}  // namespace crosapi
+
+#endif  // CHROME_BROWSER_LACROS_VIEWS_TEXT_SERVICES_CONTEXT_MENU_LACROS_H_
diff --git a/chrome/browser/media/router/mojo/media_router_mojo_impl.cc b/chrome/browser/media/router/mojo/media_router_mojo_impl.cc
index 8779cdb..e4fd52c 100644
--- a/chrome/browser/media/router/mojo/media_router_mojo_impl.cc
+++ b/chrome/browser/media/router/mojo/media_router_mojo_impl.cc
@@ -106,8 +106,8 @@
 
 void MediaRouterMojoImpl::Initialize() {
   DCHECK(!internal_routes_observer_);
-  // The observer calls virtual methods on MediaRouter; it must be created
-  // outside of the ctor
+  // Because observer calls virtual methods on MediaRouter, it must be created
+  // outside of the constructor.
   internal_routes_observer_ =
       std::make_unique<InternalMediaRoutesObserver>(this);
 }
@@ -122,7 +122,7 @@
       std::move(media_route_provider_remote));
   bound_remote.set_disconnect_handler(
       base::BindOnce(&MediaRouterMojoImpl::OnProviderConnectionError,
-                     weak_factory_.GetWeakPtr(), provider_id));
+                     AsWeakPtr(), provider_id));
   media_route_providers_[provider_id] = std::move(bound_remote);
 }
 
@@ -250,9 +250,9 @@
   const mojom::MediaRouteProviderId provider_id = sink->provider_id();
 
   const std::string presentation_id = MediaRouterBase::CreatePresentationId();
-  auto mr_callback = base::BindOnce(
-      &MediaRouterMojoImpl::RouteResponseReceived, weak_factory_.GetWeakPtr(),
-      presentation_id, provider_id, off_the_record, std::move(callback), false);
+  auto mr_callback = base::BindOnce(&MediaRouterMojoImpl::RouteResponseReceived,
+                                    AsWeakPtr(), presentation_id, provider_id,
+                                    off_the_record, std::move(callback), false);
 
   if (source.IsDesktopMirroringSource()) {
     desktop_picker_.Show(
@@ -260,9 +260,9 @@
         {DesktopMediaList::Type::kScreen},
         base::BindRepeating([](content::WebContents* wc) { return true; }),
         base::BindOnce(&MediaRouterMojoImpl::CreateRouteWithSelectedDesktop,
-                       weak_factory_.GetWeakPtr(), provider_id, sink_id,
-                       presentation_id, origin, web_contents, timeout,
-                       off_the_record, std::move(mr_callback)));
+                       AsWeakPtr(), provider_id, sink_id, presentation_id,
+                       origin, web_contents, timeout, off_the_record,
+                       std::move(mr_callback)));
   } else {
     const int frame_tree_node_id =
         web_contents ? web_contents->GetPrimaryMainFrame()->GetFrameTreeNodeId()
@@ -296,9 +296,9 @@
   const int frame_tree_node_id =
       web_contents ? web_contents->GetPrimaryMainFrame()->GetFrameTreeNodeId()
                    : kDefaultFrameTreeNodeId;
-  auto mr_callback = base::BindOnce(
-      &MediaRouterMojoImpl::RouteResponseReceived, weak_factory_.GetWeakPtr(),
-      presentation_id, *provider_id, off_the_record, std::move(callback), true);
+  auto mr_callback = base::BindOnce(&MediaRouterMojoImpl::RouteResponseReceived,
+                                    AsWeakPtr(), presentation_id, *provider_id,
+                                    off_the_record, std::move(callback), true);
   media_route_providers_[*provider_id]->JoinRoute(
       source_id, presentation_id, origin, frame_tree_node_id, timeout,
       off_the_record, std::move(mr_callback));
@@ -313,9 +313,8 @@
         mojom::RouteRequestResultCode::ROUTE_NOT_FOUND);
     return;
   }
-  auto callback =
-      base::BindOnce(&MediaRouterMojoImpl::OnTerminateRouteResult,
-                     weak_factory_.GetWeakPtr(), route_id, *provider_id);
+  auto callback = base::BindOnce(&MediaRouterMojoImpl::OnTerminateRouteResult,
+                                 AsWeakPtr(), route_id, *provider_id);
   media_route_providers_[*provider_id]->TerminateRoute(route_id,
                                                        std::move(callback));
 }
@@ -380,7 +379,7 @@
     return;
   }
   auto callback = base::BindOnce(&MediaRouterMojoImpl::OnMediaControllerCreated,
-                                 weak_factory_.GetWeakPtr(), route_id);
+                                 AsWeakPtr(), route_id);
   media_route_providers_[*provider_id]->CreateMediaRouteController(
       route_id, std::move(controller), std::move(observer),
       std::move(callback));
@@ -562,7 +561,7 @@
 void MediaRouterMojoImpl::RegisterMediaRoutesObserver(
     MediaRoutesObserver* observer) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  bool is_first_observer = !routes_query_.HasObservers();
+  const bool is_first_observer = !routes_query_.HasObservers();
   if (!is_first_observer)
     DCHECK(!routes_query_.HasObserver(observer));
 
@@ -578,19 +577,15 @@
     // MediaRoutesObserver is calling this method from its constructor, and that
     // must complete before invoking its virtual OnRoutesUpdated() method.
     content::GetUIThreadTaskRunner({})->PostTask(
-        FROM_HERE,
-        base::BindOnce(&MediaRouterMojoImpl::NotifyOfExistingRoutesIfRegistered,
-                       weak_factory_.GetWeakPtr(),
-                       // Safe because we check the routes_query_ in the notify
-                       // method before usage.
-                       base::Unretained(observer)));
+        FROM_HERE, base::BindOnce(&MediaRouterMojoImpl::NotifyOfExistingRoutes,
+                                  AsWeakPtr(), observer->AsWeakPtr()));
   }
 }
 
-void MediaRouterMojoImpl::NotifyOfExistingRoutesIfRegistered(
-    MediaRoutesObserver* observer) const {
+void MediaRouterMojoImpl::NotifyOfExistingRoutes(
+    base::WeakPtr<MediaRoutesObserver> observer) const {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  if (routes_query_.HasObserver(observer)) {
+  if (observer) {
     observer->OnRoutesUpdated(
         routes_query_.cached_route_list().value_or(std::vector<MediaRoute>{}));
   }
@@ -598,6 +593,7 @@
 
 void MediaRouterMojoImpl::UnregisterMediaRoutesObserver(
     MediaRoutesObserver* observer) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   routes_query_.RemoveObserver(observer);
 }
 
@@ -694,12 +690,14 @@
 
 void MediaRouterMojoImpl::OnRouteAdded(mojom::MediaRouteProviderId provider_id,
                                        const MediaRoute& route) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   routes_query_.AddRouteForProvider(provider_id, route);
   routes_query_.NotifyObservers();
 }
 
 void MediaRouterMojoImpl::SyncStateToMediaRouteProvider(
     mojom::MediaRouteProviderId provider_id) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   const auto& provider = media_route_providers_[provider_id];
   // Sink queries.
   for (const auto& it : sinks_queries_) {
@@ -799,6 +797,7 @@
 
 absl::optional<mojom::MediaRouteProviderId>
 MediaRouterMojoImpl::GetProviderIdForRoute(const MediaRoute::Id& route_id) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   for (const auto& provider_to_routes : routes_query_.providers_to_routes()) {
     const mojom::MediaRouteProviderId provider_id = provider_to_routes.first;
     const std::vector<MediaRoute>& routes = provider_to_routes.second;
@@ -820,6 +819,7 @@
 absl::optional<mojom::MediaRouteProviderId>
 MediaRouterMojoImpl::GetProviderIdForPresentation(
     const std::string& presentation_id) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   for (const auto& provider_to_routes : routes_query_.providers_to_routes()) {
     const mojom::MediaRouteProviderId provider_id = provider_to_routes.first;
     const std::vector<MediaRoute>& routes = provider_to_routes.second;
@@ -972,8 +972,7 @@
             std::move(inner_callback)
                 .Run(route, std::move(connection), error_text, result_code);
           },
-          std::move(mr_callback), weak_factory_.GetWeakPtr(),
-          request.stream_id));
+          std::move(mr_callback), AsWeakPtr(), request.stream_id));
 }
 
 }  // namespace media_router
diff --git a/chrome/browser/media/router/mojo/media_router_mojo_impl.h b/chrome/browser/media/router/mojo/media_router_mojo_impl.h
index b9375b6c..b2b2b16 100644
--- a/chrome/browser/media/router/mojo/media_router_mojo_impl.h
+++ b/chrome/browser/media/router/mojo/media_router_mojo_impl.h
@@ -49,7 +49,9 @@
 enum class MediaRouteProviderWakeReason;
 
 // MediaRouter implementation that delegates calls to a MediaRouteProvider.
-class MediaRouterMojoImpl : public MediaRouterBase, public mojom::MediaRouter {
+class MediaRouterMojoImpl : public MediaRouterBase,
+                            public mojom::MediaRouter,
+                            public base::SupportsWeakPtr<MediaRouterMojoImpl> {
  public:
   MediaRouterMojoImpl(const MediaRouterMojoImpl&) = delete;
   MediaRouterMojoImpl& operator=(const MediaRouterMojoImpl&) = delete;
@@ -215,7 +217,7 @@
   // Represents a query to the MediaRouteProviders for media routes and caches
   // media routes returned by MRPs. Holds observers for the query.
   //
-  // NOTE: If the to-do below for providers_for_routes_ is fixed, then this
+  // NOTE: If the to-do below for providers_to_routes_ is fixed, then this
   // entire class can be replaced with a std::vector<MediaRoute> and a
   // base::ObserverList of observers.
   class MediaRoutesQuery {
@@ -264,7 +266,7 @@
     base::flat_map<mojom::MediaRouteProviderId, std::vector<MediaRoute>>
         providers_to_routes_;
 
-    base::ObserverList<MediaRoutesObserver>::Unchecked observers_;
+    base::ObserverList<MediaRoutesObserver> observers_;
   };
 
   // See note in OnDesktopPickerDone().
@@ -308,7 +310,8 @@
 
   // Notifies |observer| of any existing cached routes, if it is still
   // registered.
-  void NotifyOfExistingRoutesIfRegistered(MediaRoutesObserver* observer) const;
+  void NotifyOfExistingRoutes(
+      base::WeakPtr<MediaRoutesObserver> observer) const;
 
   // mojom::MediaRouter implementation.
   void OnIssue(const IssueInfo& issue) override;
@@ -424,7 +427,7 @@
       sinks_queries_;
 
   // Holds observers for media route updates and a map of providers to route
-  // ids..
+  // ids.
   MediaRoutesQuery routes_query_;
 
   using PresentationConnectionMessageObserverList =
@@ -445,8 +448,6 @@
   // Collects logs from the Media Router and the native Media Route Providers.
   // TODO(crbug.com/1077138): Limit logging before Media Router usage.
   LoggerImpl logger_;
-
-  base::WeakPtrFactory<MediaRouterMojoImpl> weak_factory_{this};
 };
 
 }  // namespace media_router
diff --git a/chrome/browser/media/router/mojo/media_router_mojo_impl_unittest.cc b/chrome/browser/media/router/mojo/media_router_mojo_impl_unittest.cc
index 2a71c01b..8464b78 100644
--- a/chrome/browser/media/router/mojo/media_router_mojo_impl_unittest.cc
+++ b/chrome/browser/media/router/mojo/media_router_mojo_impl_unittest.cc
@@ -36,7 +36,6 @@
 #include "components/media_router/common/media_source.h"
 #include "components/media_router/common/test/test_helper.h"
 #include "components/version_info/version_info.h"
-#include "content/public/test/browser_task_environment.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -619,33 +618,38 @@
 TEST_F(MediaRouterMojoImplTest, RegisterAndUnregisterMediaRoutesObserver) {
   MockMediaRouter mock_router;
 
-  MediaRoutesObserver* observer_captured;
-  EXPECT_CALL(mock_router, RegisterMediaRoutesObserver(_))
-      .Times(3)
-      .WillRepeatedly(SaveArg<0>(&observer_captured));
-  MockMediaRoutesObserver routes_observer(&mock_router);
-  EXPECT_EQ(observer_captured, &routes_observer);
+  auto routes_observer =
+      std::make_unique<MockMediaRoutesObserver>(&mock_router);
+  EXPECT_TRUE(
+      mock_router.routes_observers().HasObserver(routes_observer.get()));
 
-  MockMediaRoutesObserver extra_routes_observer(&mock_router);
-  EXPECT_EQ(observer_captured, &extra_routes_observer);
+  auto extra_routes_observer =
+      std::make_unique<MockMediaRoutesObserver>(&mock_router);
+  EXPECT_TRUE(
+      mock_router.routes_observers().HasObserver(extra_routes_observer.get()));
 
-  MockMediaRoutesObserver different_routes_observer(&mock_router);
-  EXPECT_EQ(observer_captured, &different_routes_observer);
+  routes_observer.reset();
+  extra_routes_observer.reset();
+  EXPECT_TRUE(mock_router.routes_observers().empty());
+}
 
-  EXPECT_CALL(mock_router, UnregisterMediaRoutesObserver(&routes_observer));
-  EXPECT_CALL(mock_router,
-              UnregisterMediaRoutesObserver(&extra_routes_observer));
-  EXPECT_CALL(mock_router,
-              UnregisterMediaRoutesObserver(&different_routes_observer));
-  UnregisterMediaRoutesObserver(&routes_observer);
-  UnregisterMediaRoutesObserver(&extra_routes_observer);
-  UnregisterMediaRoutesObserver(&different_routes_observer);
+TEST_F(MediaRouterMojoImplTest, UnregisterBeforeNotificationDoesntCrash) {
+  auto routes_observer = std::make_unique<MediaRoutesObserver>(router());
+  auto routes_observer_two = std::make_unique<MediaRoutesObserver>(router());
+
+  // Resetting the observer immediately should cause it to be invalidated before
+  // the callback for NotifyOfExistingRoutesIfRegistered is called.
+  routes_observer_two.reset();
+
+  // Make sure the Notify task executes.
+  base::RunLoop().RunUntilIdle();
 }
 
 TEST_F(MediaRouterMojoImplTest, RegisteredObserversGetMediaRouteUpdates) {
-  StrictMock<MockMediaRoutesObserver> routes_observer(router());
-  StrictMock<MockMediaRoutesObserver> extra_routes_observer(router());
-  StrictMock<MockMediaRoutesObserver> different_routes_observer(router());
+  auto routes_observer =
+      std::make_unique<StrictMock<MockMediaRoutesObserver>>(router());
+  auto extra_routes_observer =
+      std::make_unique<StrictMock<MockMediaRoutesObserver>>(router());
 
   MediaSource media_source(kSource);
   std::vector<MediaRoute> expected_routes{
@@ -656,16 +660,14 @@
   incognito_expected_route.set_off_the_record(true);
   expected_routes.push_back(incognito_expected_route);
 
-  EXPECT_CALL(routes_observer, OnRoutesUpdated(expected_routes)).Times(1);
-  EXPECT_CALL(extra_routes_observer, OnRoutesUpdated(expected_routes)).Times(1);
-  EXPECT_CALL(different_routes_observer, OnRoutesUpdated(expected_routes))
+  EXPECT_CALL(*routes_observer, OnRoutesUpdated(expected_routes)).Times(1);
+  EXPECT_CALL(*extra_routes_observer, OnRoutesUpdated(expected_routes))
       .Times(1);
 
   UpdateRoutes(mojom::MediaRouteProviderId::CAST, expected_routes);
 
-  UnregisterMediaRoutesObserver(&routes_observer);
-  UnregisterMediaRoutesObserver(&extra_routes_observer);
-  UnregisterMediaRoutesObserver(&different_routes_observer);
+  routes_observer.reset();
+  extra_routes_observer.reset();
 
   // No route observers should be notified.
   UpdateRoutes(mojom::MediaRouteProviderId::CAST, expected_routes);
diff --git a/chrome/browser/media/router/test/media_router_mojo_test.cc b/chrome/browser/media/router/test/media_router_mojo_test.cc
index 914a264..230c92b 100644
--- a/chrome/browser/media/router/test/media_router_mojo_test.cc
+++ b/chrome/browser/media/router/test/media_router_mojo_test.cc
@@ -141,8 +141,9 @@
 void MediaRouterMojoTest::ProvideTestRoute(
     mojom::MediaRouteProviderId provider_id,
     const MediaRoute::Id& route_id) {
-  if (!routes_observer_)
+  if (!routes_observer_) {
     routes_observer_ = std::make_unique<MediaRoutesObserver>(router());
+  }
   MediaRoute route = CreateMediaRoute();
   route.set_media_route_id(route_id);
   router()->OnRoutesUpdated(provider_id, {route});
diff --git a/chrome/browser/metrics/structured/BUILD.gn b/chrome/browser/metrics/structured/BUILD.gn
index 6e9c44e0..df9cd4f 100644
--- a/chrome/browser/metrics/structured/BUILD.gn
+++ b/chrome/browser/metrics/structured/BUILD.gn
@@ -28,9 +28,14 @@
     sources += [
       "ash_structured_metrics_recorder.cc",
       "ash_structured_metrics_recorder.h",
+      "structured_metrics_user_session_observer.cc",
+      "structured_metrics_user_session_observer.h",
     ]
 
-    deps += [ "//chrome/browser/ash/crosapi" ]
+    deps += [
+      "//chrome/browser/ash/crosapi",
+      "//components/user_manager:user_manager",
+    ]
   }
 
   if (is_chromeos_lacros) {
diff --git a/chrome/browser/metrics/structured/ash_structured_metrics_recorder.cc b/chrome/browser/metrics/structured/ash_structured_metrics_recorder.cc
index b8fe8aa2..e2e6745a 100644
--- a/chrome/browser/metrics/structured/ash_structured_metrics_recorder.cc
+++ b/chrome/browser/metrics/structured/ash_structured_metrics_recorder.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/metrics/structured/ash_structured_metrics_recorder.h"
+#include <memory>
 
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "chrome/browser/ash/crosapi/crosapi_ash.h"
@@ -11,6 +12,7 @@
 #include "components/metrics/structured/event.h"
 #include "components/metrics/structured/histogram_util.h"
 #include "components/metrics/structured/recorder.h"
+#include "components/metrics/structured/structured_metrics_features.h"
 
 namespace metrics {
 namespace structured {
@@ -22,13 +24,21 @@
   DCHECK(!is_initialized_);
 
   // If already initialized, do nothing.
-  if (is_initialized_)
+  if (is_initialized_) {
     return;
+  }
 
   // Crosapi may not be initialized, in which case a pipe cannot be setup.
   if (crosapi::CrosapiManager::IsInitialized()) {
     crosapi::CrosapiManager::Get()->crosapi_ash()->BindStructuredMetricsService(
         remote_.BindNewPipeAndPassReceiver());
+
+    if (base::FeatureList::IsEnabled(kEventSequenceLogging)) {
+      auto* user_manager = user_manager::UserManager::Get();
+      DCHECK(user_manager);
+      user_session_observer_ =
+          std::make_unique<StructuredMetricsUserSessionObserver>(user_manager);
+    }
     is_initialized_ = true;
   } else {
     VLOG(2) << "Initialize() called before CrosApi is initialized.";
diff --git a/chrome/browser/metrics/structured/ash_structured_metrics_recorder.h b/chrome/browser/metrics/structured/ash_structured_metrics_recorder.h
index 411bcca..360c633 100644
--- a/chrome/browser/metrics/structured/ash_structured_metrics_recorder.h
+++ b/chrome/browser/metrics/structured/ash_structured_metrics_recorder.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_METRICS_STRUCTURED_ASH_STRUCTURED_METRICS_RECORDER_H_
 #define CHROME_BROWSER_METRICS_STRUCTURED_ASH_STRUCTURED_METRICS_RECORDER_H_
 
+#include "chrome/browser/metrics/structured/structured_metrics_user_session_observer.h"
 #include "chromeos/crosapi/mojom/structured_metrics_service.mojom.h"
 #include "components/metrics/structured/event.h"
 #include "components/metrics/structured/structured_metrics_client.h"
@@ -38,7 +39,7 @@
 
  private:
   mojo::Remote<crosapi::mojom::StructuredMetricsService> remote_;
-
+  std::unique_ptr<StructuredMetricsUserSessionObserver> user_session_observer_;
   bool is_initialized_ = false;
 };
 
diff --git a/chrome/browser/metrics/structured/structured_metrics_user_session_observer.cc b/chrome/browser/metrics/structured/structured_metrics_user_session_observer.cc
new file mode 100644
index 0000000..f76afbdb
--- /dev/null
+++ b/chrome/browser/metrics/structured/structured_metrics_user_session_observer.cc
@@ -0,0 +1,27 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/metrics/structured/structured_metrics_user_session_observer.h"
+#include "components/metrics/structured/structured_events.h"
+
+namespace metrics::structured {
+
+StructuredMetricsUserSessionObserver::StructuredMetricsUserSessionObserver(
+    user_manager::UserManager* user_manager)
+    : user_manager_(user_manager) {
+  user_manager_->AddSessionStateObserver(this);
+}
+
+StructuredMetricsUserSessionObserver::~StructuredMetricsUserSessionObserver() {
+  user_manager_->RemoveSessionStateObserver(this);
+}
+
+void StructuredMetricsUserSessionObserver::ActiveUserChanged(
+    user_manager::User* active_user) {
+  if (active_user->is_active()) {
+    events::v2::cr_os_events::UserLogin().Record();
+  }
+}
+
+}  // namespace metrics::structured
diff --git a/chrome/browser/metrics/structured/structured_metrics_user_session_observer.h b/chrome/browser/metrics/structured/structured_metrics_user_session_observer.h
new file mode 100644
index 0000000..f68abcc
--- /dev/null
+++ b/chrome/browser/metrics/structured/structured_metrics_user_session_observer.h
@@ -0,0 +1,31 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_METRICS_STRUCTURED_STRUCTURED_METRICS_USER_SESSION_OBSERVER_H_
+#define CHROME_BROWSER_METRICS_STRUCTURED_STRUCTURED_METRICS_USER_SESSION_OBSERVER_H_
+
+#include "components/user_manager/user_manager.h"
+
+namespace metrics::structured {
+
+// An UserSession observer to detect a login for an event to be recorded
+// when such a user action takes place.
+class StructuredMetricsUserSessionObserver
+    : public user_manager::UserManager::UserSessionStateObserver {
+ public:
+  explicit StructuredMetricsUserSessionObserver(
+      user_manager::UserManager* user_manager);
+
+  ~StructuredMetricsUserSessionObserver() override;
+
+  // user_manager::UserSessionStateObserver:
+  void ActiveUserChanged(user_manager::User* active_user) override;
+
+ private:
+  user_manager::UserManager* user_manager_;
+};
+
+}  // namespace metrics::structured
+
+#endif  // CHROME_BROWSER_METRICS_STRUCTURED_STRUCTURED_METRICS_USER_SESSION_OBSERVER_H_
diff --git a/chrome/browser/policy/configuration_policy_handler_list_factory.cc b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
index 869ba08..046ecfe 100644
--- a/chrome/browser/policy/configuration_policy_handler_list_factory.cc
+++ b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
@@ -20,6 +20,7 @@
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/browsing_data/browsing_data_lifetime_policy_handler.h"
 #include "chrome/browser/enterprise/connectors/device_trust/prefs.h"
+#include "chrome/browser/enterprise/idle/idle_timeout_policy_handler.h"
 #include "chrome/browser/first_party_sets/first_party_sets_overrides_policy_handler.h"
 #include "chrome/browser/net/disk_cache_dir_policy_handler.h"
 #include "chrome/browser/net/explicitly_allowed_network_ports_policy_handler.h"
@@ -161,6 +162,7 @@
 #include "chrome/browser/ash/plugin_vm/plugin_vm_pref_names.h"
 #include "chrome/browser/ash/policy/handlers/configuration_policy_handler_ash.h"
 #include "chrome/browser/ash/policy/handlers/lacros_availability_policy_handler.h"
+#include "chrome/browser/ash/policy/handlers/lacros_selection_policy_handler.h"
 #include "chrome/browser/nearby_sharing/common/nearby_share_prefs.h"
 #include "chrome/browser/policy/default_geolocation_policy_handler.h"
 #include "chrome/browser/policy/os_color_mode_policy_handler.h"
@@ -1826,22 +1828,6 @@
 }
 #endif  // BUILDFLAG(ENABLE_EXTENSIONS)
 
-#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
-void GetIdleTimeoutActionsMap(
-    std::vector<std::unique_ptr<StringMappingListPolicyHandler::MappingEntry>>*
-        result) {
-  // Mapping from IdleTimeoutActions action names to ActionType.
-  for (size_t i = 0; i < enterprise_idle::kActionTypeMapSize; i++) {
-    const enterprise_idle::ActionTypeMapEntry& entry =
-        enterprise_idle::kActionTypeMap[i];
-    result->push_back(
-        std::make_unique<StringMappingListPolicyHandler::MappingEntry>(
-            entry.name, std::make_unique<base::Value>(
-                            static_cast<int>(entry.action_type))));
-  }
-}
-#endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
-
 // Future policies are not supported on Stable and Beta by default.
 bool AreFuturePoliciesSupported() {
   // Enable future policies for branded browser tests.
@@ -2032,11 +2018,11 @@
       SimpleSchemaValidatingPolicyHandler::MANDATORY_ALLOWED));
 
 #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
-  handlers->AddHandler(std::make_unique<IntRangePolicyHandler>(
-      key::kIdleTimeout, prefs::kIdleTimeout, 5, INT_MAX, /*clamp=*/true));
-  handlers->AddHandler(std::make_unique<StringMappingListPolicyHandler>(
-      key::kIdleTimeoutActions, prefs::kIdleTimeoutActions,
-      base::BindRepeating(&GetIdleTimeoutActionsMap)));
+  handlers->AddHandler(
+      std::make_unique<enterprise_idle::IdleTimeoutPolicyHandler>());
+  handlers->AddHandler(
+      std::make_unique<enterprise_idle::IdleTimeoutActionsPolicyHandler>(
+          chrome_schema));
 #endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
 
   handlers->AddHandler(std::make_unique<RestoreOnStartupPolicyHandler>());
@@ -2408,6 +2394,7 @@
   handlers->AddHandler(std::make_unique<BooleanDisablingPolicyHandler>(
       key::kNearbyShareAllowed, prefs::kNearbySharingEnabledPrefName));
   handlers->AddHandler(std::make_unique<LacrosAvailabilityPolicyHandler>());
+  handlers->AddHandler(std::make_unique<LacrosSelectionPolicyHandler>());
   handlers->AddHandler(std::make_unique<BooleanDisablingPolicyHandler>(
       key::kFastPairEnabled, ash::prefs::kFastPairEnabled));
   handlers->AddHandler(std::make_unique<arc::ArcPolicyHandler>());
diff --git a/chrome/browser/profiles/profile_impl.cc b/chrome/browser/profiles/profile_impl.cc
index 0e17f238..a2a12efa 100644
--- a/chrome/browser/profiles/profile_impl.cc
+++ b/chrome/browser/profiles/profile_impl.cc
@@ -657,6 +657,7 @@
         policy::PolicyNamespace(policy::POLICY_DOMAIN_CHROME, std::string()));
     crosapi::browser_util::CacheLacrosAvailability(map);
     crosapi::browser_util::CacheLacrosDataBackwardMigrationMode(map);
+    crosapi::browser_util::CacheLacrosSelection(map);
   }
 #endif
 }
@@ -1183,6 +1184,7 @@
           policy::PolicyNamespace(policy::POLICY_DOMAIN_CHROME, std::string()));
       crosapi::browser_util::CacheLacrosAvailability(map);
       crosapi::browser_util::CacheLacrosDataBackwardMigrationMode(map);
+      crosapi::browser_util::CacheLacrosSelection(map);
     }
 
     ash::UserSessionManager::GetInstance()->RespectLocalePreferenceWrapper(
diff --git a/chrome/browser/reading_list/OWNERS b/chrome/browser/reading_list/OWNERS
index b4a76a2b..adf8b80d 100644
--- a/chrome/browser/reading_list/OWNERS
+++ b/chrome/browser/reading_list/OWNERS
@@ -1,6 +1,6 @@
-dtrainor@chromium.org
-shaktisahu@chromium.org
+wylieb@chromium.org
 
 # Backup reviewers:
 twellington@chromium.org
-wylieb@chromium.org
+dtrainor@chromium.org
+shaktisahu@chromium.org
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output.js
index dcaf21cf..52a083f3 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output.js
@@ -672,38 +672,6 @@
   }
 
   /** @override */
-  formatCellIndexText_(data, token, options) {
-    const buff = data.outputBuffer;
-    const node = data.node;
-    const formatLog = data.outputFormatLogger;
-
-    if (node.htmlAttributes['aria-coltext']) {
-      let value = node.htmlAttributes['aria-coltext'];
-      let row = node;
-      while (row && row.role !== RoleType.ROW) {
-        row = row.parent;
-      }
-      if (!row || !row.htmlAttributes['aria-rowtext']) {
-        return;
-      }
-      value += row.htmlAttributes['aria-rowtext'];
-      this.append_(buff, value, options);
-      formatLog.writeTokenWithValue(token, value);
-    } else {
-      formatLog.write(token);
-      this.format_({
-        node,
-        outputFormat: ` @cell_summary($if($tableCellAriaRowIndex,
-                  $tableCellAriaRowIndex, $tableCellRowIndex),
-                $if($tableCellAriaColumnIndex, $tableCellAriaColumnIndex,
-                  $tableCellColumnIndex))`,
-        outputBuffer: buff,
-        outputFormatLogger: formatLog,
-      });
-    }
-  }
-
-  /** @override */
   formatNode_(data, token, tree, options) {
     const buff = data.outputBuffer;
     const node = data.node;
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_formatter.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_formatter.js
index cfa54678..9031378d 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_formatter.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_formatter.js
@@ -97,7 +97,7 @@
         token === 'tableCellRowIndex' || token === 'tableCellColumnIndex') {
       this.formatTableCellIndex_(this.params_, token, options);
     } else if (token === 'cellIndexText') {
-      this.output_.formatCellIndexText_(this.params_, token, options);
+      this.formatCellIndexText_(this.params_, token, options);
     } else if (token === 'node') {
       this.output_.formatNode_(this.params_, token, tree, options);
     } else if (token === 'nameOrTextContent' || token === 'textContent') {
@@ -184,6 +184,43 @@
   /**
    * @param {!outputTypes.OutputFormattingData} data
    * @param {string} token
+   * @param {!{annotation: Array<*>, isUnique: (boolean|undefined)}} options
+   * @private
+   */
+  formatCellIndexText_(data, token, options) {
+    const buff = data.outputBuffer;
+    const node = data.node;
+    const formatLog = data.outputFormatLogger;
+
+    if (node.htmlAttributes['aria-coltext']) {
+      let value = node.htmlAttributes['aria-coltext'];
+      let row = node;
+      while (row && row.role !== RoleType.ROW) {
+        row = row.parent;
+      }
+      if (!row || !row.htmlAttributes['aria-rowtext']) {
+        return;
+      }
+      value += row.htmlAttributes['aria-rowtext'];
+      this.output_.append_(buff, value, options);
+      formatLog.writeTokenWithValue(token, value);
+    } else {
+      formatLog.write(token);
+      this.output_.format_({
+        node,
+        outputFormat: ` @cell_summary($if($tableCellAriaRowIndex,
+                  $tableCellAriaRowIndex, $tableCellRowIndex),
+                $if($tableCellAriaColumnIndex, $tableCellAriaColumnIndex,
+                  $tableCellColumnIndex))`,
+        outputBuffer: buff,
+        outputFormatLogger: formatLog,
+      });
+    }
+  }
+
+  /**
+   * @param {!outputTypes.OutputFormattingData} data
+   * @param {string} token
    * @private
    */
   formatChecked_(data, token) {
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_interface.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_interface.js
index be41ead..7bae0e6 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_interface.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_interface.js
@@ -65,13 +65,6 @@
   /**
    * @param {!outputTypes.OutputFormattingData} data
    * @param {string} token
-   * @param {!{annotation: Array<*>, isUnique: (boolean|undefined)}} options
-   */
-  formatCellIndexText_(data, token, options) {}
-
-  /**
-   * @param {!outputTypes.OutputFormattingData} data
-   * @param {string} token
    * @param {!OutputFormatTree} tree
    * @param {!{annotation: Array<*>, isUnique: (boolean|undefined)}} options
    */
diff --git a/chrome/browser/resources/extensions/service.ts b/chrome/browser/resources/extensions/service.ts
index 9fbe8e71..d6329613 100644
--- a/chrome/browser/resources/extensions/service.ts
+++ b/chrome/browser/resources/extensions/service.ts
@@ -138,11 +138,10 @@
           populateError: true,
         },
         extraOptions);
-
     return chrome.developerPrivate.loadUnpacked(options)
         .then(loadError => {
           if (loadError) {
-            throw new Error(loadError.error);
+            throw loadError;
           }
           // The load was successful if there's no loadError.
           return true;
diff --git a/chrome/browser/resources/management/management_ui.ts b/chrome/browser/resources/management/management_ui.ts
index 767e7135..cc01578e 100644
--- a/chrome/browser/resources/management/management_ui.ts
+++ b/chrome/browser/resources/management/management_ui.ts
@@ -379,8 +379,8 @@
       }
       // </if>
       // <if expr="not chromeos_ash">
-      this.managementNoticeHtml_ =
-          sanitizeInnerHtml(data.browserManagementNotice);
+      this.managementNoticeHtml_ = sanitizeInnerHtml(
+          data.browserManagementNotice, {attrs: ['aria-label']});
       // </if>
     });
   }
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn
index 6ed68ea..acec84f 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/internet_page/BUILD.gn
@@ -20,7 +20,6 @@
     ":internet_known_networks_page",
     ":internet_page",
     ":internet_page_browser_proxy",
-    ":internet_shared_css",
     ":internet_subpage",
     ":network_always_on_vpn",
     ":network_proxy_section",
@@ -60,7 +59,6 @@
 
 js_library("internet_config") {
   deps = [
-    ":internet_shared_css",
     "//ash/webui/common/resources:i18n_behavior",
     "//ash/webui/common/resources:util",
     "//ash/webui/common/resources/network:network_config",
@@ -99,7 +97,6 @@
 js_library("internet_detail_page") {
   deps = [
     ":internet_page_browser_proxy",
-    ":internet_shared_css",
     ":network_proxy_section",
     "//ash/webui/common/resources:assert",
     "//ash/webui/common/resources:i18n_behavior",
@@ -140,7 +137,6 @@
 
 js_library("internet_known_networks_page") {
   deps = [
-    ":internet_shared_css",
     "//ash/webui/common/resources:assert",
     "//ash/webui/common/resources:i18n_behavior",
     "//ash/webui/common/resources/network:cr_policy_network_behavior_mojo",
@@ -195,9 +191,6 @@
 js_library("internet_page_browser_proxy") {
 }
 
-js_library("internet_shared_css") {
-}
-
 js_library("internet_subpage") {
   deps = [
     ":internet_page_browser_proxy",
@@ -233,7 +226,6 @@
 
 js_library("network_always_on_vpn") {
   deps = [
-    ":internet_shared_css",
     "//ash/webui/common/resources:assert",
     "//ash/webui/common/resources:i18n_behavior",
     "//ash/webui/common/resources/network:onc_mojo",
@@ -243,7 +235,6 @@
 
 js_library("network_proxy_section") {
   deps = [
-    ":internet_shared_css",
     "//ash/webui/common/resources:i18n_behavior",
     "//ash/webui/common/resources/network:cr_policy_network_behavior_mojo",
     "//ash/webui/common/resources/network:cr_policy_network_indicator_mojo",
@@ -302,7 +293,6 @@
     "internet_detail_page.js",
     "internet_known_networks_page.js",
     "internet_page.js",
-    "internet_shared_css.js",
     "internet_subpage.js",
     "network_always_on_vpn.js",
     "network_proxy_section.js",
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/apn_subpage.js b/chrome/browser/resources/settings/chromeos/internet_page/apn_subpage.js
index 12e1ad2..947cd72 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/apn_subpage.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/apn_subpage.js
@@ -7,7 +7,7 @@
  * Settings subpage for managing APNs.
  */
 
-import './internet_shared_css.js';
+import './internet_shared.css.js';
 import 'chrome://resources/ash/common/network/apn_list.js';
 
 import {assert} from 'chrome://resources/ash/common/assert.js';
@@ -232,4 +232,4 @@
   }
 }
 
-customElements.define(ApnSubpageElement.is, ApnSubpageElement);
\ No newline at end of file
+customElements.define(ApnSubpageElement.is, ApnSubpageElement);
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/hotspot_subpage.js b/chrome/browser/resources/settings/chromeos/internet_page/hotspot_subpage.js
index 973d495..e01b0a0 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/hotspot_subpage.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/hotspot_subpage.js
@@ -7,7 +7,7 @@
  * Settings subpage for managing Hotspot.
  */
 
-import './internet_shared_css.js';
+import './internet_shared.css.js';
 
 import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
@@ -23,4 +23,4 @@
 }
 
 customElements.define(
-    SettingsHotspotSubpageElement.is, SettingsHotspotSubpageElement);
\ No newline at end of file
+    SettingsHotspotSubpageElement.is, SettingsHotspotSubpageElement);
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/internet_config.js b/chrome/browser/resources/settings/chromeos/internet_page/internet_config.js
index 571376c..23c39dd 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/internet_config.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/internet_config.js
@@ -10,11 +10,11 @@
 import 'chrome://resources/cr_elements/cr_button/cr_button.js';
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
 import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
-import './internet_shared_css.js';
+import './internet_shared.css.js';
 
-import {OncMojo} from 'chrome://resources/ash/common/network/onc_mojo.js';
 import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/ash/common/i18n_behavior.js';
 import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js';
+import {OncMojo} from 'chrome://resources/ash/common/network/onc_mojo.js';
 import {HTMLEscape} from 'chrome://resources/ash/common/util.js';
 import {NetworkType} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-webui.js';
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_page.js b/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_page.js
index 5b2d084..93c2fcd 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_page.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/internet_detail_page.js
@@ -30,20 +30,20 @@
 import '../../controls/settings_toggle_button.js';
 import '../../prefs/prefs.js';
 import './cellular_roaming_toggle_button.js';
-import './internet_shared_css.js';
+import './internet_shared.css.js';
 import './network_proxy_section.js';
 import './settings_traffic_counters.js';
 import './tether_connection_dialog.js';
 
+import {assert} from 'chrome://resources/ash/common/assert.js';
 import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/ash/common/i18n_behavior.js';
+import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js';
 import {isActiveSim, processDeviceState} from 'chrome://resources/ash/common/network/cellular_utils.js';
 import {CrPolicyNetworkBehaviorMojo, CrPolicyNetworkBehaviorMojoInterface} from 'chrome://resources/ash/common/network/cr_policy_network_behavior_mojo.js';
 import {MojoInterfaceProvider, MojoInterfaceProviderImpl} from 'chrome://resources/ash/common/network/mojo_interface_provider.js';
 import {NetworkListenerBehavior, NetworkListenerBehaviorInterface} from 'chrome://resources/ash/common/network/network_listener_behavior.js';
 import {OncMojo} from 'chrome://resources/ash/common/network/onc_mojo.js';
 import {WebUIListenerBehavior, WebUIListenerBehaviorInterface} from 'chrome://resources/ash/common/web_ui_listener_behavior.js';
-import {assert} from 'chrome://resources/ash/common/assert.js';
-import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js';
 import {ActivationStateType, ApnProperties, ConfigProperties, CrosNetworkConfigRemote, FilterType, GlobalPolicy, HiddenSsidMode, IPConfigProperties, ManagedProperties, NetworkStateProperties, NO_LIMIT, ProxySettings, SecurityType, VpnType} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/cros_network_config.mojom-webui.js';
 import {ConnectionStateType, DeviceStateType, IPConfigType, NetworkType, OncSource, PolicySource, PortalState} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-webui.js';
 import {afterNextRender, flush, html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/internet_known_networks_page.js b/chrome/browser/resources/settings/chromeos/internet_page/internet_known_networks_page.js
index ad6184d0..51a6510 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/internet_known_networks_page.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/internet_known_networks_page.js
@@ -12,24 +12,24 @@
 import 'chrome://resources/cr_elements/cr_link_row/cr_link_row.js';
 import 'chrome://resources/cr_elements/icons.html.js';
 import '../../settings_shared.css.js';
-import './internet_shared_css.js';
+import './internet_shared.css.js';
 
+import {assert} from 'chrome://resources/ash/common/assert.js';
+import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/ash/common/i18n_behavior.js';
 import {CrPolicyNetworkBehaviorMojo, CrPolicyNetworkBehaviorMojoInterface} from 'chrome://resources/ash/common/network/cr_policy_network_behavior_mojo.js';
 import {MojoInterfaceProvider, MojoInterfaceProviderImpl} from 'chrome://resources/ash/common/network/mojo_interface_provider.js';
 import {NetworkListenerBehavior, NetworkListenerBehaviorInterface} from 'chrome://resources/ash/common/network/network_listener_behavior.js';
 import {OncMojo} from 'chrome://resources/ash/common/network/onc_mojo.js';
-import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/ash/common/i18n_behavior.js';
-import {assert} from 'chrome://resources/ash/common/assert.js';
 import {ConfigProperties, CrosNetworkConfigRemote, FilterType, NO_LIMIT} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/cros_network_config.mojom-webui.js';
 import {NetworkType} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-webui.js';
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
-import {Route} from '../router.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {recordSettingChange} from '../metrics_recorder.js';
 import {routes} from '../os_route.js';
 import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
+import {Route} from '../router.js';
 
 /**
  * @constructor
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/internet_shared.css b/chrome/browser/resources/settings/chromeos/internet_page/internet_shared.css
new file mode 100644
index 0000000..e6948c0
--- /dev/null
+++ b/chrome/browser/resources/settings/chromeos/internet_page/internet_shared.css
@@ -0,0 +1,31 @@
+/* Copyright 2022 The Chromium Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/* #css_wrapper_metadata_start
+ * #type=style
+ * #import=chrome://resources/cr_elements/cr_shared_vars.css.js
+ * #include=settings-shared
+ * #css_wrapper_metadata_end */
+
+network-icon {
+  padding-inline-end: var(--cr-section-padding);
+}
+
+iron-icon.policy {
+  margin-inline-end: var(--cr-controlled-by-spacing);
+}
+
+.indented {
+  margin-inline-start: var(--cr-section-padding);
+}
+
+.stretch {
+  align-items: stretch;
+}
+
+.title {
+  font-size: 107.69%;  /* 14px / 13px */
+  font-weight: 500;
+}
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/internet_shared_css.html b/chrome/browser/resources/settings/chromeos/internet_page/internet_shared_css.html
deleted file mode 100644
index f56ed93..0000000
--- a/chrome/browser/resources/settings/chromeos/internet_page/internet_shared_css.html
+++ /dev/null
@@ -1,25 +0,0 @@
-
-<template>
-  <style include="settings-shared">
-    network-icon {
-      padding-inline-end: var(--cr-section-padding);
-    }
-
-    iron-icon.policy {
-      margin-inline-end: var(--cr-controlled-by-spacing);
-    }
-
-    .indented {
-      margin-inline-start: var(--cr-section-padding);
-    }
-
-    .stretch {
-      align-items: stretch;
-    }
-
-    .title {
-      font-size: 107.69%;  /* 14px / 13px */
-      font-weight: 500;
-    }
-  </style>
-</template>
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/internet_shared_css.js b/chrome/browser/resources/settings/chromeos/internet_page/internet_shared_css.js
deleted file mode 100644
index bbfeab4c..0000000
--- a/chrome/browser/resources/settings/chromeos/internet_page/internet_shared_css.js
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'chrome://resources/cr_elements/cr_shared_vars.css.js';
-import '../../settings_shared.css.js';
-const template = document.createElement('template');
-template.innerHTML = `
-<dom-module id="internet-shared" assetpath="chrome://resources/">{__html_template__}</dom-module>
-`;
-document.body.appendChild(template.content.cloneNode(true));
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/network_proxy_section.js b/chrome/browser/resources/settings/chromeos/internet_page/network_proxy_section.js
index e8d97ce..a9cec8b 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/network_proxy_section.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/network_proxy_section.js
@@ -16,13 +16,13 @@
 import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
 import '../../controls/extension_controlled_indicator.js';
 import '../../settings_vars.css.js';
-import './internet_shared_css.js';
+import './internet_shared.css.js';
 import '../../controls/settings_toggle_button.js';
 import 'chrome://resources/cr_elements/cr_toggle/cr_toggle.js';
 
+import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/ash/common/i18n_behavior.js';
 import {CrPolicyNetworkBehaviorMojo, CrPolicyNetworkBehaviorMojoInterface} from 'chrome://resources/ash/common/network/cr_policy_network_behavior_mojo.js';
 import {OncMojo} from 'chrome://resources/ash/common/network/onc_mojo.js';
-import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/ash/common/i18n_behavior.js';
 import {ManagedProperties, ManagedString} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/cros_network_config.mojom-webui.js';
 import {OncSource} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-webui.js';
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/settings_traffic_counters.ts b/chrome/browser/resources/settings/chromeos/internet_page/settings_traffic_counters.ts
index 7091634..0940206 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/settings_traffic_counters.ts
+++ b/chrome/browser/resources/settings/chromeos/internet_page/settings_traffic_counters.ts
@@ -7,7 +7,7 @@
  * Settings UI.
  */
 
-import './internet_shared_css.js';
+import './internet_shared.css.js';
 import 'chrome://resources/ash/common/network/network_shared.css.js';
 import 'chrome://resources/cr_elements/cr_button/cr_button.js';
 import 'chrome://resources/cr_elements/cr_toggle/cr_toggle.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/text_to_speech_page.ts b/chrome/browser/resources/settings/chromeos/os_a11y_page/text_to_speech_page.ts
index c87d29b..095ec0f 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/text_to_speech_page.ts
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/text_to_speech_page.ts
@@ -18,6 +18,7 @@
 import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Setting} from '../../mojom-webui/setting.mojom-webui.js';
+import {PrefsMixin, PrefsMixinInterface} from '../../prefs/prefs_mixin.js';
 import {DeepLinkingBehavior, DeepLinkingBehaviorInterface} from '../deep_linking_behavior.js';
 import {DevicePageBrowserProxy, DevicePageBrowserProxyImpl} from '../device_page/device_page_browser_proxy.js';
 import {routes} from '../os_route.js';
@@ -32,10 +33,11 @@
         [
           DeepLinkingBehavior,
         ],
-        RouteOriginMixin(WebUiListenerMixin(I18nMixin(PolymerElement)))) as {
+        RouteOriginMixin(
+            PrefsMixin(WebUiListenerMixin(I18nMixin(PolymerElement))))) as {
       new (): PolymerElement & I18nMixinInterface &
-          WebUiListenerMixinInterface & RouteOriginMixinInterface &
-          DeepLinkingBehaviorInterface,
+          WebUiListenerMixinInterface & PrefsMixinInterface &
+          RouteOriginMixinInterface & DeepLinkingBehaviorInterface,
     };
 
 class SettingsTextToSpeechPageElement extends
@@ -51,14 +53,6 @@
   static get properties() {
     return {
       /**
-       * Preferences state.
-       */
-      prefs: {
-        type: Object,
-        notify: true,
-      },
-
-      /**
        * |hasKeyboard_| starts undefined so observer doesn't trigger until it
        * has been populated.
        */
@@ -85,7 +79,6 @@
     };
   }
 
-  prefs: {[key: string]: any};
   private deviceBrowserProxy_: DevicePageBrowserProxy;
   private hasKeyboard_: boolean;
   private route_: Route;
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.gni b/chrome/browser/resources/settings/chromeos/os_settings.gni
index ffd35abc..278f7bd 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.gni
+++ b/chrome/browser/resources/settings/chromeos/os_settings.gni
@@ -204,6 +204,7 @@
 
 # Files that are passed as input to css_to_wrapper().
 css_files = [
+  "chromeos/internet_page/internet_shared.css",
   "chromeos/os_apps_page/app_management_page/app_management_cros_shared_style.css",
   "chromeos/os_apps_page/app_management_page/app_management_cros_shared_vars.css",
   "chromeos/os_languages_page/shared_style.css",
@@ -353,7 +354,6 @@
   "chromeos/internet_page/internet_detail_page.js",
   "chromeos/internet_page/internet_known_networks_page.js",
   "chromeos/internet_page/internet_page.js",
-  "chromeos/internet_page/internet_shared_css.js",
   "chromeos/internet_page/internet_subpage.js",
   "chromeos/internet_page/network_always_on_vpn.js",
   "chromeos/internet_page/network_proxy_section.js",
diff --git a/chrome/browser/resources/side_panel/customize_chrome/color.html b/chrome/browser/resources/side_panel/customize_chrome/color.html
index be49563..bfb971f 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/color.html
+++ b/chrome/browser/resources/side_panel/customize_chrome/color.html
@@ -1,32 +1,51 @@
 <style>
   :host {
+    align-items: center;
     cursor: pointer;
+    display: flex;
+    justify-content: center;
   }
 
   :host,
   svg {
-    border-radius: 25px;
-    display: block;
     height: 50px;
     width: 50px;
   }
 
+  :host([checked]) svg {
+    height: 46px;
+    width: 46px;
+  }
+
+  customize-chrome-check-mark {
+    --customize-chrome-check-mark-wrapper-end: -1px;
+  }
+
+  svg {
+    border-radius: 25px;
+  }
+
   #background {
     fill: var(--customize-chrome-color-background-color);
   }
 
+  :host([checked]) #background {
+    r: 25px;
+  }
+
   #foreground {
     fill: var(--customize-chrome-color-foreground-color);
   }
 </style>
-<!-- TODO(crbug.com/1384229): Allow clicking. -->
-<svg viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg"
-    xmlns:xlink="http://www.w3.org/1999/xlink">
-  <circle id="foreground" cx="25" cy="25" r="25">
-  </circle>
-  <clipPath id="bottomHalf">
-    <rect x="1" y="25" width="48" height="24"></rect>
-  </clipPath>
-  <circle id="background" cx="25" cy="25" r="24"
-      clip-path="url(#bottomHalf)"></circle>
-</svg>
+<customize-chrome-check-mark-wrapper checked="[[checked]]">
+  <svg viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg"
+      xmlns:xlink="http://www.w3.org/1999/xlink">
+    <circle id="foreground" cx="25" cy="25" r="25">
+    </circle>
+    <clipPath id="bottomHalf">
+      <rect x="1" y="25" width="50" height="25"></rect>
+    </clipPath>
+    <circle id="background" cx="25" cy="25" r="24"
+        clip-path="url(#bottomHalf)"></circle>
+  </svg>
+</customize-chrome-check-mark-wrapper>
diff --git a/chrome/browser/resources/side_panel/customize_chrome/color.ts b/chrome/browser/resources/side_panel/customize_chrome/color.ts
index 83312e8..5b3d943 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/color.ts
+++ b/chrome/browser/resources/side_panel/customize_chrome/color.ts
@@ -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 './check_mark_wrapper.js';
+
 import {skColorToRgba} from 'chrome://resources/js/color_utils.js';
 import {SkColor} from 'chrome://resources/mojo/skia/public/mojom/skcolor.mojom-webui.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
@@ -36,11 +38,16 @@
         value: 0,
         observer: 'onColorChange_',
       },
+      checked: {
+        type: Boolean,
+        reflectToAttribute: true,
+      },
     };
   }
 
   public backgroundColor: SkColor;
   public foregroundColor: SkColor;
+  public checked: boolean;
 
   private onColorChange_() {
     this.updateStyles({
diff --git a/chrome/browser/resources/side_panel/customize_chrome/colors.html b/chrome/browser/resources/side_panel/customize_chrome/colors.html
index 6138b869..f88e9dc9 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/colors.html
+++ b/chrome/browser/resources/side_panel/customize_chrome/colors.html
@@ -5,33 +5,84 @@
     justify-content: center;
   }
 
-  customize-chrome-check-mark {
-    --customize-chrome-check-mark-wrapper-end: -1px;
+  #customColorContainer {
+    position: relative;
+  }
+
+  /* colorPicker is placed on top of the theme icon to work around
+     https://crbug.com/1162053. */
+  #colorPicker {
+    border: 0;
+    height: 50px;
+    left: 0;
+    margin: 0;
+    opacity: 0;
+    padding: 0;
+    pointer-events: none;
+    position: absolute;
+    top: 0;
+    width: 50px;
+  }
+
+  #colorPickerIcon {
+    -webkit-mask-image: url(chrome://resources/cr_components/customize_themes/colorize.svg);
+    -webkit-mask-repeat: no-repeat;
+    -webkit-mask-size: 100%;
+    background-color: var(--google-grey-700);
+    height: 20px;
+    left: calc(50% - 10px);
+    pointer-events: none;
+    position: absolute;
+    top: calc(50% - 10px);
+    width: 20px;
   }
 </style>
-<!-- TODO(crbug.com/1384229): Make grid adaptive. -->
-<cr-grid columns="4">
-  <!-- TODO(crbug.com/1384229): Add extracted color. -->
-  <customize-chrome-check-mark-wrapper checked>
-    <customize-chrome-color
-        id="defaultColor"
-        background-color="[[defaultColor_.background]]"
-        foreground-color="[[defaultColor_.foreground]]"
-        title="$i18n{defaultColorName}"
-        on-click="onDefaultColorClick_">
-    </customize-chrome-color>
-  </customize-chrome-check-mark-wrapper>
+<!-- TODO(crbug.com/1395210): Make grid adaptive. -->
+<cr-grid columns="4" role="radiogroup"
+    aria-label="$i18n{colorsContainerLabel}">
+  <!-- TODO(crbug.com/1401018): Add extracted color. -->
+  <customize-chrome-color
+      id="defaultColor"
+      background-color="[[defaultColor_.background]]"
+      foreground-color="[[defaultColor_.foreground]]"
+      title="$i18n{defaultColorName}"
+      aria-label="$i18n{defaultColorName}"
+      role="radio"
+      checked="[[isDefaultColorSelected_]]"
+      aria-checked$="[[isDefaultColorSelected_]]"
+      tabindex$="[[tabIndex_(isDefaultColorSelected_)]]"
+      on-click="onDefaultColorClick_">
+  </customize-chrome-color>
   <template id="chromeColors" is="dom-repeat" items="[[colors_]]">
-    <!-- TODO(crbug.com/1384229): Only set checked if selected. -->
-    <customize-chrome-check-mark-wrapper checked>
-      <customize-chrome-color
-          class="chrome-color"
-          background-color="[[item.background]]"
-          foreground-color="[[item.foreground]]"
-          title="[[item.name]]"
-          on-click="onChromeColorClick_">
-      </customize-chrome-color>
-    </customize-chrome-check-mark-wrapper>
+    <customize-chrome-color
+        class="chrome-color"
+        background-color="[[item.background]]"
+        foreground-color="[[item.foreground]]"
+        title="[[item.name]]"
+        aria-label$="[[item.name]]"
+        role="radio"
+        checked="[[isChromeColorSelected_(item.foreground, theme_, colors_)]]"
+        aria-checked$=
+            "[[isChromeColorSelected_(item.foreground, theme_, colors_)]]"
+        tabindex$="[[chromeColorTabIndex_(item.foreground, theme_, colors_)]]"
+        on-click="onChromeColorClick_">
+    </customize-chrome-color>
   </template>
-  <!-- TODO(crbug.com/1384229): Add color picker. -->
+  <div id="customColorContainer"
+      title="$i18n{colorPickerLabel}"
+      aria-label="$i18n{colorPickerLabel}"
+      role="radio"
+      aria-checked$="[[isCustomColorSelected_]]"
+      tabindex$="[[tabIndex_(isCustomColorSelected_)]]"
+      on-click="onCustomColorClick_">
+    <customize-chrome-color
+        id="customColor"
+        background-color="[[customColor_.background]]"
+        foreground-color="[[customColor_.foreground]]"
+        checked="[[isCustomColorSelected_]]">
+    </customize-chrome-color>
+    <div id="colorPickerIcon"></div>
+    <input id="colorPicker" type="color" tabindex="-1" aria-hidden="true"
+        on-change="onCustomColorChange_">
+  </div>
 </cr-grid>
diff --git a/chrome/browser/resources/side_panel/customize_chrome/colors.ts b/chrome/browser/resources/side_panel/customize_chrome/colors.ts
index 926fd80..ac571877 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/colors.ts
+++ b/chrome/browser/resources/side_panel/customize_chrome/colors.ts
@@ -4,8 +4,8 @@
 
 import 'chrome://resources/cr_elements/cr_grid/cr_grid.js';
 import './color.js';
-import './check_mark_wrapper.js';
 
+import {hexColorToSkColor, skColorToRgba} from 'chrome://resources/js/color_utils.js';
 import {SkColor} from 'chrome://resources/mojo/skia/public/mojom/skcolor.mojom-webui.js';
 import {DomRepeat, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
@@ -29,10 +29,20 @@
   foreground: {value: 0xff202124},
 };
 
+function isChromeColorSelected(
+    theme: Theme|undefined, colors: ChromeColor[]|undefined, color: SkColor) {
+  return !!theme && !!colors && !!theme.foregroundColor &&
+      theme.foregroundColor.value === color.value;
+}
+
 export interface ColorsElement {
   $: {
     defaultColor: ColorElement,
     chromeColors: DomRepeat,
+    customColorContainer: HTMLElement,
+    customColor: ColorElement,
+    colorPicker: HTMLInputElement,
+    colorPickerIcon: HTMLElement,
   };
 }
 
@@ -53,11 +63,34 @@
       },
       colors_: Array,
       theme_: Object,
+      isDefaultColorSelected_: {
+        type: Object,
+        computed: 'computeIsDefaultColorSelected_(theme_)',
+      },
+      isCustomColorSelected_: {
+        type: Object,
+        computed: 'computeIsCustomColorSelected_(theme_, color_)',
+      },
+      customColor_: {
+        type: Object,
+        value: {
+          background: {value: 0xffffffff},
+          foreground: {value: 0xfff1f3f4},
+        },
+      },
     };
   }
 
+  static get observers() {
+    return [
+      'updateCustomColor_(colors_, theme_, isCustomColorSelected_)',
+    ];
+  }
+
   private colors_: ChromeColor[];
   private theme_: Theme;
+  private isCustomColorSelected_: boolean;
+  private customColor_: Color;
   private setThemeListenerId_: number|null = null;
 
   constructor() {
@@ -89,6 +122,29 @@
                                         LIGHT_DEFAULT_COLOR;
   }
 
+  private computeIsDefaultColorSelected_(): boolean {
+    return this.theme_ && !this.theme_.foregroundColor;
+  }
+
+  private computeIsCustomColorSelected_(): boolean {
+    return !!this.colors_ && !!this.theme_ && !!this.theme_.foregroundColor &&
+        !this.colors_.find(
+            (color: ChromeColor) =>
+                color.foreground.value === this.theme_.foregroundColor!.value);
+  }
+
+  private isChromeColorSelected_(color: SkColor): boolean {
+    return isChromeColorSelected(this.theme_, this.colors_, color);
+  }
+
+  private tabIndex_(selected: boolean): string {
+    return selected ? '0' : '-1';
+  }
+
+  private chromeColorTabIndex_(color: SkColor): string {
+    return isChromeColorSelected(this.theme_, this.colors_, color) ? '0' : '-1';
+  }
+
   private onDefaultColorClick_() {
     CustomizeChromeApiProxy.getInstance().handler.setDefaultColor();
   }
@@ -97,6 +153,30 @@
     CustomizeChromeApiProxy.getInstance().handler.setForegroundColor(
         this.$.chromeColors.itemForElement(e.target as HTMLElement).foreground);
   }
+
+  private onCustomColorClick_() {
+    this.$.colorPicker.focus();
+    this.$.colorPicker.click();
+  }
+
+  private onCustomColorChange_(e: Event) {
+    CustomizeChromeApiProxy.getInstance().handler.setForegroundColor(
+        hexColorToSkColor((e.target as HTMLInputElement).value));
+  }
+
+  private updateCustomColor_() {
+    // We only change the custom color when theme updates to a new custom color
+    // so that the picked color persists while clicking on other color circles.
+    if (!this.isCustomColorSelected_) {
+      return;
+    }
+    this.customColor_ = {
+      background: this.theme_.backgroundColor,
+      foreground: this.theme_.foregroundColor!,
+    };
+    this.$.colorPickerIcon.style.setProperty(
+        'background-color', skColorToRgba(this.theme_.colorPickerIconColor));
+  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/side_panel/customize_chrome/icons/BUILD.gn b/chrome/browser/resources/side_panel/customize_chrome/icons/BUILD.gn
index c6f0ebfd..60f2b9c1 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/icons/BUILD.gn
+++ b/chrome/browser/resources/side_panel/customize_chrome/icons/BUILD.gn
@@ -9,7 +9,10 @@
 generate_grd("build_grdp") {
   grd_prefix = "side_panel_customize_chrome"
   out_grd = "$target_gen_dir/resources.grdp"
-  input_files = [ "mini_new_tab_page.svg" ]
+  input_files = [
+    "mini_new_tab_page.svg",
+    "uploaded_image.svg",
+  ]
   input_files_base_dir = rebase_path(".", "//")
   resource_path_prefix = "icons"
 }
diff --git a/chrome/browser/resources/side_panel/customize_chrome/icons/uploaded_image.svg b/chrome/browser/resources/side_panel/customize_chrome/icons/uploaded_image.svg
new file mode 100644
index 0000000..a7b06bae
--- /dev/null
+++ b/chrome/browser/resources/side_panel/customize_chrome/icons/uploaded_image.svg
@@ -0,0 +1 @@
+<svg width="288" height="162" viewBox="0 0 288 162" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="-4" width="295" height="166" rx="20" fill="#F1F3F4"/><path fill-rule="evenodd" clip-rule="evenodd" d="M152.333 71h-18.666A2.675 2.675 0 0 0 131 73.667v18.666C131 93.8 132.2 95 133.667 95h18.666C153.8 95 155 93.8 155 92.333V73.667C155 72.2 153.8 71 152.333 71Zm0 21.333h-18.666V73.667h18.666v18.666Zm-10.666-4.373 4-4.96L151 89.667h-16l4-5.334 2.667 3.627Z" fill="#5F6368"/></svg>
\ No newline at end of file
diff --git a/chrome/browser/resources/side_panel/customize_chrome/theme_snapshot.html b/chrome/browser/resources/side_panel/customize_chrome/theme_snapshot.html
index 804b196..d1846f0a 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/theme_snapshot.html
+++ b/chrome/browser/resources/side_panel/customize_chrome/theme_snapshot.html
@@ -1,13 +1,18 @@
 <style>
-  #themeSnapshot {
+  .theme-snapshot {
     align-items: center;
     display: flex;
     flex-wrap: wrap;
     justify-content: center;
+    margin-bottom: 8px;
     margin-inline-start: 16px;
     width: 288px;
   }
 
+  .theme-snapshot img {
+    object-fit: fill;
+  }
+
   .image {
     border: 1px solid rgba(0, 0, 0, .14);
     border-radius: 20px;
@@ -16,11 +21,7 @@
     width: inherit;
   }
 
-  #themeSnapshot img {
-    object-fit: fill;
-  }
-
-  #classicChromeImageContainer {
+  #classicChrome {
     align-items: center;
     background-color: var(--customize-chrome-color-background-color);
     display: flex;
@@ -33,22 +34,25 @@
   }
 
   #themeTitle {
-    margin-bottom: 8px;
     overflow: hidden;
     white-space: nowrap;
   }
 </style>
-<div id="themeSnapshot">
-  <template is="dom-if" if="[[showThemeSnapshot_]]">
+<iron-pages selected="[[themeType_]]" attr-for-selected="theme-type">
+  <div class="theme-snapshot" theme-type="customTheme">
     <img class="image" is="cr-auto-img"
         auto-src="[[theme_.backgroundImage.url.url]]" draggable="false">
     </img>
     <label id="themeTitle">[[theme_.backgroundImage.title]]</label>
-  </template>
-  <template is="dom-if" if="[[!showThemeSnapshot_]]">
-    <div class="image" id="classicChromeImageContainer">
+  </div>
+  <div class="theme-snapshot" theme-type="classicChrome">
+    <div class="image" id="classicChrome">
       <img id="miniNewTabPage" src="icons/mini_new_tab_page.svg"></img>
     </div>
     <label id="themeTitle">$i18n{classicChrome}</label>
-  </template>
-</div>
+  </div>
+  <div class="theme-snapshot" theme-type="uploadedImage">
+    <img class="image" src="icons/uploaded_image.svg"></img>
+    <label id="themeTitle">$i18n{uploadedImage}</label>
+  </div>
+</iron-pages>
diff --git a/chrome/browser/resources/side_panel/customize_chrome/theme_snapshot.ts b/chrome/browser/resources/side_panel/customize_chrome/theme_snapshot.ts
index 22c030ae..692bff4 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/theme_snapshot.ts
+++ b/chrome/browser/resources/side_panel/customize_chrome/theme_snapshot.ts
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import 'chrome://resources/cr_elements/cr_auto_img/cr_auto_img.js';
+import 'chrome://resources/polymer/v3_0/iron-pages/iron-pages.js';
 
 import {assert} from 'chrome://resources/js/assert_ts.js';
 import {skColorToRgba} from 'chrome://resources/js/color_utils.js';
@@ -12,7 +13,13 @@
 import {CustomizeChromeApiProxy} from './customize_chrome_api_proxy.js';
 import {getTemplate} from './theme_snapshot.html.js';
 
-/** Element used to display a snapshot of a NTP custom background. */
+export enum CustomizeThemeType {
+  CLASSIC_CHROME = 'classicChrome',
+  CUSTOM_THEME = 'customTheme',
+  UPLOADED_IMAGE = 'uploadedImage',
+}
+
+/** Element used to display a snapshot of the NTP. */
 export class ThemeSnapshotElement extends PolymerElement {
   static get is() {
     return 'customize-chrome-theme-snapshot';
@@ -26,16 +33,16 @@
     return {
       theme_: Object,
 
-      showThemeSnapshot_: {
-        type: Boolean,
-        computed: 'computeShowThemeSnapshot_(theme_)',
+      themeType_: {
+        type: String,
+        computed: 'computeThemeType_(theme_)',
       },
     };
   }
 
-  private theme_: Theme;
+  private theme_: Theme|undefined = undefined;
   private setThemeListenerId_: number|null = null;
-  private showThemeSnapshot_: boolean;
+  private themeType_: CustomizeThemeType|null = null;
 
   private callbackRouter_: CustomizeChromePageCallbackRouter;
   private pageHandler_: CustomizeChromePageHandlerInterface;
@@ -51,10 +58,12 @@
     this.setThemeListenerId_ =
         this.callbackRouter_.setTheme.addListener((theme: Theme) => {
           this.theme_ = theme;
-          this.updateStyles({
-            '--customize-chrome-color-background-color':
-                skColorToRgba(this.theme_.backgroundColor),
-          });
+          if (this.theme_) {
+            this.updateStyles({
+              '--customize-chrome-color-background-color':
+                  skColorToRgba(this.theme_.backgroundColor),
+            });
+          }
         });
     this.pageHandler_.updateTheme();
   }
@@ -66,8 +75,17 @@
     this.callbackRouter_.removeListener(this.setThemeListenerId_);
   }
 
-  private computeShowThemeSnapshot_(): boolean {
-    return !!this.theme_.backgroundImage;
+  private computeThemeType_(): CustomizeThemeType|null {
+    if (!this.theme_) {
+      return null;
+    }
+    if (!this.theme_.backgroundImage) {
+      return CustomizeThemeType.CLASSIC_CHROME;
+    } else if (!this.theme_.backgroundImage.isUploadedImage) {
+      return CustomizeThemeType.CUSTOM_THEME;
+    } else {
+      return CustomizeThemeType.UPLOADED_IMAGE;
+    }
   }
 }
 
diff --git a/chrome/browser/search/background/ntp_background_data.h b/chrome/browser/search/background/ntp_background_data.h
index 1426bbe..4cf64b8 100644
--- a/chrome/browser/search/background/ntp_background_data.h
+++ b/chrome/browser/search/background/ntp_background_data.h
@@ -119,6 +119,9 @@
   // Url of the custom background selected by the user.
   GURL custom_background_url;
 
+  // Whether the image is a local resource.
+  bool is_uploaded_image;
+
   // First attribution string for custom background.
   std::string custom_background_attribution_line_1;
 
diff --git a/chrome/browser/search/background/ntp_custom_background_service.cc b/chrome/browser/search/background/ntp_custom_background_service.cc
index fcdc2745..2c5b6b030 100644
--- a/chrome/browser/search/background/ntp_custom_background_service.cc
+++ b/chrome/browser/search/background/ntp_custom_background_service.cc
@@ -398,6 +398,7 @@
     std::string local_string(chrome::kChromeUIUntrustedNewTabPageBackgroundUrl);
     GURL timestamped_url(local_string + "?ts=" + time_string);
     custom_background->custom_background_url = timestamped_url;
+    custom_background->is_uploaded_image = true;
     custom_background->custom_background_attribution_line_1 = std::string();
     custom_background->custom_background_attribution_line_2 = std::string();
     custom_background->custom_background_attribution_action_url = GURL();
@@ -429,6 +430,7 @@
     const base::Value* color =
         background_info.Find(kNtpCustomBackgroundMainColor);
     custom_background->custom_background_url = custom_background_url;
+    custom_background->is_uploaded_image = false;
     custom_background->collection_id = collection_id;
 
     if (attribution_line_1) {
diff --git a/chrome/browser/search/background/ntp_custom_background_service_unittest.cc b/chrome/browser/search/background/ntp_custom_background_service_unittest.cc
index c4c4bd5..3348082 100644
--- a/chrome/browser/search/background/ntp_custom_background_service_unittest.cc
+++ b/chrome/browser/search/background/ntp_custom_background_service_unittest.cc
@@ -100,6 +100,7 @@
 
   auto custom_background = custom_background_service_->GetCustomBackground();
   EXPECT_EQ(kUrl, custom_background->custom_background_url);
+  EXPECT_EQ(false, custom_background->is_uploaded_image);
   EXPECT_TRUE(custom_background_service_->IsCustomBackgroundSet());
 }
 
@@ -138,6 +139,7 @@
 
   auto custom_background = custom_background_service_->GetCustomBackground();
   EXPECT_EQ(kUrl, custom_background->custom_background_url);
+  EXPECT_EQ(false, custom_background->is_uploaded_image);
   EXPECT_EQ(kAttributionLine1,
             custom_background->custom_background_attribution_line_1);
   EXPECT_EQ(kAttributionLine2,
@@ -168,6 +170,8 @@
   EXPECT_EQ(true, profile_.GetTestingPrefService()->GetBoolean(
                       prefs::kNtpCustomBackgroundLocalToDevice));
   EXPECT_TRUE(custom_background_service_->IsCustomBackgroundSet());
+  auto custom_background = custom_background_service_->GetCustomBackground();
+  EXPECT_EQ(true, custom_background->is_uploaded_image);
 }
 
 TEST_F(NtpCustomBackgroundServiceTest,
@@ -194,6 +198,8 @@
   EXPECT_EQ(false, profile_.GetTestingPrefService()->GetBoolean(
                        prefs::kNtpCustomBackgroundLocalToDevice));
   ASSERT_TRUE(custom_background_service_->IsCustomBackgroundSet());
+  auto custom_background = custom_background_service_->GetCustomBackground();
+  EXPECT_EQ(false, custom_background->is_uploaded_image);
 }
 
 TEST_F(NtpCustomBackgroundServiceTest, UpdatingPrefUpdatesNtpTheme) {
@@ -218,6 +224,7 @@
 
   custom_background = custom_background_service_->GetCustomBackground();
   EXPECT_EQ(kUrlBar, custom_background->custom_background_url);
+  EXPECT_EQ(false, custom_background->is_uploaded_image);
   EXPECT_EQ(false,
             pref_service->GetBoolean(prefs::kNtpCustomBackgroundLocalToDevice));
   EXPECT_TRUE(custom_background_service_->IsCustomBackgroundSet());
@@ -247,6 +254,7 @@
   EXPECT_TRUE(
       pref_service->GetBoolean(prefs::kNtpCustomBackgroundLocalToDevice));
   EXPECT_TRUE(custom_background_service_->IsCustomBackgroundSet());
+  EXPECT_EQ(true, custom_background->is_uploaded_image);
 }
 
 TEST_F(NtpCustomBackgroundServiceTest, SyncPrefOverridesAndRemovesLocalImage) {
@@ -278,6 +286,7 @@
 
   auto custom_background = custom_background_service_->GetCustomBackground();
   EXPECT_EQ(kUrl, custom_background->custom_background_url);
+  EXPECT_EQ(false, custom_background->is_uploaded_image);
   EXPECT_FALSE(
       pref_service->GetBoolean(prefs::kNtpCustomBackgroundLocalToDevice));
   EXPECT_FALSE(base::PathExists(path));
@@ -360,6 +369,7 @@
   EXPECT_TRUE(
       pref_service->GetBoolean(prefs::kNtpCustomBackgroundLocalToDevice));
   EXPECT_TRUE(custom_background_service_->IsCustomBackgroundSet());
+  EXPECT_EQ(true, custom_background->is_uploaded_image);
   EXPECT_EQ("", custom_background->custom_background_attribution_line_1);
   EXPECT_EQ("", custom_background->custom_background_attribution_line_2);
   EXPECT_EQ(GURL(),
diff --git a/chrome/browser/share/share_features.cc b/chrome/browser/share/share_features.cc
index 0b6d30dd..48fe6465 100644
--- a/chrome/browser/share/share_features.cc
+++ b/chrome/browser/share/share_features.cc
@@ -34,7 +34,7 @@
 #if !BUILDFLAG(IS_ANDROID)
 BASE_FEATURE(kDesktopSharePreview,
              "DesktopSharePreview",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 extern const char kDesktopSharePreviewVariant16[] = "16";
 extern const char kDesktopSharePreviewVariant40[] = "40";
 extern const char kDesktopSharePreviewVariant72[] = "72";
diff --git a/chrome/browser/speech/speech_recognition_browsertest.cc b/chrome/browser/speech/speech_recognition_browsertest.cc
index be381c0f..f5e95dd5 100644
--- a/chrome/browser/speech/speech_recognition_browsertest.cc
+++ b/chrome/browser/speech/speech_recognition_browsertest.cc
@@ -60,7 +60,7 @@
  public:
   explicit SpeechWebContentsObserver(WebContents* web_contents)
       : WebContentsObserver(web_contents),
-        render_view_host_changed_(false),
+        render_frame_host_changed_(false),
         web_contents_destroyed_(false) {}
 
   SpeechWebContentsObserver(const SpeechWebContentsObserver&) = delete;
@@ -70,23 +70,23 @@
   ~SpeechWebContentsObserver() override {}
 
   // content::WebContentsObserver overrides.
-  void RenderViewHostChanged(content::RenderViewHost* old_host,
-                             content::RenderViewHost* new_host) override {
-    render_view_host_changed_ = true;
+  void RenderFrameHostChanged(content::RenderFrameHost* old_host,
+                              content::RenderFrameHost* new_host) override {
+    render_frame_host_changed_ = true;
   }
   void WebContentsDestroyed() override { web_contents_destroyed_ = true; }
 
   bool web_contents_destroyed() { return web_contents_destroyed_; }
-  bool render_view_host_changed() { return render_view_host_changed_; }
+  bool render_frame_host_changed() { return render_frame_host_changed_; }
 
  private:
-  bool render_view_host_changed_;
+  bool render_frame_host_changed_;
   bool web_contents_destroyed_;
 };
 
 // Tests that ChromeSpeechRecognitionManagerDelegate works properly
-// when a WebContents goes away (WCO::WebContentsDestroyed) or the RVH
-// changes within a WebContents (WCO::RenderViewHostChanged).
+// when a WebContents goes away (WCO::WebContentsDestroyed) or the RFH
+// changes within a WebContents (WCO::RenderFrameHostChanged).
 IN_PROC_BROWSER_TEST_F(ChromeSpeechRecognitionTest, BasicTearDown) {
   ASSERT_TRUE(embedded_test_server()->Start());
   net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
@@ -128,11 +128,11 @@
     EXPECT_EQ(kExpectedTranscript, output);
   }
 
-  // Navigating to an https page will force RVH change within
-  // |web_contents|, results in WCO::RenderViewHostChanged().
+  // Navigating to an https page will force RFH change within
+  // |web_contents|, results in WCO::RenderFrameHostChanged().
   ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), https_url));
 
-  EXPECT_TRUE(speech_contents_observer.render_view_host_changed());
+  EXPECT_TRUE(speech_contents_observer.render_frame_host_changed());
 
   {
     content::TitleWatcher title_watcher(web_contents, success_title);
diff --git a/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApprovalMetrics.java b/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApprovalMetrics.java
index cc2356d1..25acd717 100644
--- a/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApprovalMetrics.java
+++ b/chrome/browser/supervised_user/android/java/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApprovalMetrics.java
@@ -11,10 +11,14 @@
 class WebsiteParentApprovalMetrics {
     // Histogram name
     static final String WEB_APPOVAL_OUTCOME_NAME = "FamilyLinkUser.LocalWebApprovalOutcome";
+    static final String WEB_APPOVAL_PACP_ERROR_CODE =
+            "Android.FamilyLinkUser.LocalWebApprovalParentAuthenticationError";
 
     // These values are persisted to logs. Entries should not be renumbered and
     // numeric values should never be reused.
     // The values need to be in sync with FamilyLinkUserLocalWebApprovalOutcome in enums.xml.
+    // TODO (b/261403529): Remove the unused values once the solution that records directly
+    // GMS error codes is integrated in Clank.
     @IntDef({FamilyLinkUserLocalWebApprovalOutcome.APPROVED_BY_PARENT,
             FamilyLinkUserLocalWebApprovalOutcome.DENIED_BY_PARENT,
             FamilyLinkUserLocalWebApprovalOutcome.PARENT_APPROVAL_CANCELLED,
@@ -42,4 +46,8 @@
         RecordHistogram.recordEnumeratedHistogram(
                 WEB_APPOVAL_OUTCOME_NAME, outcome, FamilyLinkUserLocalWebApprovalOutcome.COUNT);
     }
+
+    public static void recordParentAuthenticationErrorCode(int errorCode) {
+        RecordHistogram.recordSparseHistogram(WEB_APPOVAL_PACP_ERROR_CODE, errorCode);
+    }
 }
\ No newline at end of file
diff --git a/chrome/browser/supervised_user/android/javatests/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApprovalMetricsUnitTest.java b/chrome/browser/supervised_user/android/javatests/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApprovalMetricsUnitTest.java
index c8c1dea..c40bfa8 100644
--- a/chrome/browser/supervised_user/android/javatests/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApprovalMetricsUnitTest.java
+++ b/chrome/browser/supervised_user/android/javatests/src/org/chromium/chrome/browser/supervised_user/WebsiteParentApprovalMetricsUnitTest.java
@@ -61,4 +61,25 @@
         count = mHistogramTestRule.getHistogramTotalCount(histogramName);
         Assert.assertEquals(3, count);
     }
+
+    @Test
+    @SmallTest
+    public void recordParentAuthenticationErrorMetrics() {
+        final String histogramName =
+                "Android.FamilyLinkUser.LocalWebApprovalParentAuthenticationError";
+        final int negativeErrorCode = -1;
+        final int lowValueCode = 10; // Example: value of CommonStatusCode.DEVELOPER_ERROR.
+
+        WebsiteParentApprovalMetrics.recordParentAuthenticationErrorCode(negativeErrorCode);
+        int count = mHistogramTestRule.getHistogramValueCount(histogramName, negativeErrorCode);
+        Assert.assertEquals(1, count);
+
+        WebsiteParentApprovalMetrics.recordParentAuthenticationErrorCode(lowValueCode);
+        WebsiteParentApprovalMetrics.recordParentAuthenticationErrorCode(lowValueCode);
+        count = mHistogramTestRule.getHistogramValueCount(histogramName, lowValueCode);
+        Assert.assertEquals(2, count);
+
+        count = mHistogramTestRule.getHistogramTotalCount(histogramName);
+        Assert.assertEquals(3, count);
+    }
 }
\ No newline at end of file
diff --git a/chrome/browser/ui/android/omnibox/BUILD.gn b/chrome/browser/ui/android/omnibox/BUILD.gn
index be8ec10..83de8451 100644
--- a/chrome/browser/ui/android/omnibox/BUILD.gn
+++ b/chrome/browser/ui/android/omnibox/BUILD.gn
@@ -130,7 +130,6 @@
     "java/src/org/chromium/chrome/browser/omnibox/suggestions/tail/TailSuggestionViewProperties.java",
     "java/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentBottomSheet.java",
     "java/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentController.java",
-    "java/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentModal.java",
     "java/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentUi.java",
     "java/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchService.java",
     "java/src/org/chromium/chrome/browser/omnibox/voice/VoiceRecognitionHandler.java",
@@ -307,7 +306,6 @@
     "java/res/drawable-xxxhdpi/btn_suggestion_refine.png",
     "java/res/drawable-xxxhdpi/ic_history_googblue_24dp.png",
     "java/res/drawable-xxxhdpi/ic_suggestion_magnifier.png",
-    "java/res/drawable/assistant_consent_illustration.xml",
     "java/res/drawable/ic_book_round.xml",
     "java/res/drawable/ic_colorful_mic.xml",
     "java/res/drawable/ic_content_copy_black.xml",
@@ -324,7 +322,6 @@
     "java/res/drawable/trending_up_black_24dp.xml",
     "java/res/layout-sw600dp/location_bar.xml",
     "java/res/layout/assistant_voice_search_consent_ui.xml",
-    "java/res/layout/assistant_voice_search_modal_consent_ui.xml",
     "java/res/layout/assistant_voice_search_simplified_consent_ui.xml",
     "java/res/layout/location_bar.xml",
     "java/res/layout/location_bar_base.xml",
diff --git a/chrome/browser/ui/android/omnibox/java/res/drawable/assistant_consent_illustration.xml b/chrome/browser/ui/android/omnibox/java/res/drawable/assistant_consent_illustration.xml
deleted file mode 100644
index 7a1597f..0000000
--- a/chrome/browser/ui/android/omnibox/java/res/drawable/assistant_consent_illustration.xml
+++ /dev/null
@@ -1,120 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-Copyright 2022 The Chromium Authors
-Use of this source code is governed by a BSD-style license that can be
-found in the LICENSE file.
--->
-<!--VectorRaster is ignored because: The image should be shown rarely enough to
-    prioritize binary size over inflation time.-->
-<vector
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:width="256dp"
-    android:height="181dp"
-    android:viewportWidth="256"
-    android:viewportHeight="181"
-    tools:ignore="VectorRaster">
-  <group>
-    <clip-path
-        android:pathData="M-36,0h332v181h-332z"/>
-    <path
-        android:pathData="m161,19.5h-62c-4.142,0 -7.5,3.358 -7.5,7.5v122c0,4.142 3.358,7.5 7.5,7.5h62c4.142,0 7.5,-3.358 7.5,-7.5L168.5,27c0,-4.142 -3.358,-7.5 -7.5,-7.5z"
-        android:strokeWidth="5"
-        android:fillColor="#ffffff"
-        android:strokeColor="#9aa0a6"/>
-    <path
-        android:pathData="m145,30h-42c-2.761,0 -5,2.239 -5,5 0,2.761 2.239,5 5,5h42c2.761,0 5,-2.239 5,-5 0,-2.761 -2.239,-5 -5,-5z"
-        android:fillColor="#9aa0a6"/>
-    <path
-        android:pathData="m152,35c0,2.761 2.239,5 5,5 2.761,0 5,-2.239 5,-5 0,-2.761 -2.239,-5 -5,-5 -2.761,0 -5,2.239 -5,5z"
-        android:fillColor="#9aa0a6"/>
-    <path
-        android:pathData="m146.502,83.128c1.476,0 2.673,-1.164 2.673,-2.601 0,-1.436 -1.197,-2.601 -2.673,-2.601 -1.475,0 -2.672,1.164 -2.672,2.601 0,1.436 1.197,2.601 2.672,2.601z"
-        android:fillColor="#33a853"/>
-    <path
-        android:pathData="m138.49,91.788c2.949,0 5.34,-2.327 5.34,-5.197 0,-2.87 -2.391,-5.197 -5.34,-5.197 -2.95,0 -5.34,2.327 -5.34,5.197 0,2.87 2.39,5.197 5.34,5.197z"
-        android:fillColor="#e55145"/>
-    <path
-        android:pathData="m138.49,105.645c3.442,0 6.232,-2.715 6.232,-6.064 0,-3.349 -2.79,-6.064 -6.232,-6.064 -3.442,0 -6.233,2.715 -6.233,6.064 0,3.349 2.791,6.064 6.233,6.064z"
-        android:fillColor="#fcc934"/>
-    <path
-        android:pathData="m120.685,91.788c5.901,0 10.684,-4.654 10.684,-10.394C131.369,75.654 126.586,71 120.685,71 114.784,71 110,75.654 110,81.394c0,5.74 4.784,10.394 10.685,10.394z"
-        android:fillColor="#4284f4"/>
-    <path
-        android:pathData="M82.484,132.567L55.478,132.567C45.825,132.567 38,140.422 38,150.111v0.005c0,9.69 7.825,17.545 17.478,17.545h27.006c9.653,0 17.478,-7.855 17.478,-17.545v-0.005c0,-9.689 -7.825,-17.544 -17.478,-17.544z"
-        android:fillColor="#fde293"/>
-    <path
-        android:pathData="M68.063,135.249 L82.058,118v17.249z"
-        android:fillColor="#fde293"/>
-    <path
-        android:pathData="m56.973,143.863 l0.682,13.839c0.003,0.148 0.047,0.293 0.129,0.418 0.082,0.125 0.197,0.225 0.333,0.289 0.136,0.064 0.288,0.089 0.438,0.073 0.15,-0.016 0.292,-0.073 0.411,-0.164l11.888,-8.648c0.121,-0.083 0.216,-0.196 0.276,-0.329 0.059,-0.132 0.082,-0.277 0.064,-0.421 -0.017,-0.144 -0.074,-0.28 -0.164,-0.394 -0.09,-0.115 -0.209,-0.203 -0.346,-0.256l-12.582,-5.199c-0.128,-0.054 -0.268,-0.075 -0.406,-0.06 -0.138,0.015 -0.27,0.064 -0.384,0.144 -0.113,0.079 -0.204,0.186 -0.263,0.31 -0.059,0.124 -0.085,0.261 -0.076,0.398z"
-        android:fillColor="#e29a64"/>
-    <path
-        android:pathData="m77.87,142.637 l-0.101,-0.007c-0.506,-0.041 -0.949,0.339 -0.989,0.847l-1.163,14.811c-0.04,0.508 0.338,0.952 0.844,0.992l0.1,0.008c0.507,0.04 0.949,-0.339 0.989,-0.847l1.164,-14.811c0.04,-0.508 -0.338,-0.952 -0.844,-0.993z"
-        android:fillColor="#e29a64"/>
-    <path
-        android:pathData="m82.141,142.974 l-0.101,-0.008c-0.506,-0.04 -0.949,0.339 -0.989,0.847l-1.164,14.811c-0.04,0.508 0.338,0.952 0.845,0.992l0.1,0.008c0.506,0.04 0.949,-0.339 0.989,-0.847l1.164,-14.811c0.04,-0.508 -0.338,-0.952 -0.844,-0.992z"
-        android:fillColor="#e29a64"/>
-    <path
-        android:pathData="M228.147,105L198.86,105c-13.786,0 -24.962,11.218 -24.962,25.056v0.006c0,13.838 11.176,25.056 24.962,25.056h29.287c13.786,0 24.962,-11.218 24.962,-25.056v-0.006C253.109,116.218 241.933,105 228.147,105Z"
-        android:fillColor="#4285f4"/>
-    <path
-        android:pathData="M193.684,109.785 L161,106.713l23.15,22.589z"
-        android:fillColor="#4285f4"/>
-    <path
-        android:pathData="m191,122h13"
-        android:strokeWidth="3"
-        android:fillColor="#00000000"
-        android:strokeColor="#ffffff"/>
-    <path
-        android:pathData="m209,122h28"
-        android:strokeWidth="3"
-        android:fillColor="#00000000"
-        android:strokeColor="#ffffff"/>
-    <path
-        android:pathData="m188,137h15"
-        android:strokeWidth="3"
-        android:fillColor="#00000000"
-        android:strokeColor="#ffffff"/>
-    <path
-        android:pathData="m210,137h8"
-        android:strokeWidth="3"
-        android:fillColor="#00000000"
-        android:strokeColor="#ffffff"/>
-    <path
-        android:pathData="m224,137h16"
-        android:strokeWidth="3"
-        android:fillColor="#00000000"
-        android:strokeColor="#ffffff"/>
-    <path
-        android:pathData="m161.876,66.669 l31.476,-41.265 8.888,18.441z"
-        android:fillColor="#aecbfa"/>
-    <path
-        android:pathData="m217.116,48.732c9.833,-9.87 9.767,-25.938 -0.147,-35.89 -9.913,-9.951 -25.921,-10.017 -35.754,-0.147 -9.832,9.87 -9.767,25.938 0.147,35.89 9.914,9.951 25.922,10.017 35.754,0.147z"
-        android:fillColor="#aecbfa"/>
-    <path
-        android:pathData="m190.806,43.51c-0.099,0.001 -0.197,-0.027 -0.282,-0.078 -0.086,-0.052 -0.155,-0.126 -0.201,-0.215 -0.046,-0.089 -0.066,-0.188 -0.059,-0.288 0.006,-0.1 0.041,-0.196 0.098,-0.277l6.457,-9.278c0.039,-0.062 0.09,-0.115 0.15,-0.157 0.061,-0.041 0.129,-0.07 0.2,-0.085 0.072,-0.014 0.146,-0.014 0.217,0.001 0.072,0.015 0.14,0.044 0.2,0.086 0.06,0.042 0.111,0.096 0.149,0.158 0.039,0.062 0.065,0.132 0.076,0.204 0.011,0.072 0.008,0.147 -0.01,0.218 -0.018,0.071 -0.05,0.138 -0.094,0.196l-6.457,9.298c-0.051,0.069 -0.119,0.124 -0.196,0.162 -0.077,0.038 -0.162,0.056 -0.248,0.055z"
-        android:strokeWidth="0.5"
-        android:fillColor="#2e4469"
-        android:strokeColor="#2e4469"/>
-    <path
-        android:strokeWidth="1"
-        android:pathData="m201.495,34.841c4.463,0 8.082,-3.632 8.082,-8.112 0,-4.48 -3.619,-8.112 -8.082,-8.112 -4.463,0 -8.082,3.632 -8.082,8.112 0,4.48 3.619,8.112 8.082,8.112z"
-        android:fillColor="#00000000"
-        android:strokeColor="#000000"/>
-    <path
-        android:pathData="m201.495,35.385c-1.706,0 -3.373,-0.508 -4.791,-1.459 -1.418,-0.951 -2.523,-2.303 -3.176,-3.885 -0.653,-1.582 -0.823,-3.322 -0.491,-5.001 0.333,-1.679 1.154,-3.221 2.36,-4.432 1.206,-1.211 2.743,-2.035 4.416,-2.369 1.673,-0.334 3.406,-0.163 4.982,0.493 1.576,0.655 2.923,1.765 3.87,3.188 0.948,1.423 1.454,3.097 1.454,4.809 -0.004,2.295 -0.913,4.495 -2.53,6.117 -1.616,1.623 -3.808,2.536 -6.094,2.539zM201.495,19.16c-1.491,0 -2.949,0.444 -4.189,1.276 -1.24,0.832 -2.206,2.014 -2.777,3.397 -0.571,1.383 -0.72,2.905 -0.429,4.373 0.291,1.468 1.009,2.817 2.064,3.875 1.054,1.059 2.397,1.779 3.86,2.071 1.463,0.292 2.979,0.142 4.356,-0.431 1.378,-0.573 2.556,-1.543 3.384,-2.788 0.829,-1.245 1.271,-2.708 1.271,-4.205 -0.004,-2.005 -0.8,-3.927 -2.214,-5.344 -1.413,-1.417 -3.328,-2.214 -5.326,-2.216z"
-        android:strokeWidth="0.5"
-        android:fillColor="#2e4469"
-        android:strokeColor="#2e4469"/>
-    <path
-        android:pathData="m226.5,72c3.038,0 5.5,-2.462 5.5,-5.5 0,-3.038 -2.462,-5.5 -5.5,-5.5 -3.038,0 -5.5,2.462 -5.5,5.5 0,3.038 2.462,5.5 5.5,5.5z"
-        android:fillColor="#dadce0"/>
-    <path
-        android:pathData="m179.404,165.212 l-5.037,2.908c-0.905,0.522 -1.215,1.679 -0.693,2.584l2.908,5.037c0.523,0.905 1.68,1.215 2.585,0.692l5.036,-2.908c0.905,-0.522 1.215,-1.679 0.693,-2.584l-2.908,-5.037c-0.523,-0.905 -1.68,-1.215 -2.584,-0.692z"
-        android:fillColor="#dadce0"/>
-    <path
-        android:pathData="m52.757,67.708 l9.765,3.234c0.185,0.062 0.384,0.075 0.577,0.038 0.192,-0.037 0.371,-0.122 0.519,-0.248 0.148,-0.125 0.259,-0.286 0.322,-0.467 0.064,-0.18 0.077,-0.373 0.038,-0.56l-1.999,-9.83c-0.038,-0.187 -0.126,-0.361 -0.255,-0.504 -0.129,-0.143 -0.295,-0.251 -0.48,-0.313 -0.186,-0.062 -0.385,-0.074 -0.577,-0.037 -0.192,0.037 -0.371,0.123 -0.519,0.248l-7.766,6.596c-0.148,0.125 -0.259,0.286 -0.323,0.466 -0.064,0.18 -0.077,0.374 -0.039,0.561 0.038,0.187 0.126,0.361 0.255,0.504 0.129,0.143 0.295,0.251 0.481,0.312z"
-        android:fillColor="#dadce0"/>
-  </group>
-</vector>
diff --git a/chrome/browser/ui/android/omnibox/java/res/layout/assistant_voice_search_modal_consent_ui.xml b/chrome/browser/ui/android/omnibox/java/res/layout/assistant_voice_search_modal_consent_ui.xml
deleted file mode 100644
index 16c47da..0000000
--- a/chrome/browser/ui/android/omnibox/java/res/layout/assistant_voice_search_modal_consent_ui.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-Copyright 2022 The Chromium Authors
-Use of this source code is governed by a BSD-style license that can be
-found in the LICENSE file.
--->
-<org.chromium.components.browser_ui.widget.FadingEdgeScrollView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:id="@+id/avs_consent_ui"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:importantForAccessibility="no">
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="vertical"
-        android:gravity="center_vertical"
-        android:paddingHorizontal="20dp"
-        android:paddingVertical="10dp"
-        android:importantForAccessibility="no">
-        <ImageView
-            android:layout_width="match_parent"
-            android:layout_height="122dp"
-            android:layout_marginTop="20dp"
-            android:importantForAccessibility="no"
-            android:src="@drawable/assistant_consent_illustration"/>
-
-        <TextView
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="20dp"
-            style="@style/TextAppearance.Headline.Primary"
-            android:text="@string/avs_consent_ui_simplified_title" />
-
-        <TextView
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="20dp"
-            style="@style/TextAppearance.TextSmall.Secondary"
-            android:text="@string/avs_consent_ui_simplified_body" />
-
-        <TextView
-            android:id="@+id/avs_consent_ui_learn_more"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="2dp"
-            style="@style/TextAppearance.TextSmall.Link"
-            android:text="@string/learn_more" />
-
-    </LinearLayout>
-</org.chromium.components.browser_ui.widget.FadingEdgeScrollView>
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentController.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentController.java
index 83d93aa..c07cc9d7 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentController.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentController.java
@@ -18,7 +18,6 @@
 import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.ui.base.WindowAndroid;
-import org.chromium.ui.modaldialog.ModalDialogManager;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -40,9 +39,6 @@
      * @param bottomSheetController The {@link BottomSheetController} used to show the bottom sheet
      *                              UI. This can be null when starting the consent flow from
      *                              SearchActivity.
-     * @param modalDialogManager The {@link ModalDialogManager} used to show the modal dialog UI.
-     *                           This can be null when starting the consent flow from
-     *                           SearchActivity.
      * @param completionCallback A callback to be invoked if the user is continuing with the
      *                           requested voice search.
      */
@@ -50,38 +46,24 @@
             @NonNull SharedPreferencesManager sharedPreferencesManager,
             @NonNull Runnable launchAssistantSettingsAction,
             @Nullable BottomSheetController bottomSheetController,
-            @Nullable ModalDialogManager modalDialogManager,
             @NonNull Callback<Boolean> completionCallback) {
         AssistantVoiceSearchConsentUi consentUi;
         assert (!ChromeFeatureList.isEnabled(
                 ChromeFeatureList.ASSISTANT_NON_PERSONALIZED_VOICE_SEARCH));
 
-        if (ChromeFeatureList.isEnabled(ChromeFeatureList.ASSISTANT_CONSENT_MODAL)) {
-            // If the modal manager isn't available, bail out of the consent flow and fallback to
-            // system-ui. Consent will be retried the next time.
-            if (modalDialogManager == null) {
-                PostTask.postTask(TaskTraits.USER_VISIBLE,
-                        () -> { completionCallback.onResult(/* useAssistant= */ false); });
-                return;
-            }
-
-            consentUi = new AssistantVoiceSearchConsentModal(
-                    windowAndroid.getContext().get(), modalDialogManager);
-        } else {
-            // When attempting voice search through the search widget, the bottom sheet isn't
-            // available. When this happens, bail out of the consent flow and fallback to system-ui.
-            // Consent will be retried the next time.
-            if (bottomSheetController == null) {
-                PostTask.postTask(TaskTraits.USER_VISIBLE,
-                        () -> { completionCallback.onResult(/* useAssistant= */ false); });
-                return;
-            }
-
-            // Use the bottom sheet consent by default.
-            consentUi = new AssistantVoiceSearchConsentBottomSheet(
-                    windowAndroid.getContext().get(), bottomSheetController);
+        // When attempting voice search through the search widget, the bottom sheet isn't
+        // available. When this happens, bail out of the consent flow and fallback to system-ui.
+        // Consent will be retried the next time.
+        if (bottomSheetController == null) {
+            PostTask.postTask(TaskTraits.USER_VISIBLE,
+                    () -> { completionCallback.onResult(/* useAssistant= */ false); });
+            return;
         }
 
+        // Use the bottom sheet consent by default.
+        consentUi = new AssistantVoiceSearchConsentBottomSheet(
+                windowAndroid.getContext().get(), bottomSheetController);
+
         AssistantVoiceSearchConsentController consent =
                 new AssistantVoiceSearchConsentController(windowAndroid, sharedPreferencesManager,
                         completionCallback, launchAssistantSettingsAction, consentUi);
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentModal.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentModal.java
deleted file mode 100644
index 2411a16..0000000
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/voice/AssistantVoiceSearchConsentModal.java
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.omnibox.voice;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.view.LayoutInflater;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-
-import org.chromium.chrome.browser.omnibox.R;
-import org.chromium.ui.modaldialog.DialogDismissalCause;
-import org.chromium.ui.modaldialog.ModalDialogManager;
-import org.chromium.ui.modaldialog.ModalDialogProperties;
-import org.chromium.ui.modaldialog.SimpleModalDialogController;
-import org.chromium.ui.modelutil.PropertyModel;
-
-/**
- * The modal dialog implementation of the ConsentUI shown to users upon first using Assistant Voice
- * Search.
- */
-class AssistantVoiceSearchConsentModal implements AssistantVoiceSearchConsentUi {
-    private View mContentView;
-    private AssistantVoiceSearchConsentUi.Observer mObserver;
-
-    private ModalDialogManager mModalDialogManager;
-    private PropertyModel mConsentModal;
-
-    public AssistantVoiceSearchConsentModal(
-            @NonNull Context context, @NonNull ModalDialogManager modalDialogManager) {
-        mModalDialogManager = modalDialogManager;
-
-        mContentView = LayoutInflater.from(context).inflate(
-                R.layout.assistant_voice_search_modal_consent_ui, /* root= */ null);
-
-        View learnMore = mContentView.findViewById(R.id.avs_consent_ui_learn_more);
-        learnMore.setOnClickListener((v) -> mObserver.onLearnMoreClicked());
-
-        Resources resources = context.getResources();
-        mConsentModal =
-                new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS)
-                        .with(ModalDialogProperties.CONTROLLER,
-                                new SimpleModalDialogController(
-                                        mModalDialogManager, this::onDismissConsentModal))
-                        .with(ModalDialogProperties.POSITIVE_BUTTON_TEXT, resources,
-                                R.string.avs_consent_ui_simplified_accept)
-                        .with(ModalDialogProperties.NEGATIVE_BUTTON_TEXT, resources,
-                                R.string.avs_consent_ui_simplified_deny)
-                        .with(ModalDialogProperties.BUTTON_STYLES,
-                                ModalDialogProperties.ButtonStyles.PRIMARY_FILLED_NEGATIVE_OUTLINE)
-                        .with(ModalDialogProperties.CANCEL_ON_TOUCH_OUTSIDE, true)
-                        .with(ModalDialogProperties.CUSTOM_VIEW, mContentView)
-                        .build();
-    }
-
-    // AssistantVoiceSearchConsentUi implementation.
-
-    @Override
-    public void show(AssistantVoiceSearchConsentUi.Observer observer) {
-        assert mObserver == null;
-
-        mObserver = observer;
-        mModalDialogManager.showDialog(mConsentModal, ModalDialogManager.ModalDialogType.APP);
-    }
-
-    @Override
-    public void dismiss() {
-        mObserver = null;
-        mModalDialogManager.dismissDialog(mConsentModal, DialogDismissalCause.ACTION_ON_CONTENT);
-    }
-
-    private void onDismissConsentModal(@DialogDismissalCause int dismissalCause) {
-        if (mObserver == null) {
-            // The observer was already notified of a result, so the UI was dismissed by the
-            // controller.
-            return;
-        }
-
-        if (dismissalCause == DialogDismissalCause.POSITIVE_BUTTON_CLICKED) {
-            mObserver.onConsentAccepted();
-        } else if (dismissalCause == DialogDismissalCause.NEGATIVE_BUTTON_CLICKED) {
-            mObserver.onConsentRejected();
-        } else if (dismissalCause == DialogDismissalCause.NAVIGATE_BACK_OR_TOUCH_OUTSIDE) {
-            mObserver.onConsentCanceled();
-        } else {
-            mObserver.onNonUserCancel();
-        }
-        mObserver = null;
-    }
-}
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/voice/VoiceRecognitionHandler.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/voice/VoiceRecognitionHandler.java
index 6054406..56f299f 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/voice/VoiceRecognitionHandler.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/voice/VoiceRecognitionHandler.java
@@ -863,8 +863,7 @@
             mDelegate.clearOmniboxFocus();
             AssistantVoiceSearchConsentController.show(windowAndroid,
                     SharedPreferencesManager.getInstance(), mLaunchAssistanceSettingsAction,
-                    BottomSheetControllerProvider.from(windowAndroid),
-                    windowAndroid.getModalDialogManager(), (useAssistant) -> {
+                    BottomSheetControllerProvider.from(windowAndroid), (useAssistant) -> {
                         // Notify the service about the consent completion.
                         assistantVoiceSearchService.onAssistantConsentDialogComplete(useAssistant);
 
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 bb51425..8b496f47 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
@@ -363,29 +363,21 @@
      *
      * @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.
+     * @param areaWidthDp Width of the widget area.
+     * @param areaHeightDp Height of the widget area.
      * @return RemoteViews to be installed on the Dino widget.
      */
     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 =
+            @NonNull SearchActivityPreferences prefs, int areaWidthDp, int areaHeightDp) {
+        RemoteViews views =
                 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);
+                context.getApplicationContext().getResources(), views, areaWidthDp, areaHeightDp);
+        return views;
     }
 
     /**
@@ -396,26 +388,16 @@
      *
      * @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 Search widget.
+     * @param areaWidthDp Width of the widget area.
+     * @param areaHeightDp Height of the widget area.
+     * @return RemoteViews to be installed on the Search widget for the passed variant.
      */
     public @NonNull RemoteViews createSearchWidgetRemoteViews(@NonNull Context context,
-            @NonNull SearchActivityPreferences prefs, int portraitModeWidthDp,
-            int portraitModeHeightDp, int landscapeModeWidthDp, int landscapeModeHeightDp) {
-        WidgetVariant landscapeVariant = getSearchWidgetVariantForHeight(landscapeModeHeightDp);
-        RemoteViews landscapeViews = createWidgetRemoteViews(context, landscapeVariant.layout);
-        applyRemoteViewsButtonVisibilityToFitWidth(
-                landscapeViews, prefs, landscapeVariant, landscapeModeWidthDp);
-
-        WidgetVariant portraitVariant = getSearchWidgetVariantForHeight(portraitModeHeightDp);
-        RemoteViews portraitViews = createWidgetRemoteViews(context, portraitVariant.layout);
-        applyRemoteViewsButtonVisibilityToFitWidth(
-                portraitViews, prefs, portraitVariant, portraitModeWidthDp);
-
-        return new RemoteViews(landscapeViews, portraitViews);
+            @NonNull SearchActivityPreferences prefs, int areaWidthDp, int areaHeightDp) {
+        WidgetVariant variant = getSearchWidgetVariantForHeight(areaHeightDp);
+        RemoteViews views = createWidgetRemoteViews(context, variant.layout);
+        applyRemoteViewsButtonVisibilityToFitWidth(views, prefs, variant, areaWidthDp);
+        return views;
     }
 
     /**
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 377fb0e..381f7e5 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
@@ -179,18 +179,13 @@
         float density = res.getDisplayMetrics().density;
 
         mWidgetView = mDelegate
-                              .createSearchWidgetRemoteViews(mContext, prefs,
-                                      // Simulate optimally sized rectangular screen:
-                                      // Portrait mode dimensions:
-                                      mDefaultWidgetWidthDp, mMediumWidgetMinHeightDp,
-                                      // Landscape mode dimensions:
-                                      mDefaultWidgetWidthDp, mMediumWidgetMinHeightDp)
+                              .createSearchWidgetRemoteViews(mContext, prefs, mDefaultWidgetWidthDp,
+                                      mMediumWidgetMinHeightDp)
                               .apply(mContext, null);
-        mDinoWidgetView =
-                mDelegate
-                        .createDinoWidgetRemoteViews(mContext, prefs, mDinoWidgetEdgeSizeDp,
-                                mDinoWidgetEdgeSizeDp, mDinoWidgetEdgeSizeDp, mDinoWidgetEdgeSizeDp)
-                        .apply(mContext, null);
+        mDinoWidgetView = mDelegate
+                                  .createDinoWidgetRemoteViews(mContext, prefs,
+                                          mDinoWidgetEdgeSizeDp, mDinoWidgetEdgeSizeDp)
+                                  .apply(mContext, null);
     }
 
     /**
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index 97d6b46..fef7816 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -3558,7 +3558,7 @@
         Restore tab group of <ph name="TAB_COUNT">%1$s<ex>5</ex></ph> tabs as a new background tab group.
       </message>
       <message name="IDS_WEB_FEED_AWARENESS" desc="Educating the user about the web feed.">
-        See sites you follow here
+        See content you follow here
       </message>
 
       <!-- Explore sites strings -->
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_WEB_FEED_AWARENESS.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_WEB_FEED_AWARENESS.png.sha1
index d9a5695..52cc55de 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_WEB_FEED_AWARENESS.png.sha1
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_WEB_FEED_AWARENESS.png.sha1
@@ -1 +1 @@
-ad4be6652099b56efeedc53b02a1fee65ea4f81d
\ No newline at end of file
+839ef508d898ad0600a9995d2b24a4f10287f37b
\ No newline at end of file
diff --git a/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc b/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc
index a35409c..74b470e 100644
--- a/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc
+++ b/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc
@@ -537,11 +537,6 @@
     provider->Start();
 
     system_web_app_manager->ScheduleStart();
-
-    base::RunLoop run_loop;
-    provider->on_external_managers_synchronized().Post(FROM_HERE,
-                                                       run_loop.QuitClosure());
-    run_loop.Run();
   }
 
   // Note that this resets previously installed SWAs.
diff --git a/chrome/browser/ui/ash/system_tray_tray_cast_browsertest_media_router_chromeos.cc b/chrome/browser/ui/ash/system_tray_tray_cast_browsertest_media_router_chromeos.cc
index 78729c7..8c82cfe 100644
--- a/chrome/browser/ui/ash/system_tray_tray_cast_browsertest_media_router_chromeos.cc
+++ b/chrome/browser/ui/ash/system_tray_tray_cast_browsertest_media_router_chromeos.cc
@@ -131,8 +131,7 @@
   }
 
   media_router::MediaRoutesObserver* media_routes_observer() const {
-    DCHECK(media_routes_observer_);
-    return media_routes_observer_;
+    return &(*media_router_->routes_observers().begin());
   }
 
  private:
@@ -149,9 +148,6 @@
     ON_CALL(*media_router_, RegisterMediaSinksObserver(_))
         .WillByDefault(Invoke(
             this, &SystemTrayTrayCastMediaRouterChromeOSTest::CaptureSink));
-    ON_CALL(*media_router_, RegisterMediaRoutesObserver(_))
-        .WillByDefault(Invoke(
-            this, &SystemTrayTrayCastMediaRouterChromeOSTest::CaptureRoutes));
     CastConfigControllerMediaRouter::SetMediaRouterForTest(media_router_.get());
     InProcessBrowserTest::PreRunTestOnMainThread();
   }
@@ -171,13 +167,8 @@
     return true;
   }
 
-  void CaptureRoutes(media_router::MediaRoutesObserver* media_routes_observer) {
-    media_routes_observer_ = media_routes_observer;
-  }
-
   std::unique_ptr<media_router::MockMediaRouter> media_router_;
   media_router::MediaSinksObserver* media_sinks_observer_ = nullptr;
-  media_router::MediaRoutesObserver* media_routes_observer_ = nullptr;
   std::unique_ptr<ash::SystemTrayTestApi> tray_test_api_;
 };
 
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc b/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc
index 7e90858..6c2e7da1 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_bar_view.cc
@@ -318,9 +318,7 @@
   }
 
   std::u16string GetTooltipText(const gfx::Point& p) const override {
-    return label()->GetPreferredSize().width() > label()->size().width()
-               ? GetAccessibleName()
-               : std::u16string();
+    return GetAccessibleName();
   }
 
   bool OnMousePressed(const ui::MouseEvent& event) override {
diff --git a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_bar.cc b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_bar.cc
index 6c91bc8..cfba815 100644
--- a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_bar.cc
+++ b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_bar.cc
@@ -81,8 +81,10 @@
   SavedTabGroupRemoved(removed_group->saved_guid());
 }
 
-void SavedTabGroupBar::SavedTabGroupUpdatedLocally(const base::GUID& guid) {
-  SavedTabGroupUpdated(guid);
+void SavedTabGroupBar::SavedTabGroupUpdatedLocally(
+    const base::GUID& group_guid,
+    const absl::optional<base::GUID>& tab_guid) {
+  SavedTabGroupUpdated(group_guid);
 }
 
 void SavedTabGroupBar::SavedTabGroupReorderedLocally() {
@@ -104,8 +106,10 @@
   SavedTabGroupRemoved(removed_group->saved_guid());
 }
 
-void SavedTabGroupBar::SavedTabGroupUpdatedFromSync(const base::GUID& guid) {
-  SavedTabGroupUpdated(guid);
+void SavedTabGroupBar::SavedTabGroupUpdatedFromSync(
+    const base::GUID& group_guid,
+    const absl::optional<base::GUID>& tab_guid) {
+  SavedTabGroupUpdated(group_guid);
 }
 
 int SavedTabGroupBar::CalculatePreferredWidthRestrictedBy(int max_x) {
diff --git a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_bar.h b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_bar.h
index c7eb73c..db9100c 100644
--- a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_bar.h
+++ b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_bar.h
@@ -11,6 +11,7 @@
 #include "components/saved_tab_groups/saved_tab_group_model.h"
 #include "components/saved_tab_groups/saved_tab_group_model_observer.h"
 #include "content/public/browser/page.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/views/accessible_pane_view.h"
 
 class Browser;
@@ -47,12 +48,16 @@
   // SavedTabGroupModelObserver
   void SavedTabGroupAddedLocally(const base::GUID& guid) override;
   void SavedTabGroupRemovedLocally(const SavedTabGroup* removed_group) override;
-  void SavedTabGroupUpdatedLocally(const base::GUID& guid) override;
+  void SavedTabGroupUpdatedLocally(
+      const base::GUID& group_guid,
+      const absl::optional<base::GUID>& tab_guid = absl::nullopt) override;
   void SavedTabGroupReorderedLocally() override;
   void SavedTabGroupAddedFromSync(const base::GUID& guid) override;
   void SavedTabGroupRemovedFromSync(
       const SavedTabGroup* removed_group) override;
-  void SavedTabGroupUpdatedFromSync(const base::GUID& guid) override;
+  void SavedTabGroupUpdatedFromSync(
+      const base::GUID& group_guid,
+      const absl::optional<base::GUID>& tab_guid = absl::nullopt) override;
 
   // Calculates what the visible width would be when a restriction on width is
   // placed on the bar.
diff --git a/chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc b/chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc
index a0f7a9a5..e7ab662 100644
--- a/chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc
+++ b/chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc
@@ -136,6 +136,17 @@
     GetViewAccessibility().AnnounceText(alert_text);
   }
 
+  // When in progress, announce the progress immediately and start the timer for
+  // further updates.
+  if (state == download::DownloadItem::IN_PROGRESS &&
+      (initial_setup || state_ != state)) {
+    AnnounceInProgressAlert();
+    accessible_alert_in_progress_timer_.Reset();
+  } else if (!initial_setup && state_ == download::DownloadItem::IN_PROGRESS &&
+             state != state_) {
+    accessible_alert_in_progress_timer_.Stop();
+  }
+
   mode_ = mode;
   state_ = state;
   is_paused_ = is_paused;
@@ -285,7 +296,12 @@
       navigation_handler_(navigation_handler),
       browser_(browser),
       inkdrop_container_(
-          AddChildView(std::make_unique<views::InkDropContainerView>())) {
+          AddChildView(std::make_unique<views::InkDropContainerView>())),
+      accessible_alert_in_progress_timer_(
+          FROM_HERE,
+          base::Minutes(3),
+          base::BindRepeating(&DownloadBubbleRowView::AnnounceInProgressAlert,
+                              base::Unretained(this))) {
   model_->SetDelegate(this);
   SetBorder(views::CreateEmptyBorder(GetLayoutInsets(DOWNLOAD_ROW)));
 
@@ -569,8 +585,9 @@
 void DownloadBubbleRowView::OnMainButtonPressed() {
   if (ui_info_.has_subpage) {
     DownloadItemWarningData::AddWarningActionEvent(
-        model_->GetDownloadItem(), DownloadItemWarningData::BUBBLE_MAINPAGE,
-        DownloadItemWarningData::OPEN_SUBPAGE);
+        model_->GetDownloadItem(),
+        DownloadItemWarningData::WarningSurface::BUBBLE_MAINPAGE,
+        DownloadItemWarningData::WarningAction::OPEN_SUBPAGE);
     navigation_handler_->OpenSecurityDialog(this);
   } else {
     RecordDownloadOpenButtonPressed(model_->IsDone());
@@ -677,8 +694,9 @@
 void DownloadBubbleRowView::RecordDownloadDisplayed() {
   if (model_->IsDangerous()) {
     DownloadItemWarningData::AddWarningActionEvent(
-        model_->GetDownloadItem(), DownloadItemWarningData::BUBBLE_MAINPAGE,
-        DownloadItemWarningData::SHOWN);
+        model_->GetDownloadItem(),
+        DownloadItemWarningData::WarningSurface::BUBBLE_MAINPAGE,
+        DownloadItemWarningData::WarningAction::SHOWN);
   }
   if (!model_->GetEphemeralWarningUiShownTime().has_value() &&
       model_->IsEphemeralWarning()) {
@@ -821,5 +839,10 @@
                      base::RepeatingClosure());
 }
 
+void DownloadBubbleRowView::AnnounceInProgressAlert() {
+  GetViewAccessibility().AnnounceText(
+      model_->GetInProgressAccessibleAlertText());
+}
+
 BEGIN_METADATA(DownloadBubbleRowView, views::View)
 END_METADATA
diff --git a/chrome/browser/ui/views/download/bubble/download_bubble_row_view.h b/chrome/browser/ui/views/download/bubble/download_bubble_row_view.h
index c137677..c9b94589 100644
--- a/chrome/browser/ui/views/download/bubble/download_bubble_row_view.h
+++ b/chrome/browser/ui/views/download/bubble/download_bubble_row_view.h
@@ -127,6 +127,8 @@
   void OnDiscardButtonPressed();
   void OnMainButtonPressed();
 
+  void AnnounceInProgressAlert();
+
   // The icon for the file. We get platform-specific icons from IconLoader.
   raw_ptr<views::ImageView> icon_ = nullptr;
   raw_ptr<views::ImageView> subpage_icon_ = nullptr;
@@ -210,6 +212,9 @@
   // avoid inaccurate repeated logging.
   bool has_download_completion_been_logged_ = false;
 
+  // A timer for accessible alerts of progress updates
+  base::RepeatingTimer accessible_alert_in_progress_timer_;
+
   base::WeakPtrFactory<DownloadBubbleRowView> weak_factory_{this};
 };
 
diff --git a/chrome/browser/ui/views/download/bubble/download_bubble_security_view.cc b/chrome/browser/ui/views/download/bubble/download_bubble_security_view.cc
index a97ace7..ab7c9a0 100644
--- a/chrome/browser/ui/views/download/bubble/download_bubble_security_view.cc
+++ b/chrome/browser/ui/views/download/bubble/download_bubble_security_view.cc
@@ -99,7 +99,8 @@
 void DownloadBubbleSecurityView::BackButtonPressed() {
   DownloadItemWarningData::AddWarningActionEvent(
       download_row_view_->model()->GetDownloadItem(),
-      DownloadItemWarningData::BUBBLE_SUBPAGE, DownloadItemWarningData::BACK);
+      DownloadItemWarningData::WarningSurface::BUBBLE_SUBPAGE,
+      DownloadItemWarningData::WarningAction::BACK);
   navigation_handler_->OpenPrimaryDialog();
   base::UmaHistogramEnumeration(
       kSubpageActionHistogram, DownloadBubbleSubpageAction::kPressedBackButton);
@@ -114,7 +115,8 @@
 void DownloadBubbleSecurityView::CloseBubble() {
   DownloadItemWarningData::AddWarningActionEvent(
       download_row_view_->model()->GetDownloadItem(),
-      DownloadItemWarningData::BUBBLE_SUBPAGE, DownloadItemWarningData::CLOSE);
+      DownloadItemWarningData::WarningSurface::BUBBLE_SUBPAGE,
+      DownloadItemWarningData::WarningAction::CLOSE);
   // CloseDialog will delete the object. Do not access any members below.
   navigation_handler_->CloseDialog(
       views::Widget::ClosedReason::kCloseButtonClicked);
diff --git a/chrome/browser/ui/views/download/download_item_view.cc b/chrome/browser/ui/views/download/download_item_view.cc
index 75a714d2..1aece888 100644
--- a/chrome/browser/ui/views/download/download_item_view.cc
+++ b/chrome/browser/ui/views/download/download_item_view.cc
@@ -948,33 +948,7 @@
   if (model_->GetOpenWhenComplete() || has_warning_label(mode_))
     return accessible_name_;
 
-  // Prefer to announce the time remaining, if known.
-  base::TimeDelta remaining;
-  if (model_->TimeRemaining(&remaining)) {
-    // If complete, skip this round: a completion status update is coming soon.
-    if (remaining.is_zero())
-      return std::u16string();
-
-    const std::u16string remaining_string =
-        ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_REMAINING,
-                               ui::TimeFormat::LENGTH_SHORT, remaining);
-    return l10n_util::GetStringFUTF16(
-        IDS_DOWNLOAD_STATUS_TIME_REMAINING_ACCESSIBLE_ALERT, remaining_string);
-  }
-
-  // Time remaining is unknown, try to announce percent remaining.
-  if (model_->PercentComplete() > 0) {
-    DCHECK_LE(model_->PercentComplete(), 100);
-    return l10n_util::GetStringFUTF16Int(
-        IDS_DOWNLOAD_STATUS_PERCENT_COMPLETE_ACCESSIBLE_ALERT,
-        100 - model_->PercentComplete());
-  }
-
-  // Percent remaining is also unknown, announce bytes to download.
-  return l10n_util::GetStringFUTF16(
-      IDS_DOWNLOAD_STATUS_IN_PROGRESS_ACCESSIBLE_ALERT,
-      ui::FormatBytes(model_->GetTotalBytes()),
-      model_->GetFileNameToReportUser().LossyDisplayName());
+  return model_->GetInProgressAccessibleAlertText();
 }
 
 void DownloadItemView::AnnounceAccessibleAlert() {
diff --git a/chrome/browser/ui/views/global_media_controls/media_dialog_view_interactive_browsertest.cc b/chrome/browser/ui/views/global_media_controls/media_dialog_view_interactive_browsertest.cc
index ddd9301..fba0a13 100644
--- a/chrome/browser/ui/views/global_media_controls/media_dialog_view_interactive_browsertest.cc
+++ b/chrome/browser/ui/views/global_media_controls/media_dialog_view_interactive_browsertest.cc
@@ -316,24 +316,13 @@
     return logger_.get();
   }
 
-  void RegisterMediaRoutesObserver(
-      media_router::MediaRoutesObserver* observer) override {
-    routes_observers_.push_back(observer);
-  }
-
-  void UnregisterMediaRoutesObserver(
-      media_router::MediaRoutesObserver* observer) override {
-    base::Erase(routes_observers_, observer);
-  }
-
   void NotifyMediaRoutesChanged(
       const std::vector<media_router::MediaRoute>& routes) {
-    for (auto* observer : routes_observers_)
-      observer->OnRoutesUpdated(routes);
+    for (auto& observer : routes_observers_)
+      observer.OnRoutesUpdated(routes);
   }
 
  private:
-  std::vector<media_router::MediaRoutesObserver*> routes_observers_;
   std::unique_ptr<media_router::LoggerImpl> logger_;
 };
 
diff --git a/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc b/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc
index f2ba822..4eec2f5c 100644
--- a/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc
+++ b/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc
@@ -269,6 +269,9 @@
 
     test_ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
     SetUpLookalikeTestParams();
+    // Check that the test top domain list contains google.
+    ASSERT_TRUE(IsTopDomain(GetDomainInfo("google.com")));
+
     InProcessBrowserTest::SetUpOnMainThread();
   }
 
@@ -567,6 +570,10 @@
   NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
   EXPECT_FALSE(IsUIShowing());
   ASSERT_NO_FATAL_FAILURE(CheckPageInfoDoesNotShowSafetyTipInfo(browser()));
+
+  // TODO(crbug.com/1401102): Only one UKM should have been recorded, but
+  // allowlisted domain also records one.
+  CheckRecordedHeuristicsUkmCount(2);
 }
 
 // Ensure sites allowed by enterprise policy don't get blocked.
@@ -584,11 +591,15 @@
     EXPECT_FALSE(IsUIShowing());
     ASSERT_NO_FATAL_FAILURE(CheckPageInfoDoesNotShowSafetyTipInfo(browser()));
   }
+
+  // TODO(crbug.com/1401102): This shouldn't record a UKM.
+  CheckRecordedHeuristicsUkmCount(2);
 }
 
 // After the user clicks 'leave site', the user should end up on a safe domain.
 IN_PROC_BROWSER_TEST_F(SafetyTipPageInfoBubbleViewBrowserTest,
                        LeaveSiteLeavesSite) {
+  ASSERT_TRUE(IsTopDomain(GetDomainInfo("google.sk")));
   // This domain is a lookalike of a top domain not in the top 500.
   const GURL kNavigatedUrl = GetURL("googlé.sk");
   SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
@@ -753,6 +764,7 @@
                      browser()->tab_strip_model()->active_index() + 1);
   EXPECT_FALSE(IsUIShowing());
   ASSERT_NO_FATAL_FAILURE(CheckPageInfoDoesNotShowSafetyTipInfo(browser()));
+  CheckRecordedHeuristicsUkmCount(0);
 }
 
 // Tests that Safety Tips do NOT trigger on lookalike domains that trigger an
@@ -763,12 +775,14 @@
   SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
   NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
   EXPECT_FALSE(IsUIShowing());
+  CheckRecordedHeuristicsUkmCount(0);
 }
 
 // Tests that Safety Tips trigger on lookalike domains that don't qualify for an
-// interstitial, but do not impact Page Info.
+// interstitial and Page Info shows Safety Tip information.
 IN_PROC_BROWSER_TEST_F(SafetyTipPageInfoBubbleViewBrowserTest,
                        TriggersOnLookalike) {
+  ASSERT_TRUE(IsTopDomain(GetDomainInfo("google.sk")));
   // This domain is a lookalike of a top domain not in the top 500.
   const GURL kNavigatedUrl = GetURL("googlé.sk");
   SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
@@ -778,12 +792,14 @@
   ASSERT_NO_FATAL_FAILURE(CheckPageInfoShowsSafetyTipInfo(
       browser(), security_state::SafetyTipStatus::kLookalike,
       GURL("https://google.sk")));
+  CheckRecordedHeuristicsUkmCount(0);
 }
 
 // Tests that Safety Tips don't trigger on lookalike domains that are explicitly
 // allowed by the allowlist.
 IN_PROC_BROWSER_TEST_F(SafetyTipPageInfoBubbleViewBrowserTest,
                        NoTriggersOnLookalikeAllowlist) {
+  ASSERT_TRUE(IsTopDomain(GetDomainInfo("google.sk")));
   // This domain is a lookalike of a top domain not in the top 500.
   const GURL kNavigatedUrl = GetURL("googlé.sk");
 
@@ -802,6 +818,10 @@
 
   EXPECT_FALSE(IsUIShowing());
   ASSERT_NO_FATAL_FAILURE(CheckPageInfoDoesNotShowSafetyTipInfo(browser()));
+
+  // TODO(crbug.com/1401102): Only one UKM should have been recorded, but
+  // allowlisted domain also records one.
+  CheckRecordedHeuristicsUkmCount(2);
 }
 
 // Tests that Safety Tips don't trigger on lookalike domains that are explicitly
@@ -833,6 +853,10 @@
   SetEngagementScore(browser(), kTargetUrl, kHighEngagement);
   NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
   EXPECT_TRUE(IsUIShowing());
+  CheckRecordedHeuristicsUkmCount(0);
+
+  CloseWarningLeaveSite(browser());
+  CheckRecordedHeuristicsUkmCount(1);
 }
 
 // Tests that Safety Tips don't trigger when using a scoped allowlist.
@@ -847,6 +871,9 @@
   NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
   EXPECT_FALSE(IsUIShowing());
   ASSERT_NO_FATAL_FAILURE(CheckPageInfoDoesNotShowSafetyTipInfo(browser()));
+
+  // TODO(crbug.com/1401102): This shouldn't record metrics.
+  CheckRecordedHeuristicsUkmCount(1);
 }
 
 // Tests that Safety Tips trigger when the URL is on the allowlist, but is
@@ -861,6 +888,10 @@
   SetEngagementScore(browser(), kTargetUrl, kHighEngagement);
   NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
   EXPECT_TRUE(IsUIShowing());
+  CheckRecordedHeuristicsUkmCount(0);
+
+  CloseWarningLeaveSite(browser());
+  CheckRecordedHeuristicsUkmCount(1);
 }
 
 // Tests that Character Swap is enabled for lookalikes matching engaged sites.
@@ -872,6 +903,10 @@
   SetEngagementScore(browser(), kTargetUrl, kHighEngagement);
   NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
   EXPECT_TRUE(IsUIShowing());
+  CheckRecordedHeuristicsUkmCount(0);
+
+  CloseWarningLeaveSite(browser());
+  CheckRecordedHeuristicsUkmCount(1);
 }
 
 // Same as TriggersOnCharacterSwap_SiteEngagement, but this time
@@ -886,11 +921,17 @@
   SetEngagementScore(browser(), kTargetUrl, kHighEngagement);
   NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
   EXPECT_TRUE(IsUIShowing());
+  CheckRecordedHeuristicsUkmCount(0);
+
+  CloseWarningLeaveSite(browser());
+  CheckRecordedHeuristicsUkmCount(1);
 }
 
 // Tests that Character Swap is enabled for lookalikes matching top sites.
 IN_PROC_BROWSER_TEST_F(SafetyTipPageInfoBubbleViewBrowserTest,
                        TriggersOnCharacterSwap_TopSite) {
+  base::HistogramTester histograms;
+
   const GURL kNavigatedUrl = GetURL("goolge.com");
   const GURL kTargetUrl = GetURL("google.com");
   // Both the lookalike and the target have low engagement.
@@ -898,18 +939,28 @@
   SetEngagementScore(browser(), kTargetUrl, kLowEngagement);
   NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
   EXPECT_TRUE(IsUIShowing());
+  CheckRecordedHeuristicsUkmCount(0);
+
+  CloseWarningLeaveSite(browser());
+  CheckRecordedHeuristicsUkmCount(1);
 }
 
 // Tests that a hostname on a safe TLD can spoof another hostname without a
 // lookalike warning.
 IN_PROC_BROWSER_TEST_F(SafetyTipPageInfoBubbleViewBrowserTest,
                        TriggersOnCharacterSwapSafeTLD_CanSpoof) {
+  base::HistogramTester histograms;
+
   const GURL kNavigatedUrl = GetURL("digitla.gov");
   const GURL kTargetUrl = GetURL("digital.gov");
   SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
   SetEngagementScore(browser(), kTargetUrl, kHighEngagement);
   NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
   EXPECT_FALSE(IsUIShowing());
+
+  histograms.ExpectTotalCount(lookalikes::kHistogramName, 0);
+  // TODO(crbug.com/1401102): This shouldn't record metrics.
+  CheckRecordedHeuristicsUkmCount(1);
 }
 
 // Navigate to a domain within a character swap of 1 to a top domain,
@@ -918,6 +969,8 @@
 IN_PROC_BROWSER_TEST_F(
     SafetyTipPageInfoBubbleViewBrowserTest,
     DoesntTriggerOnCharacterSwap_TopSiteWithDifferentRegistry) {
+  ASSERT_TRUE(IsTopDomain(GetDomainInfo("google.rs")));
+
   base::HistogramTester histograms;
   // google.sr is within one character swap of google.rs which is a top domain.
   const GURL kNavigatedUrl = GetURL("google.sr");
@@ -925,11 +978,11 @@
   // considered for lookalike suggestions.
   SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
 
-  // TestInterstitialNotShown(browser(), kNavigatedUrl);
-  // histograms.ExpectTotalCount(lookalikes::kHistogramName, 0);
-  // CheckNoUkm();
   NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
   EXPECT_FALSE(IsUIShowing());
+
+  histograms.ExpectTotalCount(lookalikes::kHistogramName, 0);
+  CheckRecordedHeuristicsUkmCount(0);
 }
 
 // Tests that Safety Tips trigger on lookalike domains with tail embedding when
@@ -1009,6 +1062,7 @@
   // the safety tip.
   histogram_tester.ExpectTotalCount(
       GetInteractionHistogram("SafetyTip_Lookalike"), 0);
+
   CloseWarningLeaveSite(browser());
   histogram_tester.ExpectUniqueSample(
       GetInteractionHistogram("SafetyTip_Lookalike"),
diff --git a/chrome/browser/ui/views/toolbar/toolbar_view.cc b/chrome/browser/ui/views/toolbar/toolbar_view.cc
index ee4e67a..dbe08461 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_view.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_view.cc
@@ -791,13 +791,13 @@
     case AppMenuIconController::Severity::NONE:
       return GetColorProvider()->GetColor(kColorToolbarButtonIcon);
     case AppMenuIconController::Severity::LOW:
-      color_id = ui::kColorAlertLowSeverity;
+      color_id = kColorAppMenuHighlightSeverityLow;
       break;
     case AppMenuIconController::Severity::MEDIUM:
-      color_id = ui::kColorAlertMediumSeverity;
+      color_id = kColorAppMenuHighlightSeverityMedium;
       break;
     case AppMenuIconController::Severity::HIGH:
-      color_id = ui::kColorAlertHighSeverity;
+      color_id = kColorAppMenuHighlightSeverityHigh;
       break;
   }
   return GetColorProvider()->GetColor(color_id);
diff --git a/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc b/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc
index 4b5068c..8963382 100644
--- a/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc
@@ -1189,7 +1189,9 @@
     FilesOptions files_options,
     AllowDenyOptions allow_deny,
     AskAgainOptions ask_again) {
-  BeforeStateChangeAction(__FUNCTION__);
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
+    return;
+  }
   AppId app_id = GetAppIdBySiteMode(site);
   views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{},
                                        "FileHandlerLaunchDialogView");
@@ -1229,7 +1231,9 @@
 void WebAppIntegrationTestDriver::LaunchFileExpectNoDialog(
     Site site,
     FilesOptions files_options) {
-  BeforeStateChangeAction(__FUNCTION__);
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
+    return;
+  }
   AppId app_id = GetAppIdBySiteMode(site);
   base::RunLoop run_loop;
 #if BUILDFLAG(IS_MAC)
@@ -1787,7 +1791,9 @@
 }
 
 void WebAppIntegrationTestDriver::SwitchIncognitoProfile() {
-  BeforeStateChangeAction(__FUNCTION__);
+  if (!BeforeStateChangeAction(__FUNCTION__)) {
+    return;
+  }
   content::WebContentsAddedObserver nav_observer;
   CHECK(chrome::ExecuteCommand(browser(), IDC_NEW_INCOGNITO_WINDOW));
   ASSERT_EQ(1U, BrowserList::GetIncognitoBrowserCount());
@@ -2069,7 +2075,9 @@
 }
 
 void WebAppIntegrationTestDriver::CheckAppInListIconCorrect(Site site) {
-  BeforeStateCheckAction(__FUNCTION__);
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
+    return;
+  }
   GURL icon_url =
       apps::AppIconSource::GetIconURL(active_app_id_, icon_size::k128);
   SkBitmap icon_bitmap;
@@ -2307,13 +2315,17 @@
 }
 
 void WebAppIntegrationTestDriver::CheckCreateShortcutNotShown() {
-  BeforeStateCheckAction(__FUNCTION__);
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
+    return;
+  }
   EXPECT_EQ(GetAppMenuCommandState(IDC_CREATE_SHORTCUT, browser()), kDisabled);
   AfterStateCheckAction();
 }
 
 void WebAppIntegrationTestDriver::CheckCreateShortcutShown() {
-  BeforeStateCheckAction(__FUNCTION__);
+  if (!BeforeStateCheckAction(__FUNCTION__)) {
+    return;
+  }
   EXPECT_EQ(GetAppMenuCommandState(IDC_CREATE_SHORTCUT, browser()), kEnabled);
   AfterStateCheckAction();
 }
diff --git a/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.h b/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.h
index 169b9e6..1f7759c 100644
--- a/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.h
+++ b/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.h
@@ -337,12 +337,12 @@
  private:
   // Must be called at the beginning of every state change action function.
   // Returns if the test should continue.
-  bool BeforeStateChangeAction(const char* function);
+  [[nodiscard]] bool BeforeStateChangeAction(const char* function);
   // Must be called at the end of every state change action function.
   void AfterStateChangeAction();
   // Must be called at the beginning of every state check action function.
   // Returns if the test should continue.
-  bool BeforeStateCheckAction(const char* function);
+  [[nodiscard]] bool BeforeStateCheckAction(const char* function);
   // Must be called at the end of every state check action function.
   void AfterStateCheckAction();
 
diff --git a/chrome/browser/ui/views/webauthn/sheet_view_factory.cc b/chrome/browser/ui/views/webauthn/sheet_view_factory.cc
index 48241ec..2e31c04 100644
--- a/chrome/browser/ui/views/webauthn/sheet_view_factory.cc
+++ b/chrome/browser/ui/views/webauthn/sheet_view_factory.cc
@@ -136,6 +136,10 @@
           std::make_unique<AuthenticatorNoAvailableTransportsErrorModel>(
               dialog_model));
       break;
+    case Step::kErrorNoPasskeys:
+      sheet_view = std::make_unique<AuthenticatorRequestSheetView>(
+          std::make_unique<AuthenticatorNoPasskeysErrorModel>(dialog_model));
+      break;
     case Step::kTimedOut:
       sheet_view = std::make_unique<AuthenticatorRequestSheetView>(
           std::make_unique<AuthenticatorTimeoutErrorModel>(dialog_model));
diff --git a/chrome/browser/ui/webauthn/sheet_models.cc b/chrome/browser/ui/webauthn/sheet_models.cc
index 953b0af..03961d46 100644
--- a/chrome/browser/ui/webauthn/sheet_models.cc
+++ b/chrome/browser/ui/webauthn/sheet_models.cc
@@ -337,6 +337,36 @@
       IDS_WEBAUTHN_ERROR_NO_TRANSPORTS_DESCRIPTION);
 }
 
+// AuthenticatorNoPasskeysErrorModel ------------------------------------------
+
+bool AuthenticatorNoPasskeysErrorModel::IsBackButtonVisible() const {
+  return false;
+}
+
+std::u16string AuthenticatorNoPasskeysErrorModel::GetCancelButtonLabel() const {
+  return l10n_util::GetStringUTF16(IDS_CLOSE);
+}
+
+const gfx::VectorIcon& AuthenticatorNoPasskeysErrorModel::GetStepIllustration(
+    ImageColorScheme color_scheme) const {
+  if (base::FeatureList::IsEnabled(
+          device::kWebAuthnNewDiscoverableCredentialsUi)) {
+    return color_scheme == ImageColorScheme::kDark ? kPasskeyErrorDarkIcon
+                                                   : kPasskeyErrorIcon;
+  }
+  return color_scheme == ImageColorScheme::kDark ? kWebauthnErrorDarkIcon
+                                                 : kWebauthnErrorIcon;
+}
+
+std::u16string AuthenticatorNoPasskeysErrorModel::GetStepTitle() const {
+  return l10n_util::GetStringUTF16(IDS_WEBAUTHN_ERROR_NO_PASSKEYS_TITLE);
+}
+
+std::u16string AuthenticatorNoPasskeysErrorModel::GetStepDescription() const {
+  return l10n_util::GetStringFUTF16(IDS_WEBAUTHN_ERROR_NO_PASSKEYS_DESCRIPTION,
+                                    GetRelyingPartyIdString(dialog_model()));
+}
+
 // AuthenticatorNotRegisteredErrorModel ---------------------------------------
 
 bool AuthenticatorNotRegisteredErrorModel::IsBackButtonVisible() const {
diff --git a/chrome/browser/ui/webauthn/sheet_models.h b/chrome/browser/ui/webauthn/sheet_models.h
index a458e7f..8373c89 100644
--- a/chrome/browser/ui/webauthn/sheet_models.h
+++ b/chrome/browser/ui/webauthn/sheet_models.h
@@ -152,6 +152,20 @@
   std::u16string GetStepDescription() const override;
 };
 
+class AuthenticatorNoPasskeysErrorModel : public AuthenticatorSheetModelBase {
+ public:
+  using AuthenticatorSheetModelBase::AuthenticatorSheetModelBase;
+
+ private:
+  // AuthenticatorSheetModelBase:
+  bool IsBackButtonVisible() const override;
+  std::u16string GetCancelButtonLabel() const override;
+  const gfx::VectorIcon& GetStepIllustration(
+      ImageColorScheme color_scheme) const override;
+  std::u16string GetStepTitle() const override;
+  std::u16string GetStepDescription() const override;
+};
+
 class AuthenticatorNotRegisteredErrorModel
     : public AuthenticatorSheetModelBase {
  public:
diff --git a/chrome/browser/ui/webui/app_home/app_home_page_handler_browsertest.cc b/chrome/browser/ui/webui/app_home/app_home_page_handler_browsertest.cc
index e52dad0..01283fb9 100644
--- a/chrome/browser/ui/webui/app_home/app_home_page_handler_browsertest.cc
+++ b/chrome/browser/ui/webui/app_home/app_home_page_handler_browsertest.cc
@@ -212,7 +212,7 @@
     scoped_refptr<extensions::Extension> extension =
         extensions::Extension::Create(
             base::FilePath(), extensions::mojom::ManifestLocation::kUnpacked,
-            manifest, 0, &error);
+            manifest.GetDict(), 0, &error);
 
     extension_service()->AddExtension(extension.get());
     return extension;
diff --git a/chrome/browser/ui/webui/ash/lock_screen_reauth/lock_screen_network_ui.cc b/chrome/browser/ui/webui/ash/lock_screen_reauth/lock_screen_network_ui.cc
index 5c404fd..a6afd58 100644
--- a/chrome/browser/ui/webui/ash/lock_screen_reauth/lock_screen_network_ui.cc
+++ b/chrome/browser/ui/webui/ash/lock_screen_reauth/lock_screen_network_ui.cc
@@ -72,7 +72,7 @@
   content::WebUIDataSource* html =
       content::WebUIDataSource::Create(chrome::kChromeUILockScreenNetworkHost);
 
-  // TODO(crbug.com/1098690): Trusted Type Polymer.
+  // TODO(crbug.com/1400799): Enable TrustedTypes
   html->DisableTrustedTypesCSP();
 
   html->AddLocalizedStrings(localized_strings);
diff --git a/chrome/browser/ui/webui/ash/lock_screen_reauth/lock_screen_start_reauth_ui.cc b/chrome/browser/ui/webui/ash/lock_screen_reauth/lock_screen_start_reauth_ui.cc
index 0f750dcd..15f75ca0 100644
--- a/chrome/browser/ui/webui/ash/lock_screen_reauth/lock_screen_start_reauth_ui.cc
+++ b/chrome/browser/ui/webui/ash/lock_screen_reauth/lock_screen_start_reauth_ui.cc
@@ -49,7 +49,7 @@
   web_ui->AddMessageHandler(std::move(main_handler));
   web_ui->AddMessageHandler(std::make_unique<MetricsHandler>());
 
-  // TODO(crbug.com/1098690): Trusted Type Polymer
+  // TODO(crbug.com/1400799): Enable TrustedTypes.
   source->DisableTrustedTypesCSP();
 
   source->EnableReplaceI18nInJS();
diff --git a/chrome/browser/ui/webui/chrome_url_data_manager_browsertest.cc b/chrome/browser/ui/webui/chrome_url_data_manager_browsertest.cc
index 9c930c56..e912fbd 100644
--- a/chrome/browser/ui/webui/chrome_url_data_manager_browsertest.cc
+++ b/chrome/browser/ui/webui/chrome_url_data_manager_browsertest.cc
@@ -189,15 +189,7 @@
 
     ASSERT_TRUE(embedded_test_server()->Start());
     ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(url)));
-
-    if (url == "chrome://network-error" || url == "chrome://dino") {
-      // We don't ASSERT_TRUE here because some WebUI pages are
-      // PAGE_TYPE_ERROR by design.
-      content::WaitForLoadStop(content);
-    } else {
-      ASSERT_TRUE(content::WaitForLoadStop(content));
-    }
-
+    ASSERT_TRUE(content::WaitForLoadStop(content));
     EXPECT_TRUE(console_observer.messages().empty());
   }
 
@@ -206,14 +198,7 @@
         browser()->tab_strip_model()->GetActiveWebContents();
     ASSERT_TRUE(embedded_test_server()->Start());
     ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(url)));
-
-    if (url == "chrome://network-error" || url == "chrome://dino") {
-      // We don't ASSERT_TRUE here because some WebUI pages are
-      // PAGE_TYPE_ERROR by design.
-      content::WaitForLoadStop(content);
-    } else {
-      ASSERT_TRUE(content::WaitForLoadStop(content));
-    }
+    ASSERT_TRUE(content::WaitForLoadStop(content));
 
     const char kIsTrustedTypesEnabled[] =
         "(function isTrustedTypesEnabled() {"
@@ -260,17 +245,11 @@
 // Non-exhaustive list of chrome:// URLs to test for
 //  1) TrustedTypes violations (see NoTrustedTypesViolation test).
 //  2) Presence of TrustedTypes checks (see TrustedTypesEnabled test).
-// This list was derived from chrome://about. :)
-//
-// TODO(crbug.com/1098690): For all commented out URLs below, uncomment after
-// enabling TrustedTypes, unless they are commented out for another reason
-// (like flaky failures).
 static constexpr const char* const kChromeUrls[] = {
     "chrome://accessibility",
     "chrome://app-service-internals",
     "chrome://attribution-internals",
     "chrome://autofill-internals",
-    // "chrome://blob-internals",
     "chrome://bookmarks",
     "chrome://bookmarks-side-panel.top-chrome",
     "chrome://chrome-urls",
@@ -281,10 +260,7 @@
     "chrome://crashes",
     "chrome://credits",
     "chrome://customize-chrome-side-panel.top-chrome",
-    // TODO(crbug/1396866): Enable when bug is fixed.
-    // "chrome://customize-chrome-side-panel.top-chrome",
     "chrome://device-log",
-    // "chrome://dino",
     // TODO(crbug.com/1113446): Test failure due to excessive output.
     // "chrome://discards",
     "chrome://download-internals",
@@ -316,7 +292,6 @@
     // "chrome://memory-internals",
     "chrome://net-export",
     "chrome://net-internals",
-    // "chrome://network-error",
     "chrome://network-errors",
     "chrome://new-tab-page",
     "chrome://new-tab-page-third-party",
@@ -354,7 +329,6 @@
     // "chrome://tab-strip",
     "chrome://terms",
     "chrome://topics-internals",
-    // "chrome://tracing",
     "chrome://translate-internals",
     "chrome://ukm",
     "chrome://usb-internals",
@@ -381,38 +355,23 @@
 #endif
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-    // "chrome://account-manager-error",
-    // "chrome://account-migration-welcome",
-    // "chrome://add-supervision",
+    // TODO(crbug.com/1400799): Add CrOS-only WebUI URLs here as TrustedTypes
+    // are deployed to more WebUIs.
+
     // TODO(crbug.com/1102129): DCHECK failure in
     // ArcGraphicsTracingHandler::ArcGraphicsTracingHandler.
     // "chrome://arc-graphics-tracing",
-    // "chrome://arc-overview-tracing",
-    // "chrome://assistant-optin",
-    // "chrome://bluetooth-pairing",
-    // "chrome://certificate-manager",
-    // "chrome://crostini-credits",
-    // "chrome://crostini-installer",
     "chrome://cryptohome",
     "chrome://drive-internals",
     "chrome://family-link-user-internals",
     "chrome://help-app",
-    // "chrome://internet-config-dialog",
-    // "chrome://internet-detail-dialog",
     "chrome://linux-proxy-config",
-    // "chrome://multidevice-setup",
-    // "chrome://network",
-    // "chrome://os-credits",
-    // "chrome://os-settings",
+    "chrome://multidevice-internals",
+    "chrome://nearby-internals",
     "chrome://power",
     "chrome://projector",
     "chrome://proximity-auth/proximity_auth.html",
-    // "chrome://set-time",
     "chrome://slow",
-    // "chrome://smb-credentials-dialog",
-    // "chrome://smb-share-dialog",
-    // "chrome://sys-internals",
-    // "chrome-untrusted://terminal",
 #endif
 #if !BUILDFLAG(IS_CHROMEOS)
     "chrome://apps",
diff --git a/chrome/browser/ui/webui/downloads/downloads_dom_handler.cc b/chrome/browser/ui/webui/downloads/downloads_dom_handler.cc
index 7cd951a..0d131dc 100644
--- a/chrome/browser/ui/webui/downloads/downloads_dom_handler.cc
+++ b/chrome/browser/ui/webui/downloads/downloads_dom_handler.cc
@@ -179,8 +179,8 @@
   download::DownloadItem* file = GetDownloadByStringId(id);
   if (file) {
     DownloadItemWarningData::AddWarningActionEvent(
-        file, DownloadItemWarningData::DOWNLOADS_PAGE,
-        DownloadItemWarningData::KEEP);
+        file, DownloadItemWarningData::WarningSurface::DOWNLOADS_PAGE,
+        DownloadItemWarningData::WarningAction::KEEP);
     ShowDangerPrompt(file);
   }
 }
@@ -192,8 +192,8 @@
     // The warning action event needs to be added before Safe Browsing report is
     // sent, because this event should be included in the report.
     DownloadItemWarningData::AddWarningActionEvent(
-        download, DownloadItemWarningData::DOWNLOADS_PAGE,
-        DownloadItemWarningData::DISCARD);
+        download, DownloadItemWarningData::WarningSurface::DOWNLOADS_PAGE,
+        DownloadItemWarningData::WarningAction::DISCARD);
     // If this download is no longer dangerous, is already canceled or
     // completed, don't send any report.
     // Only sends dangerous download discard report if :
diff --git a/chrome/browser/ui/webui/management/management_ui_browsertest.cc b/chrome/browser/ui/webui/management/management_ui_browsertest.cc
index f109a589..be8dbab 100644
--- a/chrome/browser/ui/webui/management/management_ui_browsertest.cc
+++ b/chrome/browser/ui/webui/management/management_ui_browsertest.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "base/json/json_reader.h"
+#include "base/strings/escape.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/policy/chrome_browser_policy_connector.h"
@@ -84,7 +85,9 @@
       {"browserManagementNotice",
        l10n_util::GetStringFUTF16(
            IDS_MANAGEMENT_NOT_MANAGED_NOTICE,
-           base::UTF8ToUTF16(chrome::kManagedUiLearnMoreUrl))},
+           base::UTF8ToUTF16(chrome::kManagedUiLearnMoreUrl),
+           base::EscapeForHTML(l10n_util::GetStringUTF16(
+               IDS_MANAGEMENT_LEARN_MORE_ACCCESSIBILITY_TEXT)))},
       {"extensionReportingTitle",
        l10n_util::GetStringUTF16(IDS_MANAGEMENT_EXTENSIONS_INSTALLED)},
       {"pageSubtitle",
@@ -117,7 +120,9 @@
       {"browserManagementNotice",
        l10n_util::GetStringFUTF16(
            IDS_MANAGEMENT_BROWSER_NOTICE,
-           base::UTF8ToUTF16(chrome::kManagedUiLearnMoreUrl))},
+           base::UTF8ToUTF16(chrome::kManagedUiLearnMoreUrl),
+           base::EscapeForHTML(l10n_util::GetStringUTF16(
+               IDS_MANAGEMENT_LEARN_MORE_ACCCESSIBILITY_TEXT)))},
       {"extensionReportingTitle",
        l10n_util::GetStringUTF16(IDS_MANAGEMENT_EXTENSIONS_INSTALLED)},
       {"pageSubtitle", l10n_util::GetStringUTF16(IDS_MANAGEMENT_SUBTITLE)},
diff --git a/chrome/browser/ui/webui/management/management_ui_handler.cc b/chrome/browser/ui/webui/management/management_ui_handler.cc
index f611708..f8ed735 100644
--- a/chrome/browser/ui/webui/management/management_ui_handler.cc
+++ b/chrome/browser/ui/webui/management/management_ui_handler.cc
@@ -18,6 +18,7 @@
 #include "base/feature_list.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/user_metrics.h"
+#include "base/strings/escape.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
@@ -841,7 +842,9 @@
                l10n_util::GetStringFUTF16(
                    managed_() ? IDS_MANAGEMENT_BROWSER_NOTICE
                               : IDS_MANAGEMENT_NOT_MANAGED_NOTICE,
-                   base::UTF8ToUTF16(chrome::kManagedUiLearnMoreUrl)));
+                   base::UTF8ToUTF16(chrome::kManagedUiLearnMoreUrl),
+                   base::EscapeForHTML(l10n_util::GetStringUTF16(
+                       IDS_MANAGEMENT_LEARN_MORE_ACCCESSIBILITY_TEXT))));
 #endif
 
   if (enterprise_manager.empty()) {
diff --git a/chrome/browser/ui/webui/management/management_ui_handler_unittest.cc b/chrome/browser/ui/webui/management/management_ui_handler_unittest.cc
index 2c45f3b..378817b 100644
--- a/chrome/browser/ui/webui/management/management_ui_handler_unittest.cc
+++ b/chrome/browser/ui/webui/management/management_ui_handler_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/json/json_reader.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/strings/escape.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/single_thread_task_runner.h"
 #include "build/chromeos_buildflags.h"
@@ -714,7 +715,9 @@
   EXPECT_EQ(GetBrowserManagementNotice(),
             l10n_util::GetStringFUTF16(
                 IDS_MANAGEMENT_NOT_MANAGED_NOTICE,
-                base::UTF8ToUTF16(chrome::kManagedUiLearnMoreUrl)));
+                base::UTF8ToUTF16(chrome::kManagedUiLearnMoreUrl),
+                base::EscapeForHTML(l10n_util::GetStringUTF16(
+                    IDS_MANAGEMENT_LEARN_MORE_ACCCESSIBILITY_TEXT))));
   EXPECT_EQ(GetPageSubtitle(),
             l10n_util::GetStringUTF16(IDS_MANAGEMENT_NOT_MANAGED_SUBTITLE));
 }
@@ -732,7 +735,9 @@
   EXPECT_EQ(GetBrowserManagementNotice(),
             l10n_util::GetStringFUTF16(
                 IDS_MANAGEMENT_BROWSER_NOTICE,
-                base::UTF8ToUTF16(chrome::kManagedUiLearnMoreUrl)));
+                base::UTF8ToUTF16(chrome::kManagedUiLearnMoreUrl),
+                base::EscapeForHTML(l10n_util::GetStringUTF16(
+                    IDS_MANAGEMENT_LEARN_MORE_ACCCESSIBILITY_TEXT))));
   EXPECT_EQ(GetPageSubtitle(),
             l10n_util::GetStringUTF16(IDS_MANAGEMENT_SUBTITLE));
   EXPECT_TRUE(GetManaged());
@@ -752,7 +757,9 @@
   EXPECT_EQ(GetBrowserManagementNotice(),
             l10n_util::GetStringFUTF16(
                 IDS_MANAGEMENT_BROWSER_NOTICE,
-                base::UTF8ToUTF16(chrome::kManagedUiLearnMoreUrl)));
+                base::UTF8ToUTF16(chrome::kManagedUiLearnMoreUrl),
+                base::EscapeForHTML(l10n_util::GetStringUTF16(
+                    IDS_MANAGEMENT_LEARN_MORE_ACCCESSIBILITY_TEXT))));
   EXPECT_EQ(GetPageSubtitle(),
             l10n_util::GetStringUTF16(IDS_MANAGEMENT_SUBTITLE));
   EXPECT_TRUE(GetManaged());
@@ -777,7 +784,9 @@
   EXPECT_EQ(GetBrowserManagementNotice(),
             l10n_util::GetStringFUTF16(
                 IDS_MANAGEMENT_NOT_MANAGED_NOTICE,
-                base::UTF8ToUTF16(chrome::kManagedUiLearnMoreUrl)));
+                base::UTF8ToUTF16(chrome::kManagedUiLearnMoreUrl),
+                base::EscapeForHTML(l10n_util::GetStringUTF16(
+                    IDS_MANAGEMENT_LEARN_MORE_ACCCESSIBILITY_TEXT))));
   EXPECT_EQ(GetPageSubtitle(),
             l10n_util::GetStringUTF16(IDS_MANAGEMENT_NOT_MANAGED_SUBTITLE));
   EXPECT_FALSE(GetManaged());
@@ -797,7 +806,9 @@
   EXPECT_EQ(GetBrowserManagementNotice(),
             l10n_util::GetStringFUTF16(
                 IDS_MANAGEMENT_NOT_MANAGED_NOTICE,
-                base::UTF8ToUTF16(chrome::kManagedUiLearnMoreUrl)));
+                base::UTF8ToUTF16(chrome::kManagedUiLearnMoreUrl),
+                base::EscapeForHTML(l10n_util::GetStringUTF16(
+                    IDS_MANAGEMENT_LEARN_MORE_ACCCESSIBILITY_TEXT))));
   EXPECT_EQ(GetPageSubtitle(),
             l10n_util::GetStringUTF16(IDS_MANAGEMENT_NOT_MANAGED_SUBTITLE));
   EXPECT_FALSE(GetManaged());
@@ -821,7 +832,9 @@
   EXPECT_EQ(GetBrowserManagementNotice(),
             l10n_util::GetStringFUTF16(
                 IDS_MANAGEMENT_BROWSER_NOTICE,
-                base::UTF8ToUTF16(chrome::kManagedUiLearnMoreUrl)));
+                base::UTF8ToUTF16(chrome::kManagedUiLearnMoreUrl),
+                base::EscapeForHTML(l10n_util::GetStringUTF16(
+                    IDS_MANAGEMENT_LEARN_MORE_ACCCESSIBILITY_TEXT))));
   EXPECT_EQ(GetPageSubtitle(),
             l10n_util::GetStringFUTF16(IDS_MANAGEMENT_SUBTITLE_MANAGED_BY,
                                        base::UTF8ToUTF16(domain)));
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome.mojom b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome.mojom
index f28a707..183df02 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome.mojom
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome.mojom
@@ -11,6 +11,8 @@
 struct BackgroundImage {
   // URL to the background image. Can point to untrusted content.
   url.mojom.Url url;
+  // Whether the image is a local resource.
+  bool is_uploaded_image;
   // Title of the background image.
   string title;
 };
@@ -25,6 +27,8 @@
   skia.mojom.SkColor background_color;
   // The current theme foreground color. If not set, we use the default theme.
   skia.mojom.SkColor? foreground_color;
+  // The color of the color picker icon.
+  skia.mojom.SkColor color_picker_icon_color;
 };
 
 struct ChromeColor {
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.cc b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.cc
index 1733c816..9a9b5107 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.cc
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler.cc
@@ -103,6 +103,7 @@
   auto background_image = side_panel::mojom::BackgroundImage::New();
   if (custom_background.has_value()) {
     background_image->url = custom_background->custom_background_url;
+    background_image->is_uploaded_image = custom_background->is_uploaded_image;
     background_image->title =
         custom_background->custom_background_attribution_line_1;
   } else {
@@ -116,6 +117,8 @@
     theme->foreground_color =
         web_contents_->GetColorProvider().GetColor(ui::kColorFrameActive);
   }
+  theme->color_picker_icon_color =
+      web_contents_->GetColorProvider().GetColor(kColorNewTabPageText);
   auto* native_theme = ui::NativeTheme::GetInstanceForNativeUi();
   CHECK(native_theme);
   theme->system_dark_mode = native_theme->ShouldUseDarkColors();
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler_unittest.cc b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler_unittest.cc
index 7a2b33e..df9063e 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler_unittest.cc
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_page_handler_unittest.cc
@@ -270,6 +270,7 @@
   CustomBackground custom_background;
   custom_background.custom_background_url = GURL("https://foo.com/img.png");
   custom_background.custom_background_attribution_line_1 = "foo line";
+  custom_background.is_uploaded_image = false;
   ON_CALL(mock_ntp_custom_background_service_, GetCustomBackground())
       .WillByDefault(testing::Return(absl::make_optional(custom_background)));
   ON_CALL(mock_theme_service(), UsingDefaultTheme())
@@ -284,6 +285,7 @@
   ASSERT_TRUE(theme);
   ASSERT_TRUE(theme->background_image);
   EXPECT_EQ("https://foo.com/img.png", theme->background_image->url);
+  ASSERT_FALSE(theme->background_image->is_uploaded_image);
   EXPECT_EQ("foo line", theme->background_image->title);
   EXPECT_TRUE(theme->system_dark_mode);
   EXPECT_EQ(
@@ -291,6 +293,34 @@
       theme->background_color);
   EXPECT_EQ(web_contents().GetColorProvider().GetColor(ui::kColorFrameActive),
             theme->foreground_color);
+  EXPECT_EQ(web_contents().GetColorProvider().GetColor(kColorNewTabPageText),
+            theme->color_picker_icon_color);
+}
+
+TEST_P(CustomizeChromePageHandlerSetThemeTest, SetUploadedImage) {
+  side_panel::mojom::ThemePtr theme;
+  EXPECT_CALL(mock_page_, SetTheme)
+      .Times(1)
+      .WillOnce(testing::Invoke([&theme](side_panel::mojom::ThemePtr arg) {
+        theme = std::move(arg);
+      }));
+  CustomBackground custom_background;
+  custom_background.custom_background_url = GURL("https://foo.com/img.png");
+  custom_background.is_uploaded_image = true;
+  ON_CALL(mock_ntp_custom_background_service_, GetCustomBackground())
+      .WillByDefault(testing::Return(absl::make_optional(custom_background)));
+  ON_CALL(mock_theme_service(), UsingDefaultTheme())
+      .WillByDefault(testing::Return(false));
+  ON_CALL(mock_theme_service(), UsingSystemTheme())
+      .WillByDefault(testing::Return(false));
+
+  UpdateTheme();
+  mock_page_.FlushForTesting();
+
+  ASSERT_TRUE(theme);
+  ASSERT_TRUE(theme->background_image);
+  EXPECT_EQ("https://foo.com/img.png", theme->background_image->url);
+  ASSERT_TRUE(theme->background_image->is_uploaded_image);
 }
 
 INSTANTIATE_TEST_SUITE_P(All,
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.cc b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.cc
index 9d2b08b5..160e331c 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.cc
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.cc
@@ -30,6 +30,8 @@
 
   static constexpr webui::LocalizedString kLocalizedStrings[] = {
       {"classicChrome", IDS_NTP_CUSTOMIZE_NO_BACKGROUND_LABEL},
+      {"colorsContainerLabel", IDS_NTP_THEMES_CONTAINER_LABEL},
+      {"colorPickerLabel", IDS_NTP_CUSTOMIZE_COLOR_PICKER_LABEL},
       {"customizeThisPage", IDS_NTP_CUSTOM_BG_CUSTOMIZE_NTP_LABEL},
       {"appearanceHeader", IDS_NTP_CUSTOMIZE_APPEARANCE_LABEL},
       {"defaultColorName", IDS_NTP_CUSTOMIZE_DEFAULT_LABEL},
@@ -40,6 +42,7 @@
       {"shortcutsSuggested", IDS_NTP_CUSTOMIZE_MOST_VISITED_DESC},
       {"showShortcutsToggle", IDS_NTP_CUSTOMIZE_SHOW_SHORTCUTS_LABEL},
       {"title", IDS_SIDE_PANEL_CUSTOMIZE_CHROME_TITLE},
+      {"uploadedImage", IDS_NTP_CUSTOMIZE_UPLOADED_IMAGE_LABEL},
   };
   source->AddLocalizedStrings(kLocalizedStrings);
 
diff --git a/chrome/browser/ui/webui/web_app_internals/web_app_internals_source.cc b/chrome/browser/ui/webui/web_app_internals/web_app_internals_source.cc
index ceb3b878..da667a5 100644
--- a/chrome/browser/ui/webui/web_app_internals/web_app_internals_source.cc
+++ b/chrome/browser/ui/webui/web_app_internals/web_app_internals_source.cc
@@ -335,7 +335,6 @@
 #if BUILDFLAG(IS_MAC)
   root.Append(BuildAppShimRegistryLocalStorageJson());
 #endif
-  root.Append(BuildInstallProcessErrorLogJson(*provider));
   base::ThreadPool::PostTaskAndReplyWithResult(
       FROM_HERE, {base::TaskPriority::USER_VISIBLE, base::MayBlock()},
       base::BindOnce(&BuildWebAppDiskStateJson,
diff --git a/chrome/browser/web_applications/extensions/web_app_policy_manager_unittest.cc b/chrome/browser/web_applications/extensions/web_app_policy_manager_unittest.cc
index fe92068..54e98d5 100644
--- a/chrome/browser/web_applications/extensions/web_app_policy_manager_unittest.cc
+++ b/chrome/browser/web_applications/extensions/web_app_policy_manager_unittest.cc
@@ -537,6 +537,8 @@
   if (ShouldSkipPWASpecificTest())
     return;
 
+  base::RunLoop().RunUntilIdle();
+
   const auto& install_requests =
       externally_managed_app_manager().install_requests();
   EXPECT_TRUE(install_requests.empty());
@@ -918,7 +920,6 @@
 TEST_P(WebAppPolicyManagerTest, UninstallAppInstalledInPreviousSession) {
   if (ShouldSkipPWASpecificTest())
     return;
-
   // Simulate two policy apps and a regular app that were installed in the
   // previous session.
   SimulatePreviouslyInstalledApp(GURL(kWindowedUrl),
@@ -952,6 +953,7 @@
 TEST_P(WebAppPolicyManagerTest, UninstallAppInstalledInCurrentSession) {
   if (ShouldSkipPWASpecificTest())
     return;
+  base::RunLoop().RunUntilIdle();
 
   // Add two sites, one that opens in a window and one that opens in a tab.
   base::Value::List first_list;
@@ -1103,7 +1105,7 @@
 TEST_P(WebAppPolicyManagerTest, SayRefreshTwoTimesQuickly) {
   if (ShouldSkipPWASpecificTest())
     return;
-
+  base::RunLoop().RunUntilIdle();
   // Add an app.
   {
     base::Value::List list;
@@ -1118,17 +1120,7 @@
     profile()->GetPrefs()->SetList(prefs::kWebAppInstallForceList,
                                    std::move(list));
   }
-
-  // `OnAppsSynchronized` should be triggered twice.
-  base::RunLoop loop;
-  policy_manager().SetOnAppsSynchronizedCompletedCallbackForTesting(
-      loop.QuitClosure());
-  loop.Run();
-
-  base::RunLoop loop2;
-  policy_manager().SetOnAppsSynchronizedCompletedCallbackForTesting(
-      loop2.QuitClosure());
-  loop2.Run();
+  base::RunLoop().RunUntilIdle();
 
   // Both apps should have been installed.
   std::vector<ExternalInstallOptions> expected_options_list;
@@ -1193,6 +1185,8 @@
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 TEST_P(WebAppPolicyManagerTest, DisableSystemWebApps) {
+  base::RunLoop().RunUntilIdle();
+
   auto disabled_apps = policy_manager().GetDisabledSystemWebApps();
   EXPECT_TRUE(disabled_apps.empty());
 
diff --git a/chrome/browser/web_applications/externally_managed_app_manager.cc b/chrome/browser/web_applications/externally_managed_app_manager.cc
index f382c6b..5c2844c 100644
--- a/chrome/browser/web_applications/externally_managed_app_manager.cc
+++ b/chrome/browser/web_applications/externally_managed_app_manager.cc
@@ -15,8 +15,6 @@
 #include "base/stl_util.h"
 #include "base/task/single_thread_task_runner.h"
 #include "build/chromeos_buildflags.h"
-#include "chrome/browser/web_applications/locks/full_system_lock.h"
-#include "chrome/browser/web_applications/web_app_command_scheduler.h"
 #include "chrome/browser/web_applications/web_app_install_utils.h"
 #include "chrome/browser/web_applications/web_app_registrar.h"
 #include "components/webapps/browser/install_result_code.h"
@@ -90,6 +88,7 @@
     std::vector<ExternalInstallOptions> desired_apps_install_options,
     ExternalInstallSource install_source,
     SynchronizeCallback callback) {
+  DCHECK(registrar_);
   DCHECK(base::ranges::all_of(
       desired_apps_install_options,
       [&install_source](const ExternalInstallOptions& install_options) {
@@ -98,29 +97,14 @@
   // Only one concurrent SynchronizeInstalledApps() expected per
   // ExternalInstallSource.
   DCHECK(!base::Contains(synchronize_requests_, install_source));
-  command_scheduler_->ScheduleCallbackWithLock<FullSystemLock>(
-      "ExternallyManagedAppManager::SynchronizeInstalledApps",
-      std::make_unique<FullSystemLockDescription>(),
-      base::BindOnce(
-          &ExternallyManagedAppManager::SynchronizeInstalledAppsOnLockAcquired,
-          weak_ptr_factory_.GetWeakPtr(),
-          std::move(desired_apps_install_options), std::move(install_source),
-          std::move(callback)));
-}
 
-void ExternallyManagedAppManager::SynchronizeInstalledAppsOnLockAcquired(
-    std::vector<ExternalInstallOptions> desired_apps_install_options,
-    ExternalInstallSource install_source,
-    SynchronizeCallback callback,
-    FullSystemLock& lock) {
   std::vector<GURL> installed_urls;
   for (const auto& apps_it :
-       lock.registrar().GetExternallyInstalledApps(install_source)) {
+       registrar_->GetExternallyInstalledApps(install_source)) {
     // TODO(crbug.com/1339965): Remove this check once we cleanup
     // ExternallyInstalledWebAppPrefs on external app uninstall.
     bool has_same_external_source =
-        lock.registrar()
-            .GetAppById(apps_it.first)
+        registrar_->GetAppById(apps_it.first)
             ->GetSources()
             .test(ConvertExternalInstallSourceToSource(install_source));
     if (has_same_external_source) {
diff --git a/chrome/browser/web_applications/externally_managed_app_manager.h b/chrome/browser/web_applications/externally_managed_app_manager.h
index 8e15a41..1b7eb9d0 100644
--- a/chrome/browser/web_applications/externally_managed_app_manager.h
+++ b/chrome/browser/web_applications/externally_managed_app_manager.h
@@ -24,7 +24,6 @@
 
 namespace web_app {
 
-class FullSystemLock;
 class WebAppRegistrar;
 class WebAppInstallFinalizer;
 class WebAppCommandScheduler;
@@ -191,12 +190,6 @@
     std::map<GURL, bool> uninstall_results;
   };
 
-  void SynchronizeInstalledAppsOnLockAcquired(
-      std::vector<ExternalInstallOptions> desired_apps_install_options,
-      ExternalInstallSource install_source,
-      SynchronizeCallback callback,
-      FullSystemLock& lock);
-
   void InstallForSynchronizeCallback(
       ExternalInstallSource source,
       const GURL& install_url,
diff --git a/chrome/browser/web_applications/externally_managed_app_manager_impl.cc b/chrome/browser/web_applications/externally_managed_app_manager_impl.cc
index 2228de8..73a6064 100644
--- a/chrome/browser/web_applications/externally_managed_app_manager_impl.cc
+++ b/chrome/browser/web_applications/externally_managed_app_manager_impl.cc
@@ -12,12 +12,9 @@
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/feature_list.h"
-#include "base/functional/bind.h"
 #include "base/task/single_thread_task_runner.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/web_applications/externally_managed_app_manager.h"
 #include "chrome/browser/web_applications/externally_managed_app_registration_task.h"
-#include "chrome/browser/web_applications/locks/full_system_lock.h"
 #include "chrome/browser/web_applications/web_app.h"
 #include "chrome/browser/web_applications/web_app_command_scheduler.h"
 #include "chrome/browser/web_applications/web_app_install_finalizer.h"
@@ -161,19 +158,6 @@
 void ExternallyManagedAppManagerImpl::MaybeStartNext() {
   if (current_install_ || is_in_shutdown_)
     return;
-  command_scheduler()->ScheduleCallbackWithLock<FullSystemLock>(
-      "ExternallyManagedAppManagerImpl::MaybeStartNext",
-      std::make_unique<FullSystemLockDescription>(),
-      base::BindOnce(
-          &ExternallyManagedAppManagerImpl::MaybeStartNextOnLockAcquired,
-          weak_ptr_factory_.GetWeakPtr()));
-}
-
-void ExternallyManagedAppManagerImpl::MaybeStartNextOnLockAcquired(
-    FullSystemLock& lock) {
-  if (current_install_ || is_in_shutdown_) {
-    return;
-  }
 
   while (!pending_installs_.empty()) {
     std::unique_ptr<TaskAndCallback> front =
@@ -189,7 +173,7 @@
     }
 
     absl::optional<AppId> app_id =
-        lock.registrar().LookupExternalAppId(install_options.install_url);
+        registrar()->LookupExternalAppId(install_options.install_url);
 
     // If the URL is not in web_app registrar,
     // then no external source has installed it.
@@ -198,10 +182,10 @@
       return;
     }
 
-    if (lock.registrar().IsInstalled(app_id.value())) {
+    if (registrar()->IsInstalled(app_id.value())) {
       if (install_options.wait_for_windows_closed &&
-          lock.ui_manager().GetNumWindowsForApp(app_id.value()) != 0) {
-        lock.ui_manager().NotifyOnAllAppWindowsClosed(
+          ui_manager()->GetNumWindowsForApp(app_id.value()) != 0) {
+        ui_manager()->NotifyOnAllAppWindowsClosed(
             app_id.value(),
             base::BindOnce(&ExternallyManagedAppManagerImpl::Install,
                            weak_ptr_factory_.GetWeakPtr(), install_options,
@@ -212,9 +196,9 @@
       // If the app is already installed, only reinstall it if the app is a
       // placeholder app and the client asked for it to be reinstalled.
       if (install_options.reinstall_placeholder &&
-          lock.registrar().IsPlaceholderApp(
-              app_id.value(), ConvertExternalInstallSourceToSource(
-                                  install_options.install_source))) {
+          registrar()->IsPlaceholderApp(app_id.value(),
+                                        ConvertExternalInstallSourceToSource(
+                                            install_options.install_source))) {
         StartInstallationTask(std::move(front));
         return;
       }
@@ -227,7 +211,7 @@
         return;
       } else {
         // Add install source before returning the result.
-        ScopedRegistryUpdate update(&lock.sync_bridge());
+        ScopedRegistryUpdate update(sync_bridge());
         WebApp* app_to_update = update->UpdateApp(app_id.value());
         app_to_update->AddSource(ConvertExternalInstallSourceToSource(
             install_options.install_source));
diff --git a/chrome/browser/web_applications/externally_managed_app_manager_impl.h b/chrome/browser/web_applications/externally_managed_app_manager_impl.h
index 794125722..9fe18dd 100644
--- a/chrome/browser/web_applications/externally_managed_app_manager_impl.h
+++ b/chrome/browser/web_applications/externally_managed_app_manager_impl.h
@@ -27,7 +27,6 @@
 
 namespace web_app {
 
-class FullSystemLock;
 class ExternallyManagedAppRegistrationTaskBase;
 
 // Installs, uninstalls, and updates any External Web Apps. This class should
@@ -75,7 +74,6 @@
   void PostMaybeStartNext();
 
   void MaybeStartNext();
-  void MaybeStartNextOnLockAcquired(FullSystemLock& lock);
 
   void StartInstallationTask(std::unique_ptr<TaskAndCallback> task);
 
diff --git a/chrome/browser/web_applications/externally_managed_app_manager_impl_unittest.cc b/chrome/browser/web_applications/externally_managed_app_manager_impl_unittest.cc
index 1ca55fcf..596ca26 100644
--- a/chrome/browser/web_applications/externally_managed_app_manager_impl_unittest.cc
+++ b/chrome/browser/web_applications/externally_managed_app_manager_impl_unittest.cc
@@ -466,7 +466,7 @@
   }
 
   void TearDown() override {
-    provider().Shutdown();
+    command_scheduler().Shutdown();
     WebAppTest::TearDown();
   }
 
@@ -510,6 +510,7 @@
               barrier_closure.Run();
             }));
     run_loop.Run();
+
     return results;
   }
 
diff --git a/chrome/browser/web_applications/isolated_web_apps/OWNERS b/chrome/browser/web_applications/isolated_web_apps/OWNERS
new file mode 100644
index 0000000..5c9ed56
--- /dev/null
+++ b/chrome/browser/web_applications/isolated_web_apps/OWNERS
@@ -0,0 +1,2 @@
+cmfcmf@chromium.org
+rmcelrath@chromium.org
diff --git a/chrome/browser/web_applications/policy/web_app_policy_manager.cc b/chrome/browser/web_applications/policy/web_app_policy_manager.cc
index 4893400..83ccc9a 100644
--- a/chrome/browser/web_applications/policy/web_app_policy_manager.cc
+++ b/chrome/browser/web_applications/policy/web_app_policy_manager.cc
@@ -118,11 +118,7 @@
   system_web_apps_delegate_map_ = system_web_apps_delegate_map;
 }
 
-void WebAppPolicyManager::Start(base::OnceClosure initialization_complete) {
-  DCHECK(initialization_complete_.is_null());
-
-  initialization_complete_ = std::move(initialization_complete);
-
+void WebAppPolicyManager::Start(base::OnceClosure on_done) {
   // When Lacros is enabled, don't run PWA-specific logic in Ash.
   // TODO(crbug.com/1251491): Consider factoring out logic that should only run
   // in Ash into a separate class. This way, when running in Ash, we won't need
@@ -135,7 +131,8 @@
       ->PostTask(FROM_HERE,
                  base::BindOnce(
                      &WebAppPolicyManager::InitChangeRegistrarAndRefreshPolicy,
-                     weak_ptr_factory_.GetWeakPtr(), enable_pwa_support));
+                     weak_ptr_factory_.GetWeakPtr(), enable_pwa_support)
+                     .Then(std::move(on_done)));
 }
 
 void WebAppPolicyManager::ReinstallPlaceholderAppIfNecessary(const GURL& url) {
@@ -207,10 +204,6 @@
             weak_ptr_factory_.GetWeakPtr()));
     RefreshPolicyInstalledIsolatedWebApps();
 #endif
-  } else {
-    if (initialization_complete_) {
-      std::move(initialization_complete_).Run();
-    }
   }
   ObserveDisabledSystemFeaturesPolicy();
 }
@@ -545,7 +538,7 @@
 
 void WebAppPolicyManager::SetOnAppsSynchronizedCompletedCallbackForTesting(
     base::OnceClosure callback) {
-  on_apps_synchronized_for_testing_ = std::move(callback);
+  on_apps_synchronized_ = std::move(callback);
 }
 
 void WebAppPolicyManager::SetRefreshPolicySettingsCompletedCallbackForTesting(
@@ -638,13 +631,8 @@
                                   url_and_result.second.code);
   }
 
-  if (on_apps_synchronized_for_testing_) {
-    std::move(on_apps_synchronized_for_testing_).Run();
-  }
-
-  if (initialization_complete_) {
-    std::move(initialization_complete_).Run();
-  }
+  if (on_apps_synchronized_)
+    std::move(on_apps_synchronized_).Run();
 }
 
 WebAppPolicyManager::WebAppSetting::WebAppSetting() {
diff --git a/chrome/browser/web_applications/policy/web_app_policy_manager.h b/chrome/browser/web_applications/policy/web_app_policy_manager.h
index 8bf8375..6ea29f7 100644
--- a/chrome/browser/web_applications/policy/web_app_policy_manager.h
+++ b/chrome/browser/web_applications/policy/web_app_policy_manager.h
@@ -65,8 +65,6 @@
   void SetSystemWebAppDelegateMap(
       const ash::SystemWebAppDelegateMap* system_web_apps_delegate_map);
 
-  // `initialization_complete` waits for the first `SynchronizeInstalledApps` to
-  // finish if it's triggered on `Start`.
   void Start(base::OnceClosure initialization_complete);
 
   void ReinstallPlaceholderAppIfNecessary(const GURL& url);
@@ -192,7 +190,7 @@
 
   // Testing callbacks
   base::OnceClosure refresh_policy_settings_completed_;
-  base::OnceClosure on_apps_synchronized_for_testing_;
+  base::OnceClosure on_apps_synchronized_;
 
   bool is_refreshing_ = false;
   bool needs_refresh_ = false;
@@ -202,9 +200,6 @@
   std::unique_ptr<WebAppSetting> default_settings_;
 
   ExternallyInstalledWebAppPrefs externally_installed_app_prefs_;
-
-  base::OnceClosure initialization_complete_;
-
 #if BUILDFLAG(IS_CHROMEOS)
   std::unique_ptr<IsolatedWebAppPolicyManager> iwa_policy_manager_;
 #endif
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model.cc b/chrome/browser/webauthn/authenticator_request_dialog_model.cc
index 651b7ab..9cfccebe 100644
--- a/chrome/browser/webauthn/authenticator_request_dialog_model.cc
+++ b/chrome/browser/webauthn/authenticator_request_dialog_model.cc
@@ -245,7 +245,12 @@
     SetCurrentStep(*pending_step_);
     pending_step_.reset();
   } else if (mechanisms_.empty()) {
-    SetCurrentStep(Step::kErrorNoAvailableTransports);
+    if (base::FeatureList::IsEnabled(device::kWebAuthnNoPasskeysError) &&
+        transport_availability_.transport_list_did_include_internal) {
+      SetCurrentStep(Step::kErrorNoPasskeys);
+    } else {
+      SetCurrentStep(Step::kErrorNoAvailableTransports);
+    }
   } else if (mechanisms_.size() == 1) {
     mechanisms_[0].callback.Run();
   } else if (priority_mechanism_it != mechanisms_.end()) {
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model.h b/chrome/browser/webauthn/authenticator_request_dialog_model.h
index 2bcd8fdd..3a5986da4 100644
--- a/chrome/browser/webauthn/authenticator_request_dialog_model.h
+++ b/chrome/browser/webauthn/authenticator_request_dialog_model.h
@@ -73,6 +73,7 @@
     // The request errored out before completing. Error will only be sent
     // after user interaction.
     kErrorNoAvailableTransports,
+    kErrorNoPasskeys,
     kErrorInternalUnrecognized,
 
     // The request is already complete, but the error dialog should wait
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 8d4b0b0..2dec125 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1671019116-6b9cae0d3e8c73277c9f214478366172a511d31a.profdata
+chrome-linux-main-1671040780-7354adbba216d8d22545e6cbff01983a0ef786cc.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 3178b67b..846237e3 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1670997253-a4bdf1b52f9c04eb5ecbbf26461b8564531d27cc.profdata
+chrome-mac-arm-main-1671040780-ab587c46ae159837cb1901b47db9f541feb1ec6f.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index d401ef2..7dd319db 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1671008076-19a9c8909deb1a4f7619d99c4ef88613e515e068.profdata
+chrome-win32-main-1671029899-f511e3be18f418e934aad5640a3a4197391f4ec1.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 0d62531af..5106b24 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1671029899-ba84905e9939d7394e959eb36f6891b3bd5d0d8c.profdata
+chrome-win64-main-1671040780-fa17ea6b4718dc5c5320f88590d164f43c2aa427.profdata
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index 314d831b..f3e79a7 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -3478,13 +3478,21 @@
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 // Enum pref indicating how to launch the Lacros browser. It is managed by
-// LacrosAvailability policy can have one of the following values:
+// LacrosAvailability policy and can have one of the following values:
 // 0: User choice (default value).
 // 1: Lacros is disallowed.
 // 2: Lacros is enabled but not the pimary browser.
 // 3: Lacros is enabled as the primary browser.
 // 4: Lacros is the only available browser.
 const char kLacrosLaunchSwitch[] = "lacros_launch_switch";
+
+// Enum pref indicating which Lacros browser to launch: rootfs or stateful. It
+// is managed by LacrosSelection policy and can have one of the following
+// values:
+// 0: User choice (default value).
+// 1: Always load rootfs Lacros.
+// 2: Always load stateful Lacros.
+const char kLacrosSelection[] = "lacros_selection";
 #endif
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index e3b12b2..6230f1a 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -1208,6 +1208,7 @@
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 extern const char kLacrosLaunchSwitch[];
+extern const char kLacrosSelection[];
 #endif
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 6a1f4147..ca11bd4 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -8618,6 +8618,7 @@
       "../browser/enterprise/idle/action_runner_unittest.cc",
       "../browser/enterprise/idle/action_unittest.cc",
       "../browser/enterprise/idle/browser_closer_unittest.cc",
+      "../browser/enterprise/idle/idle_timeout_policy_handler_unittest.cc",
       "../browser/enterprise/remote_commands/rotate_attestation_credential_job_unittest.cc",
       "../browser/enterprise/signals/user_delegate_impl_unittest.cc",
       "../browser/net/disk_cache_dir_policy_handler_unittest.cc",
diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json
index 99e44fae7..033b3fb 100644
--- a/chrome/test/data/policy/policy_test_cases.json
+++ b/chrome/test/data/policy/policy_test_cases.json
@@ -18779,6 +18779,56 @@
       }
     ]
   },
+  "LacrosSelection": {
+    "os": [
+      "chromeos_ash"
+    ],
+    "can_be_recommended": false,
+    "policy_pref_mapping_tests": [
+      {
+        "policies": {},
+        "prefs": {
+          "lacros_selection": {
+            "location": "local_state",
+            "value": 1
+          }
+        }
+      },
+      {
+        "policies": {
+          "LacrosSelection": "user_choice"
+        },
+        "prefs": {
+          "lacros_selection": {
+            "location": "local_state",
+            "value": 0
+          }
+        }
+      },
+      {
+        "policies": {
+          "LacrosSelection": "rootfs"
+        },
+        "prefs": {
+          "lacros_selection": {
+            "location": "local_state",
+            "value": 1
+          }
+        }
+      },
+      {
+        "policies": {
+          "LacrosSelection": "stateful"
+        },
+        "prefs": {
+          "lacros_selection": {
+            "location": "local_state",
+            "value": 2
+          }
+        }
+      }
+    ]
+  },
   "LacrosDataBackwardMigrationMode": {
     "os": [
       "chromeos_ash"
@@ -20603,18 +20653,18 @@
         },
         "prefs": {
           "idle_timeout": {
-            "value": 10
+            "value": "600000000"
           }
         }
       },
       {
         "policies": {
-          "IdleTimeout": 1,
+          "IdleTimeout": 0,
           "IdleTimeoutActions": []
         },
         "prefs": {
           "idle_timeout": {
-            "value": 5
+            "value": "60000000"
           }
         }
       }
diff --git a/chrome/test/data/webui/BUILD.gn b/chrome/test/data/webui/BUILD.gn
index 5dabfeb2..f941d99 100644
--- a/chrome/test/data/webui/BUILD.gn
+++ b/chrome/test/data/webui/BUILD.gn
@@ -169,12 +169,12 @@
         "chromeos/emoji_picker/emoji_picker_browsertest.js",
         "chromeos/gaia_action_buttons/gaia_action_buttons_browsertest.js",
         "chromeos/internet_detail_dialog_browsertest.js",
+        "chromeos/set_time_dialog_browsertest.js",
+        "chromeos/sys_internals/sys_internals_browsertest.js",
         "cr_components/chromeos/cr_components_chromeos_v3_browsertest.js",
         "js/i18n_process_test.js",
-        "set_time_dialog_browsertest.js",
         "settings/chromeos/a11y/v3_os_a11y_browsertest.js",
         "settings/chromeos/os_settings_v3_browsertest.js",
-        "sys_internals/sys_internals_browsertest.js",
       ]
     } else {
       sources += [ "signin/signin_browsertest.js" ]
@@ -461,16 +461,16 @@
 
   if (is_chromeos_ash) {
     input_files += [
+      "chromeos/arc_account_picker/test_util.js",
+      "chromeos/ash_common/i18n_behavior_test.js",
       "chromeos/chai_assert.js",
+      "chromeos/fake_network_config_mojom.js",
       "chromeos/mock_controller.js",
       "chromeos/mock_controller.m.js",
+      "chromeos/set_time_dialog_test.js",
       "chromeos/test_browser_proxy.js",
       "chromeos/test_store.js",
       "chromeos/test_util.js",
-      "chromeos/arc_account_picker/test_util.js",
-      "chromeos/ash_common/i18n_behavior_test.js",
-      "chromeos/fake_network_config_mojom.js",
-      "set_time_dialog_test.js",
     ]
   }
 
diff --git a/chrome/test/data/webui/set_time_dialog_browsertest.js b/chrome/test/data/webui/chromeos/set_time_dialog_browsertest.js
similarity index 85%
rename from chrome/test/data/webui/set_time_dialog_browsertest.js
rename to chrome/test/data/webui/chromeos/set_time_dialog_browsertest.js
index 0767e75f..2f466b9 100644
--- a/chrome/test/data/webui/set_time_dialog_browsertest.js
+++ b/chrome/test/data/webui/chromeos/set_time_dialog_browsertest.js
@@ -10,7 +10,7 @@
 var SetTimeDialogBrowserTest = class extends PolymerTest {
   /** @override */
   get browsePreload() {
-    return 'chrome://set-time/test_loader.html?module=set_time_dialog_test.js';
+    return 'chrome://set-time/test_loader.html?module=chromeos/set_time_dialog_test.js';
   }
 };
 
diff --git a/chrome/test/data/webui/set_time_dialog_test.js b/chrome/test/data/webui/chromeos/set_time_dialog_test.js
similarity index 99%
rename from chrome/test/data/webui/set_time_dialog_test.js
rename to chrome/test/data/webui/chromeos/set_time_dialog_test.js
index 4f19c3e9..6b6ede7 100644
--- a/chrome/test/data/webui/set_time_dialog_test.js
+++ b/chrome/test/data/webui/chromeos/set_time_dialog_test.js
@@ -7,7 +7,6 @@
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {SetTimeBrowserProxyImpl} from 'chrome://set-time/set_time_browser_proxy.js';
-
 import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
 
 suite('SetTimeDialog', function() {
diff --git a/chrome/test/data/webui/sys_internals/all_tests.js b/chrome/test/data/webui/chromeos/sys_internals/all_tests.js
similarity index 100%
rename from chrome/test/data/webui/sys_internals/all_tests.js
rename to chrome/test/data/webui/chromeos/sys_internals/all_tests.js
diff --git a/chrome/test/data/webui/sys_internals/api_test.js b/chrome/test/data/webui/chromeos/sys_internals/api_test.js
similarity index 100%
rename from chrome/test/data/webui/sys_internals/api_test.js
rename to chrome/test/data/webui/chromeos/sys_internals/api_test.js
diff --git a/chrome/test/data/webui/sys_internals/line_chart/data_series_test.js b/chrome/test/data/webui/chromeos/sys_internals/line_chart/data_series_test.js
similarity index 100%
rename from chrome/test/data/webui/sys_internals/line_chart/data_series_test.js
rename to chrome/test/data/webui/chromeos/sys_internals/line_chart/data_series_test.js
diff --git a/chrome/test/data/webui/sys_internals/line_chart/line_chart_test.js b/chrome/test/data/webui/chromeos/sys_internals/line_chart/line_chart_test.js
similarity index 100%
rename from chrome/test/data/webui/sys_internals/line_chart/line_chart_test.js
rename to chrome/test/data/webui/chromeos/sys_internals/line_chart/line_chart_test.js
diff --git a/chrome/test/data/webui/sys_internals/line_chart/menu_test.js b/chrome/test/data/webui/chromeos/sys_internals/line_chart/menu_test.js
similarity index 100%
rename from chrome/test/data/webui/sys_internals/line_chart/menu_test.js
rename to chrome/test/data/webui/chromeos/sys_internals/line_chart/menu_test.js
diff --git a/chrome/test/data/webui/sys_internals/line_chart/scrollbar_test.js b/chrome/test/data/webui/chromeos/sys_internals/line_chart/scrollbar_test.js
similarity index 100%
rename from chrome/test/data/webui/sys_internals/line_chart/scrollbar_test.js
rename to chrome/test/data/webui/chromeos/sys_internals/line_chart/scrollbar_test.js
diff --git a/chrome/test/data/webui/sys_internals/line_chart/sub_chart_test.js b/chrome/test/data/webui/chromeos/sys_internals/line_chart/sub_chart_test.js
similarity index 100%
rename from chrome/test/data/webui/sys_internals/line_chart/sub_chart_test.js
rename to chrome/test/data/webui/chromeos/sys_internals/line_chart/sub_chart_test.js
diff --git a/chrome/test/data/webui/sys_internals/line_chart/unit_label_test.js b/chrome/test/data/webui/chromeos/sys_internals/line_chart/unit_label_test.js
similarity index 100%
rename from chrome/test/data/webui/sys_internals/line_chart/unit_label_test.js
rename to chrome/test/data/webui/chromeos/sys_internals/line_chart/unit_label_test.js
diff --git a/chrome/test/data/webui/sys_internals/page_drawer_test.js b/chrome/test/data/webui/chromeos/sys_internals/page_drawer_test.js
similarity index 100%
rename from chrome/test/data/webui/sys_internals/page_drawer_test.js
rename to chrome/test/data/webui/chromeos/sys_internals/page_drawer_test.js
diff --git a/chrome/test/data/webui/sys_internals/page_infopage_test.js b/chrome/test/data/webui/chromeos/sys_internals/page_infopage_test.js
similarity index 100%
rename from chrome/test/data/webui/sys_internals/page_infopage_test.js
rename to chrome/test/data/webui/chromeos/sys_internals/page_infopage_test.js
diff --git a/chrome/test/data/webui/sys_internals/page_switch_test.js b/chrome/test/data/webui/chromeos/sys_internals/page_switch_test.js
similarity index 100%
rename from chrome/test/data/webui/sys_internals/page_switch_test.js
rename to chrome/test/data/webui/chromeos/sys_internals/page_switch_test.js
diff --git a/chrome/test/data/webui/sys_internals/page_unit_test.js b/chrome/test/data/webui/chromeos/sys_internals/page_unit_test.js
similarity index 100%
rename from chrome/test/data/webui/sys_internals/page_unit_test.js
rename to chrome/test/data/webui/chromeos/sys_internals/page_unit_test.js
diff --git a/chrome/test/data/webui/sys_internals/sys_internals_browsertest.js b/chrome/test/data/webui/chromeos/sys_internals/sys_internals_browsertest.js
similarity index 94%
rename from chrome/test/data/webui/sys_internals/sys_internals_browsertest.js
rename to chrome/test/data/webui/chromeos/sys_internals/sys_internals_browsertest.js
index f05096f..60d011f0 100644
--- a/chrome/test/data/webui/sys_internals/sys_internals_browsertest.js
+++ b/chrome/test/data/webui/chromeos/sys_internals/sys_internals_browsertest.js
@@ -18,7 +18,7 @@
   __proto__: testing.Test.prototype,
 
   browsePreload:
-      'chrome://sys-internals/index.html?module=sys_internals/all_tests.js&host=test',
+      'chrome://sys-internals/index.html?module=chromeos/sys_internals/all_tests.js&host=test',
 
   isAsync: true,
 
diff --git a/chrome/test/data/webui/sys_internals/test_util.js b/chrome/test/data/webui/chromeos/sys_internals/test_util.js
similarity index 100%
rename from chrome/test/data/webui/sys_internals/test_util.js
rename to chrome/test/data/webui/chromeos/sys_internals/test_util.js
diff --git a/chrome/test/data/webui/settings/chromeos/text_to_speech_page_tests.js b/chrome/test/data/webui/settings/chromeos/text_to_speech_page_tests.js
index 782fe7b..15a07159 100644
--- a/chrome/test/data/webui/settings/chromeos/text_to_speech_page_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/text_to_speech_page_tests.js
@@ -4,32 +4,24 @@
 
 import 'chrome://os-settings/chromeos/lazy_load.js';
 
-import {Router, routes} from 'chrome://os-settings/chromeos/os_settings.js';
+import {CrSettingsPrefs, Router, routes} from 'chrome://os-settings/chromeos/os_settings.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {waitAfterNextRender, waitBeforeNextRender} from 'chrome://webui-test/polymer_test_util.js';
 import {eventToPromise, isVisible} from 'chrome://webui-test/test_util.js';
 
-import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
-
 suite('TextToSpeechPageTests', function() {
   let page = null;
 
-  function initPage(opt_prefs) {
-    page = document.createElement('settings-text-to-speech-page');
-    page.prefs = opt_prefs || getDefaultPrefs();
-    document.body.appendChild(page);
-  }
+  async function initPage() {
+    const prefElement = document.createElement('settings-prefs');
+    document.body.appendChild(prefElement);
 
-  function getDefaultPrefs() {
-    return {
-      'settings': {
-        'accessibility': {
-          key: 'settings.accessibility',
-          type: chrome.settingsPrivate.PrefType.BOOLEAN,
-          value: false,
-        },
-      },
-    };
+    await CrSettingsPrefs.initialized;
+    page = document.createElement('settings-text-to-speech-page');
+    page.prefs = prefElement.prefs;
+    document.body.appendChild(page);
+    flush();
   }
 
   setup(function() {
@@ -50,8 +42,7 @@
         `should focus ${selector} button when returning from ${
             route.path} subpage`,
         async () => {
-          initPage();
-          flush();
+          await initPage();
           const router = Router.getInstance();
 
           const subpageButton = page.shadowRoot.querySelector(selector);
@@ -75,13 +66,12 @@
         });
   });
 
-  test('only allowed subpages are available in kiosk mode', function() {
+  test('only allowed subpages are available in kiosk mode', async function() {
     loadTimeData.overrideValues({
       isKioskModeActive: true,
       showTabletModeShelfNavigationButtonsSettings: true,
     });
-    initPage();
-    flush();
+    await initPage();
 
     const allowed_subpages = [
       'chromeVoxSubpageButton',
diff --git a/chrome/test/data/webui/side_panel/customize_chrome/color_test.ts b/chrome/test/data/webui/side_panel/customize_chrome/color_test.ts
index 5990693f..ebb12f6 100644
--- a/chrome/test/data/webui/side_panel/customize_chrome/color_test.ts
+++ b/chrome/test/data/webui/side_panel/customize_chrome/color_test.ts
@@ -5,6 +5,7 @@
 import 'chrome://webui-test/mojo_webui_test_support.js';
 
 import {ColorElement} from 'chrome://customize-chrome-side-panel.top-chrome/color.js';
+import {assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 
 import {assertStyle} from './test_support.js';
 
@@ -24,4 +25,30 @@
     assertStyle(colorElement.$.background, 'fill', 'rgb(255, 0, 0)');
     assertStyle(colorElement.$.foreground, 'fill', 'rgb(0, 255, 0)');
   });
+
+  test('color can be checked', () => {
+    colorElement.checked = true;
+
+    const wrapper = colorElement.shadowRoot!.querySelector(
+        'customize-chrome-check-mark-wrapper')!;
+    assertTrue(wrapper.checked);
+    const svg = colorElement.shadowRoot!.querySelector('svg')!;
+    assertStyle(svg, 'width', '46px');
+    assertStyle(svg, 'height', '46px');
+    const background = colorElement.shadowRoot!.querySelector('#background')!;
+    assertStyle(background, 'r', '25px');
+  });
+
+  test('color can be unchecked', () => {
+    colorElement.checked = false;
+
+    const wrapper = colorElement.shadowRoot!.querySelector(
+        'customize-chrome-check-mark-wrapper')!;
+    assertFalse(wrapper.checked);
+    const svg = colorElement.shadowRoot!.querySelector('svg')!;
+    assertStyle(svg, 'width', '50px');
+    assertStyle(svg, 'height', '50px');
+    const background = colorElement.shadowRoot!.querySelector('#background')!;
+    assertStyle(background, 'r', '24px');
+  });
 });
diff --git a/chrome/test/data/webui/side_panel/customize_chrome/colors_test.ts b/chrome/test/data/webui/side_panel/customize_chrome/colors_test.ts
index 16a1964..06dda2220 100644
--- a/chrome/test/data/webui/side_panel/customize_chrome/colors_test.ts
+++ b/chrome/test/data/webui/side_panel/customize_chrome/colors_test.ts
@@ -9,11 +9,11 @@
 import {ChromeColor, CustomizeChromePageCallbackRouter, CustomizeChromePageHandlerRemote, CustomizeChromePageRemote, Theme} from 'chrome://customize-chrome-side-panel.top-chrome/customize_chrome.mojom-webui.js';
 import {CustomizeChromeApiProxy} from 'chrome://customize-chrome-side-panel.top-chrome/customize_chrome_api_proxy.js';
 import {PromiseResolver} from 'chrome://resources/js/promise_resolver.js';
-import {assertDeepEquals, assertEquals} from 'chrome://webui-test/chai_assert.js';
+import {assertDeepEquals, assertEquals, assertGE, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
 import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
 
-import {createTheme, installMock} from './test_support.js';
+import {assertStyle, capture, createTheme, installMock} from './test_support.js';
 
 suite('ColorsTest', () => {
   let colorsElement: ColorsElement;
@@ -100,4 +100,114 @@
     assertEquals(1, handler.getCallCount('setForegroundColor'));
     assertEquals(2, handler.getArgs('setForegroundColor')[0].value);
   });
+
+  test('opens color picker', () => {
+    const focus = capture(colorsElement.$.colorPicker, 'focus');
+    const click = capture(colorsElement.$.colorPicker, 'click');
+
+    colorsElement.$.customColor.click();
+
+    assertTrue(focus.received);
+    assertTrue(click.received);
+  });
+
+  test('sets custom color', () => {
+    colorsElement.$.colorPicker.value = '#ff0000';
+    colorsElement.$.colorPicker.dispatchEvent(new Event('change'));
+
+    const args = handler.getArgs('setForegroundColor');
+    assertGE(1, args.length);
+    assertEquals(0xffff0000, args.at(-1).value);
+  });
+
+  test('updates custom color for theme', async () => {
+    const colors = {
+      colors: [
+        {id: 1, name: 'foo', background: {value: 1}, foreground: {value: 2}},
+      ],
+    };
+    chromeColorsResolver.resolve(colors);
+
+    // Set a custom color theme.
+    const customColortheme = createTheme();
+    customColortheme.backgroundColor = {value: 0xffff0000};
+    customColortheme.foregroundColor = {value: 0xff00ff00};
+    customColortheme.colorPickerIconColor = {value: 0xff0000ff};
+    callbackRouter.setTheme(customColortheme);
+    await callbackRouter.$.flushForTesting();
+
+    // Custom color circle should be updated.
+    assertEquals(0xffff0000, colorsElement.$.customColor.backgroundColor.value);
+    assertEquals(0xff00ff00, colorsElement.$.customColor.foregroundColor.value);
+    assertStyle(
+        colorsElement.$.colorPickerIcon, 'background-color', 'rgb(0, 0, 255)');
+
+    // Set a theme that is not a custom color theme.
+    const otherTheme = createTheme();
+    otherTheme.backgroundColor = {value: 0xffffffff};
+    otherTheme.foregroundColor = undefined;  // Makes a default theme.
+    otherTheme.colorPickerIconColor = {value: 0xffffffff};
+    callbackRouter.setTheme(otherTheme);
+    await callbackRouter.$.flushForTesting();
+
+    // Custom color circle should be not be updated.
+    assertEquals(0xffff0000, colorsElement.$.customColor.backgroundColor.value);
+    assertEquals(0xff00ff00, colorsElement.$.customColor.foregroundColor.value);
+    assertStyle(
+        colorsElement.$.colorPickerIcon, 'background-color', 'rgb(0, 0, 255)');
+  });
+
+  test('checks selected color', async () => {
+    const colors = {
+      colors: [
+        {id: 1, name: 'foo', background: {value: 1}, foreground: {value: 2}},
+        {id: 2, name: 'bar', background: {value: 3}, foreground: {value: 4}},
+      ],
+    };
+    chromeColorsResolver.resolve(colors);
+    const theme = createTheme();
+
+    // Set default color.
+    theme.foregroundColor = undefined;
+    callbackRouter.setTheme(theme);
+    await callbackRouter.$.flushForTesting();
+
+    // Check default color selected.
+    let checkedColors = colorsElement.shadowRoot!.querySelectorAll('[checked]');
+    assertEquals(1, checkedColors.length);
+    assertEquals(colorsElement.$.defaultColor, checkedColors[0]);
+    let indexedColors =
+        colorsElement.shadowRoot!.querySelectorAll('[tabindex="0"]');
+    assertEquals(1, indexedColors.length);
+    assertEquals(colorsElement.$.defaultColor, indexedColors[0]);
+
+    // Set Chrome color.
+    theme.foregroundColor = {value: 2};
+    callbackRouter.setTheme(theme);
+    await callbackRouter.$.flushForTesting();
+
+    // Check Chrome color selected.
+    checkedColors = colorsElement.shadowRoot!.querySelectorAll('[checked]');
+    assertEquals(1, checkedColors.length);
+    assertEquals('chrome-color', checkedColors[0]!.className);
+    assertEquals(2, (checkedColors[0]! as ColorElement).foregroundColor.value);
+    indexedColors =
+        colorsElement.shadowRoot!.querySelectorAll('[tabindex="0"]');
+    assertEquals(1, indexedColors.length);
+    assertEquals('chrome-color', indexedColors[0]!.className);
+
+    // Set custom color.
+    theme.foregroundColor = {value: 5};
+    callbackRouter.setTheme(theme);
+    await callbackRouter.$.flushForTesting();
+
+    // Check custom color selected.
+    checkedColors = colorsElement.shadowRoot!.querySelectorAll('[checked]');
+    assertEquals(1, checkedColors.length);
+    assertEquals(colorsElement.$.customColor, checkedColors[0]);
+    indexedColors =
+        colorsElement.shadowRoot!.querySelectorAll('[tabindex="0"]');
+    assertEquals(1, indexedColors.length);
+    assertEquals(colorsElement.$.customColorContainer, indexedColors[0]);
+  });
 });
diff --git a/chrome/test/data/webui/side_panel/customize_chrome/test_support.ts b/chrome/test/data/webui/side_panel/customize_chrome/test_support.ts
index dd47307..3107b072 100644
--- a/chrome/test/data/webui/side_panel/customize_chrome/test_support.ts
+++ b/chrome/test/data/webui/side_panel/customize_chrome/test_support.ts
@@ -41,6 +41,7 @@
 export function createBackgroundImage(url: string): BackgroundImage {
   return {
     url: {url},
+    isUploadedImage: false,
     title: '',
   };
 }
@@ -51,5 +52,13 @@
     systemDarkMode,
     backgroundColor: {value: 0xffff0000},
     foregroundColor: undefined,
+    colorPickerIconColor: {value: 0xffff0000},
   };
 }
+
+export function capture(
+    target: HTMLElement, event: string): {received: boolean} {
+  const capture = {received: false};
+  target.addEventListener(event, () => capture.received = true);
+  return capture;
+}
diff --git a/chrome/test/data/webui/side_panel/customize_chrome/theme_snapshot_test.ts b/chrome/test/data/webui/side_panel/customize_chrome/theme_snapshot_test.ts
index 3fcfa8b..3ff7543 100644
--- a/chrome/test/data/webui/side_panel/customize_chrome/theme_snapshot_test.ts
+++ b/chrome/test/data/webui/side_panel/customize_chrome/theme_snapshot_test.ts
@@ -8,7 +8,7 @@
 import {CustomizeChromePageCallbackRouter, CustomizeChromePageHandlerRemote, CustomizeChromePageRemote} from 'chrome://customize-chrome-side-panel.top-chrome/customize_chrome.mojom-webui.js';
 import {CustomizeChromeApiProxy} from 'chrome://customize-chrome-side-panel.top-chrome/customize_chrome_api_proxy.js';
 import {ThemeSnapshotElement} from 'chrome://customize-chrome-side-panel.top-chrome/theme_snapshot.js';
-import {assertEquals} from 'chrome://webui-test/chai_assert.js';
+import {assertEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
 
 import {$$, assertStyle, createBackgroundImage, createTheme, installMock} from './test_support.js';
@@ -52,13 +52,16 @@
 
     // Assert.
     assertEquals(1, handler.getCallCount('updateTheme'));
+    const selector =
+        themeSnapshotElement.shadowRoot!.querySelector('iron-pages');
+    assertTrue(!!selector);
+    assertEquals('customTheme', selector.selected);
     assertEquals(
         'chrome://theme/foo',
-        $$<HTMLImageElement>(
-            themeSnapshotElement, '#themeSnapshot .image')!.src);
+        $$<HTMLImageElement>(themeSnapshotElement, '.theme-snapshot img')!.src);
   });
 
-  test('having no theme set updates preview background color', async () => {
+  test('not setting a theme updates preview background color', async () => {
     // Arrange.
     createThemeSnapshotElement();
     const theme = createTheme();
@@ -70,8 +73,31 @@
 
     // Assert.
     assertEquals(1, handler.getCallCount('updateTheme'));
+    const selector =
+        themeSnapshotElement.shadowRoot!.querySelector('iron-pages');
+    assertTrue(!!selector);
+    assertEquals('classicChrome', selector.selected);
     assertStyle(
-        $$(themeSnapshotElement, '#themeSnapshot .image')!, 'background-color',
-        'rgb(20, 83, 154)');
+        $$(themeSnapshotElement, '.theme-snapshot #classicChrome')!,
+        'background-color', 'rgb(20, 83, 154)');
+  });
+
+  test('uploading a background updates theme snapshot', async () => {
+    // Arrange.
+    createThemeSnapshotElement();
+    const theme = createTheme();
+    theme.backgroundImage = createBackgroundImage('chrome://theme/foo');
+    theme.backgroundImage.isUploadedImage = true;
+
+    // Act.
+    callbackRouterRemote.setTheme(theme);
+    await callbackRouterRemote.$.flushForTesting();
+
+    // Assert.
+    assertEquals(1, handler.getCallCount('updateTheme'));
+    const selector =
+        themeSnapshotElement.shadowRoot!.querySelector('iron-pages');
+    assertTrue(!!selector);
+    assertEquals('uploadedImage', selector.selected);
   });
 });
diff --git a/chrome/test/media_router/access_code_cast/access_code_cast_integration_browsertest.cc b/chrome/test/media_router/access_code_cast/access_code_cast_integration_browsertest.cc
index 257e75b..a29f59e2 100644
--- a/chrome/test/media_router/access_code_cast/access_code_cast_integration_browsertest.cc
+++ b/chrome/test/media_router/access_code_cast/access_code_cast_integration_browsertest.cc
@@ -143,22 +143,6 @@
         }
       });
 
-  ON_CALL(*media_router_, RegisterMediaRoutesObserver(_))
-      .WillByDefault([this](MediaRoutesObserver* observer) {
-        media_routes_observers_.push_back(observer);
-        return true;
-      });
-
-  // Remove route observers as appropriate (destructing handlers will cause
-  // this to occur).
-  ON_CALL(*media_router_, UnregisterMediaRoutesObserver(_))
-      .WillByDefault([this](MediaRoutesObserver* observer) {
-        auto it = base::ranges::find(media_routes_observers_, observer);
-        if (it != media_routes_observers_.end()) {
-          media_routes_observers_.erase(it);
-        }
-      });
-
   // Handler so MockMediaRouter will respond to requests to create a route.
   // Will construct a RouteRequestResult based on the set result code and
   // then call the handler's callback, which should call the page's callback.
@@ -547,8 +531,9 @@
 
 void AccessCodeCastIntegrationBrowserTest::UpdateRoutes(
     const std::vector<MediaRoute>& routes) {
-  for (MediaRoutesObserver* routes_observer : media_routes_observers_) {
-    routes_observer->OnRoutesUpdated(routes);
+  for (MediaRoutesObserver& routes_observer :
+       media_router_->routes_observers()) {
+    routes_observer.OnRoutesUpdated(routes);
   }
 }
 
diff --git a/chrome/test/media_router/media_router_integration_browsertest.cc b/chrome/test/media_router/media_router_integration_browsertest.cc
index cc299e54..a5f50c3c 100644
--- a/chrome/test/media_router/media_router_integration_browsertest.cc
+++ b/chrome/test/media_router/media_router_integration_browsertest.cc
@@ -220,7 +220,7 @@
   // TerminateRoute, or add pass callback to the TestProvider to run when all
   // routes are gone.
   base::RunLoop run_loop;
-  NoRoutesObserver no_routes_observer(
+  auto no_routes_observer = std::make_unique<NoRoutesObserver>(
       MediaRouterFactory::GetApiForBrowserContext(
           web_contents->GetBrowserContext()),
       run_loop.QuitClosure());
diff --git a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastContentWindowAndroid.java b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastContentWindowAndroid.java
index 809ee0e..e0bf8cff7 100644
--- a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastContentWindowAndroid.java
+++ b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastContentWindowAndroid.java
@@ -5,6 +5,8 @@
 package org.chromium.chromecast.shell;
 
 import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.view.Display;
 
 import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
@@ -40,12 +42,29 @@
     @CalledByNative
     private static CastContentWindowAndroid create(long nativeCastContentWindowAndroid,
             boolean enableTouchInput, boolean isRemoteControlMode, boolean turnOnScreen,
-            boolean keepScreenOn, String sessionId) {
+            boolean keepScreenOn, String sessionId, String displayId) {
         return new CastContentWindowAndroid(nativeCastContentWindowAndroid,
-                ContextUtils.getApplicationContext(), enableTouchInput, isRemoteControlMode,
+                getContextWithDisplay(displayId), enableTouchInput, isRemoteControlMode,
                 turnOnScreen, keepScreenOn, sessionId);
     }
 
+    private static Context getContextWithDisplay(String displayId) {
+        Context context = ContextUtils.getApplicationContext();
+        try {
+            int id = Integer.parseInt(displayId);
+            DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+            Display display = displayManager.getDisplay(id);
+            if (display != null) {
+                return context.createDisplayContext(display);
+            }
+        } catch (NumberFormatException e) {
+        }
+        Log.i(TAG,
+                "Display with the given cast display id is not available, "
+                        + "use the default display to create the web view.");
+        return context;
+    }
+
     private CastContentWindowAndroid(long nativeCastContentWindowAndroid, final Context context,
             boolean enableTouchInput, boolean isRemoteControlMode, boolean turnOnScreen,
             boolean keepScreenOn, String sessionId) {
diff --git a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsComponent.java b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsComponent.java
index 836cf997..16d5abd 100644
--- a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsComponent.java
+++ b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsComponent.java
@@ -10,14 +10,17 @@
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.os.PatternMatcher;
 
 import androidx.annotation.VisibleForTesting;
 
+import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.Log;
 import org.chromium.chromecast.base.Controller;
 import org.chromium.content_public.browser.WebContents;
+import org.chromium.ui.display.DisplayAndroidManager;
 
 /**
  * A layer of indirection between CastContentWindowAndroid and CastWebContents(Activity|Service).
@@ -100,9 +103,11 @@
             boolean isRemoteControlMode, boolean turnOnScreen) {
         Intent intent = CastWebContentsIntentUtils.requestStartCastActivity(context, webContents,
                 enableTouch, isRemoteControlMode, turnOnScreen, mKeepScreenOn, mSessionId);
-        if (DEBUG) Log.d(TAG, "start activity by intent: " + intent);
+        int displayId = DisplayAndroidManager.getDefaultDisplayForContext(context).getDisplayId();
+        if (DEBUG) Log.d(TAG, "start activity by intent: " + intent + " on display: " + displayId);
         sResumeIntent.set(intent);
-        context.startActivity(intent);
+        Bundle bundle = ApiCompatibilityUtils.createLaunchDisplayIdActivityOptions(displayId);
+        context.startActivity(intent, bundle);
     }
 
     private void sendStopWebContentEvent() {
diff --git a/chromecast/browser/android/cast_content_window_android.cc b/chromecast/browser/android/cast_content_window_android.cc
index 7315c85..29efb38 100644
--- a/chromecast/browser/android/cast_content_window_android.cc
+++ b/chromecast/browser/android/cast_content_window_android.cc
@@ -24,11 +24,13 @@
     bool is_remote_control_mode,
     bool turn_on_screen,
     bool keep_screen_on,
-    const std::string& session_id) {
+    const std::string& session_id,
+    const std::string& display_id) {
   JNIEnv* env = base::android::AttachCurrentThread();
   return Java_CastContentWindowAndroid_create(
       env, native_window, enable_touch_input, is_remote_control_mode,
-      turn_on_screen, keep_screen_on, ConvertUTF8ToJavaString(env, session_id));
+      turn_on_screen, keep_screen_on, ConvertUTF8ToJavaString(env, session_id),
+      ConvertUTF8ToJavaString(env, display_id));
 }
 
 }  // namespace
@@ -42,7 +44,8 @@
                                     params_->is_remote_control_mode,
                                     params_->turn_on_screen,
                                     params_->keep_screen_on,
-                                    params_->session_id)) {}
+                                    params_->session_id,
+                                    params_->display_id)) {}
 
 CastContentWindowAndroid::~CastContentWindowAndroid() {
   JNIEnv* env = base::android::AttachCurrentThread();
diff --git a/chromecast/browser/android/junit/src/org/chromium/chromecast/shell/CastWebContentsComponentTest.java b/chromecast/browser/android/junit/src/org/chromium/chromecast/shell/CastWebContentsComponentTest.java
index 0d6bb44..bdc6e69 100644
--- a/chromecast/browser/android/junit/src/org/chromium/chromecast/shell/CastWebContentsComponentTest.java
+++ b/chromecast/browser/android/junit/src/org/chromium/chromecast/shell/CastWebContentsComponentTest.java
@@ -10,13 +10,18 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.Activity;
 import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.ContextWrapper;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import android.view.Display;
 
 import androidx.localbroadcastmanager.content.LocalBroadcastManager;
 import androidx.test.core.app.ApplicationProvider;
@@ -51,7 +56,11 @@
 
     private static final String SESSION_ID = "123456789";
 
+    private static final int DISPLAY_ID = 1;
+    private static final String ACTIVITY_OPTIONS_DISPLAY_ID = "android.activity.launchDisplayId";
+
     private @Mock WebContents mWebContents;
+    private @Mock Display mDisplay;
     private Activity mActivity;
     private ShadowActivity mShadowActivity;
     private StartParams mStartParams;
@@ -62,6 +71,7 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        when(mDisplay.getDisplayId()).thenReturn(DISPLAY_ID);
         mActivity = Mockito.spy(Robolectric.buildActivity(Activity.class).setup().get());
         mShadowActivity = Shadows.shadowOf(mActivity);
         mStartParams = new StartParams(mActivity, mWebContents, APP_ID);
@@ -82,6 +92,29 @@
     }
 
     @Test
+    @Config(minSdk = VERSION_CODES.R)
+    public void testStartStartsWebContentsActivityWithDisplayId() {
+        Assume.assumeFalse(BuildConfig.DISPLAY_WEB_CONTENTS_IN_SERVICE);
+
+        ContextWrapper context =
+                Mockito.spy(new ContextWrapper(ContextUtils.getApplicationContext()) {
+                    @Override
+                    public Display getDisplay() {
+                        return mDisplay;
+                    }
+                });
+        StartParams startParams = new StartParams(context, mWebContents, APP_ID);
+
+        CastWebContentsComponent component =
+                new CastWebContentsComponent(SESSION_ID, null, null, false, false, true, false);
+        component.start(startParams, false);
+
+        ArgumentCaptor<Bundle> bundle = ArgumentCaptor.forClass(Bundle.class);
+        verify(context).startActivity(any(Intent.class), bundle.capture());
+        Assert.assertEquals(bundle.getValue().getInt(ACTIVITY_OPTIONS_DISPLAY_ID), DISPLAY_ID);
+    }
+
+    @Test
     public void testStartStartsWebContentsService() {
         Assume.assumeFalse(BuildConfig.DISPLAY_WEB_CONTENTS_IN_SERVICE);
 
diff --git a/chromecast/browser/mojom/cast_web_service.mojom b/chromecast/browser/mojom/cast_web_service.mojom
index 7f002d14..90f4a64 100644
--- a/chromecast/browser/mojom/cast_web_service.mojom
+++ b/chromecast/browser/mojom/cast_web_service.mojom
@@ -55,6 +55,9 @@
   // Cast Application/Activity session ID.
   string session_id = "";
 
+  // Cast display ID to create the web view (if available).
+  string display_id = "";
+
   // Cast Receiver SDK version of the application (if available).
   string sdk_version = "";
 
diff --git a/chromeos/ash/components/cryptohome/auth_factor_conversions.cc b/chromeos/ash/components/cryptohome/auth_factor_conversions.cc
index 2add076..543543a 100644
--- a/chromeos/ash/components/cryptohome/auth_factor_conversions.cc
+++ b/chromeos/ash/components/cryptohome/auth_factor_conversions.cc
@@ -10,6 +10,7 @@
 #include "base/check_op.h"
 #include "base/logging.h"
 #include "base/notreached.h"
+#include "base/strings/string_number_conversions.h"
 #include "chromeos/ash/components/dbus/cryptohome/auth_factor.pb.h"
 
 namespace cryptohome {
@@ -152,7 +153,9 @@
         proto_input->set_recovery_response(recovery_auth.recovery_data);
       } else {
         const auto& recovery_creation = auth_input.GetRecoveryCreationInput();
-        proto_input->set_mediator_pub_key(recovery_creation.pub_key);
+        DCHECK(
+            base::HexStringToString(recovery_creation.pub_key,
+                                    proto_input->mutable_mediator_pub_key()));
         proto_input->set_user_gaia_id(recovery_creation.user_gaia_id);
         proto_input->set_device_user_id(recovery_creation.device_user_id);
       }
diff --git a/chromeos/crosapi/mojom/BUILD.gn b/chromeos/crosapi/mojom/BUILD.gn
index 14ff114..12d04d1 100644
--- a/chromeos/crosapi/mojom/BUILD.gn
+++ b/chromeos/crosapi/mojom/BUILD.gn
@@ -106,6 +106,7 @@
     "web_page_info.mojom",
   ]
   disable_variants = true
+  cpp_only = true
 
   public_deps = [
     "//chromeos/components/remote_apps/mojom",
diff --git a/components/autofill/content/renderer/form_autofill_util.cc b/components/autofill/content/renderer/form_autofill_util.cc
index d1e7399..023bc02 100644
--- a/components/autofill/content/renderer/form_autofill_util.cc
+++ b/components/autofill/content/renderer/form_autofill_util.cc
@@ -18,6 +18,7 @@
 #include "base/containers/contains.h"
 #include "base/containers/flat_map.h"
 #include "base/containers/flat_set.h"
+#include "base/feature_list.h"
 #include "base/i18n/case_conversion.h"
 #include "base/metrics/field_trial.h"
 #include "base/metrics/histogram_functions.h"
@@ -499,6 +500,31 @@
   return std::u16string();
 }
 
+// Detects a label declared after the `element`, which is visually positioned
+// above the element (usually using CCS). Such labels often act as a
+// placeholders. E.g.
+// <div>
+//  <input>
+//  <span>Placeholder</span>
+// </div>
+std::u16string InferLabelFromOverlayingSuccessor(
+    const WebFormControlElement& element) {
+  if (!base::FeatureList::IsEnabled(
+          features::kAutofillSupportPoorMansPlaceholder)) {
+    return std::u16string();
+  }
+
+  WebNode next = element.NextSibling();
+  while (!next.IsNull() && !next.IsElementNode())
+    next = next.NextSibling();
+  if (!next.IsNull()) {
+    gfx::Rect bounds = next.To<WebElement>().BoundsInWidget();
+    if (!bounds.IsEmpty() && element.BoundsInWidget().Contains(bounds))
+      return FindChildText(next);
+  }
+  return std::u16string();
+}
+
 // Helper for |InferLabelForElement()| that infers a label, from
 // the value attribute when it is present and user has not typed in (if
 // element's value attribute is same as the element's value).
@@ -800,6 +826,13 @@
     return true;
   }
 
+  inferred_label = InferLabelFromOverlayingSuccessor(element);
+  if (IsLabelValid(inferred_label)) {
+    label_source = FormFieldData::LabelSource::kOverlayingLabel;
+    label = std::move(inferred_label);
+    return true;
+  }
+
   // If we didn't find a placeholder, check for aria-label text.
   inferred_label = GetAriaLabel(element.GetDocument(), element);
   if (IsLabelValid(inferred_label)) {
diff --git a/components/autofill/content/renderer/form_autofill_util_browsertest.cc b/components/autofill/content/renderer/form_autofill_util_browsertest.cc
index 26791bb..19952796 100644
--- a/components/autofill/content/renderer/form_autofill_util_browsertest.cc
+++ b/components/autofill/content/renderer/form_autofill_util_browsertest.cc
@@ -59,6 +59,22 @@
   const char16_t* expected_label;
 };
 
+// An <input> with a label placed on top of it (usually used as a placeholder
+// replacement).
+const char* kPoorMansPlaceholder = R"(
+  <style>
+    .fixed_position_and_size {
+      position: fixed;
+      top: 0;
+      left: 0;
+      width: 100px;
+      height: 20px;
+    }
+  </style>
+  <input id='target' class=fixed_position_and_size>
+  <span class=fixed_position_and_size>label</span>
+)";
+
 void VerifyButtonTitleCache(const WebFormElement& form_target,
                             const ButtonTitleList& expected_button_titles,
                             const ButtonTitlesCache& actual_cache) {
@@ -172,6 +188,9 @@
 }
 
 TEST_F(FormAutofillUtilsTest, InferLabelForElementTest) {
+  base::test::ScopedFeatureList features;
+  features.InitAndEnableFeature(features::kAutofillSupportPoorMansPlaceholder);
+
   static const AutofillFieldUtilCase test_cases[] = {
       {"DIV table test 1", R"(
        <div>
@@ -215,6 +234,7 @@
        u""},
       {"Infer from next sibling",
        "<input id='target' type='checkbox'>hello <b>world</b>", u"hello world"},
+      {"Poor man's placeholder", kPoorMansPlaceholder, u"label"},
   };
   for (auto test_case : test_cases) {
     SCOPED_TRACE(test_case.description);
@@ -233,6 +253,9 @@
 }
 
 TEST_F(FormAutofillUtilsTest, InferLabelSourceTest) {
+  base::test::ScopedFeatureList features;
+  features.InitAndEnableFeature(features::kAutofillSupportPoorMansPlaceholder);
+
   struct AutofillFieldLabelSourceCase {
     const char* html;
     const FormFieldData::LabelSource label_source;
@@ -263,7 +286,7 @@
        FormFieldData::LabelSource::kTdTag},
       {"<dl><dt>label</dt><dd><input id='target'></dd></dl>",
        FormFieldData::LabelSource::kDdTag},
-  };
+      {kPoorMansPlaceholder, FormFieldData::LabelSource::kOverlayingLabel}};
 
   for (auto test_case : test_cases) {
     SCOPED_TRACE(testing::Message() << test_case.label_source);
diff --git a/components/autofill/core/common/autofill_features.cc b/components/autofill/core/common/autofill_features.cc
index 24da4705..af6d86f7 100644
--- a/components/autofill/core/common/autofill_features.cc
+++ b/components/autofill/core/common/autofill_features.cc
@@ -488,6 +488,13 @@
              "AutofillSkipComparingInferredLabels",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Enables support for artificial placeholders, implemented by placing text on
+// top of the input field using CSS.
+// TODO(crbug.com/1396374): Remove when launched.
+BASE_FEATURE(kAutofillSupportPoorMansPlaceholder,
+             "AutofillSupportPoorMansPlaceholder",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Controls whether Autofill should search prefixes of all words/tokens when
 // filtering profiles, or only on prefixes of the whole string.
 BASE_FEATURE(kAutofillTokenPrefixMatching,
diff --git a/components/autofill/core/common/autofill_features.h b/components/autofill/core/common/autofill_features.h
index db665334..9c093072 100644
--- a/components/autofill/core/common/autofill_features.h
+++ b/components/autofill/core/common/autofill_features.h
@@ -148,6 +148,8 @@
 COMPONENT_EXPORT(AUTOFILL) BASE_DECLARE_FEATURE(kAutofillTokenPrefixMatching);
 COMPONENT_EXPORT(AUTOFILL) BASE_DECLARE_FEATURE(kAutofillUploadThrottling);
 COMPONENT_EXPORT(AUTOFILL)
+BASE_DECLARE_FEATURE(kAutofillSupportPoorMansPlaceholder);
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillUseAlternativeStateNameMap);
 COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillUseImprovedLabelDisambiguation);
diff --git a/components/autofill/core/common/mojom/autofill_types.mojom b/components/autofill/core/common/mojom/autofill_types.mojom
index 46a45154..7e4368f 100644
--- a/components/autofill/core/common/mojom/autofill_types.mojom
+++ b/components/autofill/core/common/mojom/autofill_types.mojom
@@ -284,18 +284,19 @@
 
   // From which source the label is inferred.
   enum LabelSource {
-    kUnknown,  // The source is unknown.
-    kLabelTag,
-    kPTag,
-    kDivTable,
-    kTdTag,
-    kDdTag,
-    kLiTag,
-    kPlaceHolder,
-    kAriaLabel,
-    kCombined,  // Combined with various elements.
-    kValue,     // label is the value of element.
-    kFor,       // Derived from the "for" attribute of a label element.
+    kUnknown,          // No label.
+    kLabelTag,         // <label> sibling/ancestor.
+    kPTag,             // <p> sibling.
+    kDivTable,         // <div> ancestor.
+    kTdTag,            // <td> sibling.
+    kDdTag,            // <dd> sibling.
+    kLiTag,            // <li> ancestor.
+    kPlaceHolder,      // placeholder attribute.
+    kAriaLabel,        // aria-label attribute.
+    kCombined,         // Text node sibling (includes <b>, etc).
+    kValue,            // value attribute.
+    kFor,              // <label> for-attribute
+    kOverlayingLabel,  // Succeeding DOM node overlaying the input.
   };
 
   mojo_base.mojom.String16 label;
diff --git a/components/browser_ui/widget/android/BUILD.gn b/components/browser_ui/widget/android/BUILD.gn
index 9bc7f19..631b786 100644
--- a/components/browser_ui/widget/android/BUILD.gn
+++ b/components/browser_ui/widget/android/BUILD.gn
@@ -148,14 +148,6 @@
     "java/res/anim/chip_out.xml",
     "java/res/anim/image_grid_enter.xml",
     "java/res/anim/image_tile_enter.xml",
-    "java/res/anim/menu_enter.xml",
-    "java/res/anim/menu_enter_from_bottom.xml",
-    "java/res/anim/menu_enter_from_bottom_left.xml",
-    "java/res/anim/menu_enter_from_top_left.xml",
-    "java/res/anim/menu_exit.xml",
-    "java/res/anim/menu_exit_from_bottom.xml",
-    "java/res/anim/menu_exit_from_bottom_left.xml",
-    "java/res/anim/menu_exit_from_top_left.xml",
     "java/res/anim/textbubble_in.xml",
     "java/res/anim/textbubble_out.xml",
     "java/res/color/chip_background_color.xml",
@@ -270,11 +262,9 @@
     "java/res/layout/tile_view_modern.xml",
     "java/res/layout/tile_view_modern_condensed.xml",
     "java/res/layout/title_and_description_layout.xml",
-    "java/res/values-ldrtl/values.xml",
     "java/res/values-night/colors.xml",
     "java/res/values-night/dimens.xml",
     "java/res/values-night/drawables.xml",
-    "java/res/values-sw600dp/dimens.xml",
     "java/res/values-v23/drawables.xml",
     "java/res/values/attrs.xml",
     "java/res/values/colors.xml",
diff --git a/components/browser_ui/widget/android/java/res/values-ldrtl/values.xml b/components/browser_ui/widget/android/java/res/values-ldrtl/values.xml
deleted file mode 100644
index 7c7ab04..0000000
--- a/components/browser_ui/widget/android/java/res/values-ldrtl/values.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-Copyright 2019 The Chromium Authors
-Use of this source code is governed by a BSD-style license that can be
-found in the LICENSE file.
--->
-<resources>
-    <!-- Menu Animation Constants -->
-    <item type="fraction" format="fraction" name="menu_animation_pivot_x">5%</item>
-    <item type="fraction" format="fraction" name="menu_start_animation_pivot_x">95%</item>
-</resources>
diff --git a/components/browser_ui/widget/android/java/res/values-sw600dp/dimens.xml b/components/browser_ui/widget/android/java/res/values-sw600dp/dimens.xml
deleted file mode 100644
index b469f2f1..0000000
--- a/components/browser_ui/widget/android/java/res/values-sw600dp/dimens.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-Copyright 2019 The Chromium Authors
-Use of this source code is governed by a BSD-style license that can be
-found in the LICENSE file.
--->
-
-<resources>
-    <!-- Menu Dimensions -->
-    <!-- Necessary to align the menu icon with the actual button. -->
-    <dimen name="menu_negative_software_vertical_offset">6dp</dimen>
-</resources>
-
diff --git a/components/browser_ui/widget/android/java/res/values/dimens.xml b/components/browser_ui/widget/android/java/res/values/dimens.xml
index c8672d3b..7ab6fd8 100644
--- a/components/browser_ui/widget/android/java/res/values/dimens.xml
+++ b/components/browser_ui/widget/android/java/res/values/dimens.xml
@@ -42,7 +42,6 @@
     <dimen name="list_menu_width">180dp</dimen>
 
     <!-- Custom Menu dimensions -->
-    <dimen name="menu_negative_software_vertical_offset">0dp</dimen>
     <dimen name="menu_divider_padding">8dp</dimen>
     <dimen name="menu_padding_start">16dp</dimen>
 
diff --git a/components/browser_ui/widget/android/java/res/values/styles.xml b/components/browser_ui/widget/android/java/res/values/styles.xml
index c11fd0f..00c37d2 100644
--- a/components/browser_ui/widget/android/java/res/values/styles.xml
+++ b/components/browser_ui/widget/android/java/res/values/styles.xml
@@ -61,22 +61,10 @@
         <item name="android:paddingEnd">@dimen/default_list_row_padding</item>
     </style>
 
-    <style name="EndIconMenuAnim">
-        <item name="android:windowEnterAnimation">@anim/menu_enter</item>
-        <item name="android:windowExitAnimation">@anim/menu_exit</item>
-    </style>
-    <style name="EndIconMenuAnimBottom">
-        <item name="android:windowEnterAnimation">@anim/menu_enter_from_bottom</item>
-        <item name="android:windowExitAnimation">@anim/menu_exit_from_bottom</item>
-    </style>
-    <style name="StartIconMenuAnim">
-        <item name="android:windowEnterAnimation">@anim/menu_enter_from_top_left</item>
-        <item name="android:windowExitAnimation">@anim/menu_exit_from_top_left</item>
-    </style>
-    <style name="StartIconMenuAnimBottom">
-        <item name="android:windowEnterAnimation">@anim/menu_enter_from_bottom_left</item>
-        <item name="android:windowExitAnimation">@anim/menu_exit_from_bottom_left</item>
-    </style>
+    <style name="EndIconMenuAnim" parent="AnchoredPopupAnimEndTop" />
+    <style name="EndIconMenuAnimBottom" parent="AnchoredPopupAnimEndBottom" />
+    <style name="StartIconMenuAnim" parent="AnchoredPopupAnimStartTop" />
+    <style name="StartIconMenuAnimBottom" parent="AnchoredPopupAnimStartBottom" />
 
     <!-- Modern List Item -->
     <style name="ListItemContainer">
diff --git a/components/browser_ui/widget/android/java/res/values/values.xml b/components/browser_ui/widget/android/java/res/values/values.xml
index e80066d..ad03559 100644
--- a/components/browser_ui/widget/android/java/res/values/values.xml
+++ b/components/browser_ui/widget/android/java/res/values/values.xml
@@ -6,10 +6,6 @@
 -->
 
 <resources>
-    <!-- Menu Animation Constants -->
-    <item type="fraction" format="fraction" name="menu_animation_pivot_x">95%</item>
-    <item type="fraction" format="fraction" name="menu_start_animation_pivot_x">5%</item>
-
     <!-- Drag-Reorderable List Item -->
     <!-- Used for 90% = 230/255 alpha -->
     <integer name="list_item_dragged_alpha">230</integer>
diff --git a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/ContextMenuDialog.java b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/ContextMenuDialog.java
index 9ffbf96..e8cea36 100644
--- a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/ContextMenuDialog.java
+++ b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/ContextMenuDialog.java
@@ -188,6 +188,7 @@
                     mPopupWindow.setSmartAnchorWithMaxWidth(true);
                     mPopupWindow.setVerticalOverlapAnchor(true);
                     mPopupWindow.setOutsideTouchable(false);
+                    mPopupWindow.setAnimateFromAnchor(true);
                     // Set popup focusable so the screen reader can announce the popup properly.
                     if (mAccessibilityUtil != null
                             && mAccessibilityUtil.isTouchExplorationEnabled()) {
diff --git a/components/history_clusters/core/config.cc b/components/history_clusters/core/config.cc
index 94ab0f1..c7c61df 100644
--- a/components/history_clusters/core/config.cc
+++ b/components/history_clusters/core/config.cc
@@ -107,6 +107,9 @@
             "JourneysPersistClustersInHistoryDbPeriodMinutes",
             persist_clusters_in_history_db_period_minutes);
 
+    persist_on_query = base::GetFieldTrialParamByFeatureAsBool(
+        internal::kPersistedClusters, "persist_on_query", persist_on_query);
+
     max_persisted_clusters_to_fetch = base::GetFieldTrialParamByFeatureAsInt(
         internal::kPersistedClusters, "max_persisted_clusters_to_fetch",
         max_persisted_clusters_to_fetch);
diff --git a/components/history_clusters/core/config.h b/components/history_clusters/core/config.h
index 42c0800..69edc4e 100644
--- a/components/history_clusters/core/config.h
+++ b/components/history_clusters/core/config.h
@@ -107,6 +107,16 @@
   // every hour.
   int persist_clusters_in_history_db_period_minutes = 60;
 
+  // No effect if `persist_clusters_in_history_db` is disabled. If disabled,
+  // persistence occurs on a timer (see the above 2 params). If enabled, will
+  // instead occur on query like refreshing the keyword cache does. This may
+  // help bound the number of persistence requests. If enabled, will continue to
+  // also be capped to at most 1 request per
+  // `persist_clusters_in_history_db_period_minutes`, but
+  // `persist_clusters_in_history_db_after_startup_delay_minutes` will be
+  // unused.
+  bool persist_on_query = false;
+
   // Hard cap on max clusters to fetch after exhausting unclustered visits and
   // fetching persisted clusters for the get most recent flow. Doesn't affect
   // the update flow, which uses day boundaries as well as
diff --git a/components/history_clusters/core/history_clusters_service.cc b/components/history_clusters/core/history_clusters_service.cc
index ba1407e2..e079b85e 100644
--- a/components/history_clusters/core/history_clusters_service.cc
+++ b/components/history_clusters/core/history_clusters_service.cc
@@ -184,8 +184,13 @@
 }
 
 void HistoryClustersService::RepeatedlyUpdateClusters() {
-  if (!GetConfig().persist_clusters_in_history_db)
+  // If `persist_on_query` is enabled, clusters are updated on query and not on
+  // a timer.
+  if (!GetConfig().persist_clusters_in_history_db ||
+      GetConfig().persist_on_query) {
     return;
+  }
+
   // Update clusters, both periodically and once after startup because:
   // 1) To avoid having very stale (up to 90 days) clusters for the initial
   //    period after startup.
@@ -205,8 +210,38 @@
 
 void HistoryClustersService::UpdateClusters() {
   DCHECK(history_service_);
+
+  // TODO(manukh): This logic (if task not done, if time since < period) is
+  //  repeated for both keyword caches and here. Should probably share it in
+  //  some kind of base task that all 3 inherit from.
+
   if (update_clusters_task_ && !update_clusters_task_->Done())
     return;
+
+  // Make sure clusters aren't updated too frequently. If `persist_on_query` is
+  // false, this is already ensured by `update_clusters_period_timer_`. If
+  // update_clusters_task_ is null, this is the 1st request which shouldn't be
+  // delayed.
+  if (GetConfig().persist_on_query &&
+      update_clusters_timer_.Elapsed() <=
+          base::Minutes(
+              GetConfig().persist_clusters_in_history_db_period_minutes) &&
+      update_clusters_task_) {
+    return;
+  }
+
+  // Using custom histogram as this occurs too infrequently to be captured by
+  // the built in histograms. `persist_clusters_in_history_db_period_minutes`
+  // ranges from 1 to 12 hours while the built in timing histograms go up to 1
+  // hr.
+  base::UmaHistogramCustomTimes(
+      "History.Clusters.UpdateClusters.TimeBetweenTasks",
+      update_clusters_timer_.Elapsed(), base::Minutes(60), base::Hours(48),
+      100);
+
+  // Reset the timer.
+  update_clusters_timer_ = {};
+
   update_clusters_task_ =
       std::make_unique<HistoryClustersServiceTaskUpdateClusters>(
           weak_ptr_factory_.GetWeakPtr(), incomplete_visit_context_annotations_,
@@ -223,6 +258,8 @@
     return absl::nullopt;
 
   StartKeywordCacheRefresh();
+  if (GetConfig().persist_on_query)
+    UpdateClusters();
 
   // Early exit for single-character queries, even if it's an exact match.
   // We still want to allow for two-character exact matches like "uk".
@@ -254,6 +291,8 @@
     return false;
 
   StartKeywordCacheRefresh();
+  if (GetConfig().persist_on_query)
+    UpdateClusters();
 
   return short_url_keywords_cache_.find(url_keyword) !=
              short_url_keywords_cache_.end() ||
diff --git a/components/history_clusters/core/history_clusters_service.h b/components/history_clusters/core/history_clusters_service.h
index 96ab6d32..e40926e 100644
--- a/components/history_clusters/core/history_clusters_service.h
+++ b/components/history_clusters/core/history_clusters_service.h
@@ -210,6 +210,11 @@
   friend class HistoryClustersServiceTestApi;
 
   // Starts a keyword cache refresh, if necessary.
+  // TODO(manukh): `StartKeywordCacheRefresh()` and
+  //  `PopulateClusterKeywordCache()` should be encapsulated into their own task
+  //  to avoid cluttering `HistoryClusterService` with their callbacks. Similar
+  //  to the `HistoryClustersServiceTaskGetMostRecentClusters` and
+  //  `HistoryClustersServiceTaskUpdateClusters` tasks.
   void StartKeywordCacheRefresh();
 
   // This is a callback used for the `QueryClusters()` call from
@@ -281,6 +286,10 @@
   // `RepeatedlyUpdateClusters()`'s comment.
   base::RepeatingTimer update_clusters_period_timer_;
 
+  // The time of the last `UpdateClusters()` call. Used for logging and to limit
+  // requests when `persist_on_query` is enabled.
+  base::ElapsedTimer update_clusters_timer_;
+
   // A list of observers for this service.
   base::ObserverList<Observer> observers_;
 
diff --git a/components/keyed_service/content/browser_context_keyed_service_factory.cc b/components/keyed_service/content/browser_context_keyed_service_factory.cc
index 417ff7d..b2d52be 100644
--- a/components/keyed_service/content/browser_context_keyed_service_factory.cc
+++ b/components/keyed_service/content/browser_context_keyed_service_factory.cc
@@ -87,12 +87,24 @@
 }
 
 std::unique_ptr<KeyedService>
+BrowserContextKeyedServiceFactory::BuildServiceInstanceForBrowserContext(
+    content::BrowserContext* context) const {
+  // TODO(tsepez): fully deprecate the form below.
+  return base::WrapUnique(BuildServiceInstanceFor(context));
+}
+
+KeyedService* BrowserContextKeyedServiceFactory::BuildServiceInstanceFor(
+    content::BrowserContext* context) const {
+  // Stub to prevent converted sub-classes from needing to implement this form.
+  NOTREACHED();
+  return nullptr;
+}
+
+std::unique_ptr<KeyedService>
 BrowserContextKeyedServiceFactory::BuildServiceInstanceFor(
     void* context) const {
-  // TODO(isherman): The wrapped BuildServiceInstanceFor() should return a
-  // scoped_ptr as well.
-  return base::WrapUnique(
-      BuildServiceInstanceFor(static_cast<content::BrowserContext*>(context)));
+  return BuildServiceInstanceForBrowserContext(
+      static_cast<content::BrowserContext*>(context));
 }
 
 bool BrowserContextKeyedServiceFactory::IsOffTheRecord(void* context) const {
diff --git a/components/keyed_service/content/browser_context_keyed_service_factory.h b/components/keyed_service/content/browser_context_keyed_service_factory.h
index f546244..dda3f0c 100644
--- a/components/keyed_service/content/browser_context_keyed_service_factory.h
+++ b/components/keyed_service/content/browser_context_keyed_service_factory.h
@@ -141,8 +141,15 @@
   //
   // This should not return nullptr; instead, return nullptr from
   // `GetBrowserContextToUse()`.
+  //
+  // Sub-classes implement one of these two forms:
+  virtual std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext(
+      content::BrowserContext* context) const;
+
+  // DEPRECATED: allows incremental conversion to the unique_ptr<> form
+  // above. New code should not be using this form.
   virtual KeyedService* BuildServiceInstanceFor(
-      content::BrowserContext* context) const = 0;
+      content::BrowserContext* context) const;
 
   // A helper object actually listens for notifications about BrowserContext
   // destruction, calculates the order in which things are destroyed and then
diff --git a/components/management_strings.grdp b/components/management_strings.grdp
index 7ede678..153625b6 100644
--- a/components/management_strings.grdp
+++ b/components/management_strings.grdp
@@ -61,20 +61,23 @@
     <if expr="not chromeos_ash">
       <if expr="_google_chrome">
         <message name="IDS_MANAGEMENT_BROWSER_NOTICE" desc="Message shown when the (Google-branded) Chrome browser is managed, it indicates what the administrator can do on the browser.">
-        Your administrator can change your browser setup remotely. Activity on this device may also be managed outside of Chrome. <ph name="BEGIN_LINK">&lt;a target="_blank" href="$1"&gt;</ph>Learn more<ph name="END_LINK">&lt;/a&gt;</ph>
+        Your administrator can change your browser setup remotely. Activity on this device may also be managed outside of Chrome. <ph name="BEGIN_LINK">&lt;a target="_blank" href="$1" aria-label="$2"&gt;</ph>Learn more<ph name="END_LINK">&lt;/a&gt;</ph>
         </message>
         <message name="IDS_MANAGEMENT_NOT_MANAGED_NOTICE" desc="Message indicating that the (Google-branded) Chrome browser is not managed">
-        This browser is not managed by a company or other organization. Activity on this device may be managed outside of Chrome. <ph name="BEGIN_LINK">&lt;a target="_blank" href="$1"&gt;</ph>Learn more<ph name="END_LINK">&lt;/a&gt;</ph>
+        This browser is not managed by a company or other organization. Activity on this device may be managed outside of Chrome. <ph name="BEGIN_LINK">&lt;a target="_blank" href="$1" aria-label="$2"&gt;</ph>Learn more<ph name="END_LINK">&lt;/a&gt;</ph>
         </message>
       </if>
       <if expr="not _google_chrome">
         <message name="IDS_MANAGEMENT_BROWSER_NOTICE" desc="Message shown when the (non-Google-branded) Chromium browser is managed, it indicates what the administrator can do on the browser.">
-        Your administrator can change your browser setup remotely. Activity on this device may also be managed outside of Chromium. <ph name="BEGIN_LINK">&lt;a target="_blank" href="$1"&gt;</ph>Learn more<ph name="END_LINK">&lt;/a&gt;</ph>
+        Your administrator can change your browser setup remotely. Activity on this device may also be managed outside of Chromium. <ph name="BEGIN_LINK">&lt;a target="_blank" href="$1" aria-label="$2"&gt;</ph>Learn more<ph name="END_LINK">&lt;/a&gt;</ph>
         </message>
         <message name="IDS_MANAGEMENT_NOT_MANAGED_NOTICE" desc="Message indicating that the (non-Google-branded) Chromium browser is not managed">
-        This browser is not managed by a company or other organization. Activity on this device may be managed outside of Chromium. <ph name="BEGIN_LINK">&lt;a target="_blank" href="$1"&gt;</ph>Learn more<ph name="END_LINK">&lt;/a&gt;</ph>
+        This browser is not managed by a company or other organization. Activity on this device may be managed outside of Chromium. <ph name="BEGIN_LINK">&lt;a target="_blank" href="$1" aria-label="$2"&gt;</ph>Learn more<ph name="END_LINK">&lt;/a&gt;</ph>
         </message>
       </if>
+      <message name="IDS_MANAGEMENT_LEARN_MORE_ACCCESSIBILITY_TEXT" desc="Accessibility text for a 'Learn more' link that links to a help article about how the browser is managed.">
+        Learn more about how your browser is managed
+      </message>
     </if>
   </if>
 
diff --git a/components/management_strings_grdp/IDS_MANAGEMENT_BROWSER_NOTICE.png.sha1 b/components/management_strings_grdp/IDS_MANAGEMENT_BROWSER_NOTICE.png.sha1
index 2ea1997..16e39345 100644
--- a/components/management_strings_grdp/IDS_MANAGEMENT_BROWSER_NOTICE.png.sha1
+++ b/components/management_strings_grdp/IDS_MANAGEMENT_BROWSER_NOTICE.png.sha1
@@ -1 +1 @@
-ad0a9ae496e2d8a74660cd371bcc9acc79b475a2
\ No newline at end of file
+4a860cd9622b611c3b76432b7d4b84ed1eeb0460
\ No newline at end of file
diff --git a/components/management_strings_grdp/IDS_MANAGEMENT_LEARN_MORE_ACCCESSIBILITY_TEXT.png.sha1 b/components/management_strings_grdp/IDS_MANAGEMENT_LEARN_MORE_ACCCESSIBILITY_TEXT.png.sha1
new file mode 100644
index 0000000..4ecf846
--- /dev/null
+++ b/components/management_strings_grdp/IDS_MANAGEMENT_LEARN_MORE_ACCCESSIBILITY_TEXT.png.sha1
@@ -0,0 +1 @@
+6734674614d77faf8b2ac866ca1da0c22d06a495
\ No newline at end of file
diff --git a/components/management_strings_grdp/IDS_MANAGEMENT_NOT_MANAGED_NOTICE.png.sha1 b/components/management_strings_grdp/IDS_MANAGEMENT_NOT_MANAGED_NOTICE.png.sha1
index 20ed25a..84b7d02 100644
--- a/components/management_strings_grdp/IDS_MANAGEMENT_NOT_MANAGED_NOTICE.png.sha1
+++ b/components/management_strings_grdp/IDS_MANAGEMENT_NOT_MANAGED_NOTICE.png.sha1
@@ -1 +1 @@
-1a56c5e54aeb312486f7f3b4564cc6f64525aac3
\ No newline at end of file
+17731ccf8561565d284cbd30a16f7d9a88a5c8f5
\ No newline at end of file
diff --git a/components/media_router/browser/android/media_router_android.h b/components/media_router/browser/android/media_router_android.h
index c53880a..69186a6 100644
--- a/components/media_router/browser/android/media_router_android.h
+++ b/components/media_router/browser/android/media_router_android.h
@@ -168,7 +168,7 @@
                          std::unique_ptr<MediaSinksObserverList>>;
   MediaSinkObservers sinks_observers_;
 
-  base::ObserverList<MediaRoutesObserver>::Unchecked routes_observers_;
+  base::ObserverList<MediaRoutesObserver> routes_observers_;
 
   struct MediaRouteRequest {
     MediaRouteRequest(const MediaSource& source,
diff --git a/components/media_router/browser/media_routes_observer.cc b/components/media_router/browser/media_routes_observer.cc
index b94975c..0b68da1 100644
--- a/components/media_router/browser/media_routes_observer.cc
+++ b/components/media_router/browser/media_routes_observer.cc
@@ -6,16 +6,20 @@
 
 #include "base/check.h"
 #include "components/media_router/browser/media_router.h"
+#include "content/public/browser/browser_thread.h"
 
 namespace media_router {
 
 MediaRoutesObserver::MediaRoutesObserver(MediaRouter* router)
     : router_(router) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   DCHECK(router_);
   router_->RegisterMediaRoutesObserver(this);
 }
 
 MediaRoutesObserver::~MediaRoutesObserver() {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  CHECK(IsInObserverList());
   router_->UnregisterMediaRoutesObserver(this);
 }
 
diff --git a/components/media_router/browser/media_routes_observer.h b/components/media_router/browser/media_routes_observer.h
index c7fb0cc..251fbb8 100644
--- a/components/media_router/browser/media_routes_observer.h
+++ b/components/media_router/browser/media_routes_observer.h
@@ -8,6 +8,8 @@
 #include <vector>
 
 #include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list_types.h"
 #include "components/media_router/common/media_route.h"
 
 namespace media_router {
@@ -20,14 +22,17 @@
 // |OnRoutesUpdated| that match the route IDs contained in the
 // |joinable_route_ids| can be connected joined by the source.  If no
 // |source_id| is supplied, then the idea of joinable routes no longer applies.
-class MediaRoutesObserver {
+class MediaRoutesObserver : public base::CheckedObserver,
+                            public base::SupportsWeakPtr<MediaRoutesObserver> {
  public:
   explicit MediaRoutesObserver(MediaRouter* router);
 
   MediaRoutesObserver(const MediaRoutesObserver&) = delete;
   MediaRoutesObserver& operator=(const MediaRoutesObserver&) = delete;
 
-  virtual ~MediaRoutesObserver();
+  // NOTE: must be destroyed on the Browser UI thread to avoid threading issues
+  // with access.
+  ~MediaRoutesObserver() override;
 
   // Invoked when the list of routes and their associated sinks have been
   // updated.
diff --git a/components/media_router/browser/test/mock_media_router.h b/components/media_router/browser/test/mock_media_router.h
index 6235c7b..ff2f589 100644
--- a/components/media_router/browser/test/mock_media_router.h
+++ b/components/media_router/browser/test/mock_media_router.h
@@ -10,9 +10,11 @@
 #include <string>
 #include <vector>
 
+#include "base/observer_list.h"
 #include "build/build_config.h"
 #include "components/media_router/browser/logger_impl.h"
 #include "components/media_router/browser/media_router_base.h"
+#include "components/media_router/browser/media_routes_observer.h"
 #include "components/media_router/common/media_route.h"
 #include "components/media_router/common/media_sink.h"
 #include "components/media_router/common/media_source.h"
@@ -127,16 +129,25 @@
   MOCK_METHOD1(RegisterMediaSinksObserver, bool(MediaSinksObserver* observer));
   MOCK_METHOD1(UnregisterMediaSinksObserver,
                void(MediaSinksObserver* observer));
-  MOCK_METHOD1(RegisterMediaRoutesObserver,
-               void(MediaRoutesObserver* observer));
-  MOCK_METHOD1(UnregisterMediaRoutesObserver,
-               void(MediaRoutesObserver* observer));
+  void RegisterMediaRoutesObserver(MediaRoutesObserver* observer) override {
+    routes_observers_.AddObserver(observer);
+  }
+  void UnregisterMediaRoutesObserver(MediaRoutesObserver* observer) override {
+    routes_observers_.RemoveObserver(observer);
+  }
   MOCK_METHOD1(RegisterPresentationConnectionMessageObserver,
                void(PresentationConnectionMessageObserver* observer));
   MOCK_METHOD1(UnregisterPresentationConnectionMessageObserver,
                void(PresentationConnectionMessageObserver* observer));
   MOCK_METHOD0(GetMediaSinkServiceStatus, std::string());
 
+  base::ObserverList<MediaRoutesObserver>& routes_observers() {
+    return routes_observers_;
+  }
+
+ protected:
+  base::ObserverList<MediaRoutesObserver> routes_observers_;
+
  private:
 #if !BUILDFLAG(IS_ANDROID)
   IssueManager issue_manager_;
diff --git a/components/messages/android/internal/java/res/layout/message_banner_view.xml b/components/messages/android/internal/java/res/layout/message_banner_view.xml
index ef8b723d..a2824757 100644
--- a/components/messages/android/internal/java/res/layout/message_banner_view.xml
+++ b/components/messages/android/internal/java/res/layout/message_banner_view.xml
@@ -71,7 +71,7 @@
         android:visibility="gone"
         android:contentDescription="@null"
         android:background="?actionBarItemBackground"
-        android:layout_width="@dimen/message_banner_button_width"
+        android:layout_width="@dimen/min_touch_target_size"
         android:layout_height="@dimen/min_touch_target_size" />
 
     <ImageView
diff --git a/components/messages/android/internal/java/res/values/dimens.xml b/components/messages/android/internal/java/res/values/dimens.xml
index 337256a..9cd54a1 100644
--- a/components/messages/android/internal/java/res/values/dimens.xml
+++ b/components/messages/android/internal/java/res/values/dimens.xml
@@ -14,8 +14,6 @@
     <dimen name="message_banner_elevation">6dp</dimen>
     <!-- For messages below the front messages, used in stacking animation. -->
     <dimen name="message_banner_back_elevation">5dp</dimen>
-    <!-- Min width to ensure the min touchable size. -->
-    <dimen name="message_banner_button_width">44dp</dimen>
     <dimen name="message_primary_button_max_width">100dp</dimen>
     <dimen name="message_icon_margin">12dp</dimen>
     <dimen name="message_icon_size">24dp</dimen>
diff --git a/components/omnibox/browser/omnibox_prefs.cc b/components/omnibox/browser/omnibox_prefs.cc
index 9c93f02..efe11e3 100644
--- a/components/omnibox/browser/omnibox_prefs.cc
+++ b/components/omnibox/browser/omnibox_prefs.cc
@@ -83,8 +83,8 @@
     SuggestionGroupVisibility visibility) {
   DCHECK(prefs);
 
-  DictionaryPrefUpdate update(prefs, kSuggestionGroupVisibility);
-  update->SetIntKey(base::NumberToString(suggestion_group_id), visibility);
+  ScopedDictPrefUpdate update(prefs, kSuggestionGroupVisibility);
+  update->Set(base::NumberToString(suggestion_group_id), visibility);
 
   base::SparseHistogram::FactoryGet(
       visibility == SuggestionGroupVisibility::SHOWN
diff --git a/components/payments/content/BUILD.gn b/components/payments/content/BUILD.gn
index 2b3eeae..0846734 100644
--- a/components/payments/content/BUILD.gn
+++ b/components/payments/content/BUILD.gn
@@ -88,7 +88,7 @@
   ]
 
   if (is_chromeos_ash) {
-    sources += [ "android_app_communication_chrome_os.cc" ]
+    sources += [ "android_app_communication_ash.cc" ]
 
     deps += [
       "//ash/components/arc",
diff --git a/components/payments/content/android_app_communication_chrome_os.cc b/components/payments/content/android_app_communication_ash.cc
similarity index 96%
rename from components/payments/content/android_app_communication_chrome_os.cc
rename to components/payments/content/android_app_communication_ash.cc
index 3a8d4da..2710288d 100644
--- a/components/payments/content/android_app_communication_chrome_os.cc
+++ b/components/payments/content/android_app_communication_ash.cc
@@ -155,8 +155,9 @@
   // at this time.
   auto supported_method_iterator =
       stringified_method_data.find(methods::kGooglePlayBilling);
-  if (supported_method_iterator == stringified_method_data.end())
+  if (supported_method_iterator == stringified_method_data.end()) {
     return nullptr;
+  }
 
   // Chrome OS TWA supports only one set of payment method specific data.
   if (supported_method_iterator->second.size() > 1) {
@@ -193,20 +194,19 @@
 }
 
 // Invokes the TWA Android app in Android subsystem on Chrome OS.
-class AndroidAppCommunicationChromeOS : public AndroidAppCommunication {
+class AndroidAppCommunicationAsh : public AndroidAppCommunication {
  public:
-  explicit AndroidAppCommunicationChromeOS(content::BrowserContext* context)
+  explicit AndroidAppCommunicationAsh(content::BrowserContext* context)
       : AndroidAppCommunication(context),
         get_app_service_(base::BindRepeating(
             &arc::ArcPaymentAppBridge::GetForBrowserContext)) {}
 
-  ~AndroidAppCommunicationChromeOS() override = default;
+  ~AndroidAppCommunicationAsh() override = default;
 
   // Disallow copy and assign.
-  AndroidAppCommunicationChromeOS(
-      const AndroidAppCommunicationChromeOS& other) = delete;
-  AndroidAppCommunicationChromeOS& operator=(
-      const AndroidAppCommunicationChromeOS& other) = delete;
+  AndroidAppCommunicationAsh(const AndroidAppCommunicationAsh& other) = delete;
+  AndroidAppCommunicationAsh& operator=(
+      const AndroidAppCommunicationAsh& other) = delete;
 
   // AndroidAppCommunication implementation:
   void GetAppDescriptions(const std::string& twa_package_name,
@@ -371,7 +371,7 @@
 // static
 std::unique_ptr<AndroidAppCommunication> AndroidAppCommunication::Create(
     content::BrowserContext* context) {
-  return std::make_unique<AndroidAppCommunicationChromeOS>(context);
+  return std::make_unique<AndroidAppCommunicationAsh>(context);
 }
 
 }  // namespace payments
diff --git a/components/policy/resources/templates/policies.yaml b/components/policy/resources/templates/policies.yaml
index 68770da1..ca758916 100644
--- a/components/policy/resources/templates/policies.yaml
+++ b/components/policy/resources/templates/policies.yaml
@@ -1043,6 +1043,7 @@
   1042: ShowCastSessionsStartedByOtherDevices
   1043: CloudAPAuthEnabled
   1044: UsbDetectorNotificationEnabled
+  1045: LacrosSelection
 atomic_groups:
   1: Homepage
   2: RemoteAccess
diff --git a/components/policy/resources/templates/policy_definitions/Miscellaneous/LacrosSelection.yaml b/components/policy/resources/templates/policy_definitions/Miscellaneous/LacrosSelection.yaml
new file mode 100644
index 0000000..0850325
--- /dev/null
+++ b/components/policy/resources/templates/policy_definitions/Miscellaneous/LacrosSelection.yaml
@@ -0,0 +1,66 @@
+caption: Select <ph name="LACROS_NAME">Lacros</ph> browser binary
+default: user_choice
+default_for_enterprise_users: rootfs
+desc: |-
+  This setting configures which <ph name="LACROS_NAME">Lacros</ph> browser to use.
+
+  If the policy is set to <ph name="LACROS_SELECTION_USER_CHOICE_VALUE">user_choice</ph>,
+  the user can decide which <ph name="LACROS_NAME">Lacros</ph> browser to load: binary from
+  <ph name="LACROS_ROOTFS_NAME">rootfs</ph> or <ph name="LACROS_STATEFUL_NAME">stateful</ph> partition.
+  If the user has not set any preference, the binary with the newest version will be chosen.
+
+  If the policy is set to <ph name="LACROS_SELECTION_ROOTFS_VALUE">rootfs</ph>,
+  always load <ph name="LACROS_ROOTFS_NAME">rootfs</ph> binary of
+  <ph name="LACROS_NAME">Lacros</ph> browser.
+
+  If the policy is set to <ph name="LACROS_SELECTION_STATEFUL_VALUE">stateful</ph>,
+  always load <ph name="LACROS_STATEFUL_NAME">stateful</ph> binary of
+  <ph name="LACROS_NAME">Lacros</ph> browser.
+  Using this value may cause unexpected behavior if the <ph name="LACROS_STATEFUL_NAME">stateful</ph>
+  browser version becomes older than the <ph name="PRODUCT_OS_NAME">$2<ex>Google ChromeOS</ex></ph> version.
+  This case is not supported and is not guaranteed to work correctly.
+
+  If the policy is unset, the default is <ph name="LACROS_SELECTION_ROOTFS_VALUE">rootfs</ph> for enterprise-managed users and
+  <ph name="LACROS_SELECTION_USER_CHOICE_VALUE">user_choice</ph> for non-managed users.
+
+  Note that changing the policy's value may cause
+  <ph name="LACROS_NAME">Lacros</ph> browser's data loss if the browser's
+  version it changes to is older than the current one. For example, if the
+  policy changes from <ph name="LACROS_SELECTION_STATEFUL_VALUE">stateful</ph>
+  to <ph name="LACROS_SELECTION_ROOTFS_VALUE">rootfs</ph>, and the first one was
+  updated. Or if <ph name="PRODUCT_OS_NAME">$2<ex>Google ChromeOS</ex></ph> was
+  updated together with <ph name="LACROS_ROOTFS_NAME">rootfs</ph>
+  <ph name="LACROS_NAME">Lacros</ph> browser, and
+  <ph name="LACROS_STATEFUL_NAME">stateful</ph> has not been updated yet.
+  In such scenarios the correct data migration is not guaranteed.
+
+  Using <ph name="LACROS_SELECTION_USER_CHOICE_VALUE">user_choice</ph> or <ph name="LACROS_SELECTION_ROOTFS_VALUE">rootfs</ph>
+  is a safe option. Switching from <ph name="LACROS_SELECTION_ROOTFS_VALUE">rootfs</ph> to <ph name="LACROS_SELECTION_USER_CHOICE_VALUE">user_choice</ph>
+  is safe as well.
+
+example_value: stateful
+features:
+  dynamic_refresh: false
+  per_profile: false
+items:
+- caption: Allow users to select <ph name="LACROS_NAME">Lacros</ph> browser binary
+  name: user_choice
+  value: user_choice
+- caption: Always load <ph name="LACROS_ROOTFS_NAME">rootfs</ph> <ph name="LACROS_NAME">Lacros</ph> browser
+  name: rootfs
+  value: rootfs
+- caption: Always load <ph name="LACROS_STATEFUL_NAME">stateful</ph> <ph name="LACROS_NAME">Lacros</ph> browser
+  name: stateful
+  value: stateful
+owners:
+- asumaneev@google.com
+schema:
+  enum:
+  - user_choice
+  - rootfs
+  - stateful
+  type: string
+supported_on:
+- chrome_os:110-
+tags: []
+type: string-enum
diff --git a/components/policy_strings.grdp b/components/policy_strings.grdp
index d189d8e..f4c2dc75 100644
--- a/components/policy_strings.grdp
+++ b/components/policy_strings.grdp
@@ -326,6 +326,9 @@
   <message name="IDS_POLICY_DEPENDENCY_ERROR" desc="The text displayed in the status column when a policy is ignored as another policies is not set properly.">
     Ignored because <ph name="POLICY_NAME">$1<ex>CloudReportingEnabled</ex></ph> is not set to <ph name="VALUE">$2<ex>Enabled</ex></ph>.
   </message>
+  <message name="IDS_POLICY_DEPENDENCY_ERROR_ANY_VALUE" desc="The text displayed in the status column when a policy is ignored as another policies is not set at all.">
+    Ignored because <ph name="POLICY_NAME">$1<ex>CloudReportingEnabled</ex></ph> is not set.
+  </message>
   <message name="IDS_POLICY_USER_IS_NOT_AFFILIATED_ERROR" desc="The text displayed in the status column when a user cloud policy is ignored due to user is not affiliated to the machine management.">
     Ignored because user is not affiliated to the machine management or the machine is not managed.
   </message>
diff --git a/components/policy_strings_grdp/IDS_POLICY_DEPENDENCY_ERROR_ANY_VALUE.png.sha1 b/components/policy_strings_grdp/IDS_POLICY_DEPENDENCY_ERROR_ANY_VALUE.png.sha1
new file mode 100644
index 0000000..face0ce
--- /dev/null
+++ b/components/policy_strings_grdp/IDS_POLICY_DEPENDENCY_ERROR_ANY_VALUE.png.sha1
@@ -0,0 +1 @@
+6e7315f3cbbe1df993996f5128e7b2e3c01d2b71
\ No newline at end of file
diff --git a/components/remote_cocoa/app_shim/immersive_mode_controller.h b/components/remote_cocoa/app_shim/immersive_mode_controller.h
index 940775a1..299777e 100644
--- a/components/remote_cocoa/app_shim/immersive_mode_controller.h
+++ b/components/remote_cocoa/app_shim/immersive_mode_controller.h
@@ -59,7 +59,8 @@
   void RevealUnlock();
   int reveal_lock_count() { return reveal_lock_count_; }
 
-  NSWindow* overlay_widget() { return overlay_widget_; }
+  NSWindow* browser_window() { return browser_window_; }
+  NSWindow* overlay_window() { return overlay_window_; }
 
  private:
   // Pin or unpin the titlebar.
@@ -73,8 +74,8 @@
 
   bool enabled_ = false;
 
-  NSWindow* const browser_widget_;
-  NSWindow* const overlay_widget_;
+  NSWindow* const browser_window_;
+  NSWindow* const overlay_window_;
 
   // A controller for top chrome.
   base::scoped_nsobject<ImmersiveModeTitlebarViewController>
@@ -127,13 +128,12 @@
 //
 // This class will keep the position of the overlay window in sync with its
 // original content (top chrome).
-REMOTE_COCOA_APP_SHIM_EXPORT @interface ImmersiveModeTitlebarObserver
-    : NSObject {
-  NSWindow* _overlay_window;
-  NSView* _overlay_view;
-}
-- (instancetype)initWithOverlayWindow:(NSWindow*)overlay_window
-                          overlayView:(NSView*)overlay_view;
+REMOTE_COCOA_APP_SHIM_EXPORT @interface ImmersiveModeTitlebarObserver : NSObject
+
+- (instancetype)initWithController:
+                    (base::WeakPtr<remote_cocoa::ImmersiveModeController>)
+                        controller
+                       overlayView:(NSView*)overlay_view;
 @end
 
 #endif  // COMPONENTS_REMOTE_COCOA_APP_SHIM_IMMERSIVE_MODE_CONTROLLER_H_
diff --git a/components/remote_cocoa/app_shim/immersive_mode_controller.mm b/components/remote_cocoa/app_shim/immersive_mode_controller.mm
index d9cf7a8..2f17d51 100644
--- a/components/remote_cocoa/app_shim/immersive_mode_controller.mm
+++ b/components/remote_cocoa/app_shim/immersive_mode_controller.mm
@@ -4,6 +4,7 @@
 
 #include "components/remote_cocoa/app_shim/immersive_mode_controller.h"
 
+#include "base/auto_reset.h"
 #include "base/check.h"
 #include "base/mac/foundation_util.h"
 #include "base/mac/scoped_block.h"
@@ -38,13 +39,22 @@
 
 }  // namespace
 
+@interface ImmersiveModeTitlebarObserver () {
+  base::WeakPtr<remote_cocoa::ImmersiveModeController> _controller;
+  NSView* _overlay_view;
+  BOOL _barrier;
+}
+@end
+
 @implementation ImmersiveModeTitlebarObserver
 
-- (instancetype)initWithOverlayWindow:(NSWindow*)overlay_window
-                          overlayView:(NSView*)overlay_view {
+- (instancetype)initWithController:
+                    (base::WeakPtr<remote_cocoa::ImmersiveModeController>)
+                        controller
+                       overlayView:(NSView*)overlay_view {
   self = [super init];
   if (self) {
-    _overlay_window = overlay_window;
+    _controller = std::move(controller);
     _overlay_view = overlay_view;
   }
   return self;
@@ -80,9 +90,29 @@
   if (_overlay_view.visibleRect.size.height !=
       _overlay_view.frame.size.height) {
     point_on_screen.y = -_overlay_view.frame.size.height;
+  } else {
+    // If there are sub-windows and the titlebar is fully visible (a y origin of
+    // 0), pin the titlebar. This will prevent the titlebar from autohiding and
+    // causing the sub-windows from moving up when the mouse leaves top chrome.
+    NSRect frame = [change[@"new"] rectValue];
+    if (!_barrier && frame.origin.y == 0 &&
+        _controller->titlebar_lock_count() > 0) {
+      // Add a barrier to prevent re-entry, which is a byproduct of
+      // TitlebarLock() and TitlebarUnlock().
+      base::AutoReset<BOOL> set_barrier(&_barrier, YES);
+      // This lock / unlock scheme is to force the titlebar to be pinned in
+      // place, which can only be done when the titlebar is fully visible.
+      // Existing sub-windows hold a lock, however since the titlebar isn't
+      // fully revealed until this point the existing locks don't actually pin
+      // the titlebar. The existing locks are still important for knowing when
+      // to unpin the titlebar. When all outstanding locks are released the
+      // titlebar be unpinned.
+      _controller->TitlebarLock();
+      _controller->TitlebarUnlock();
+    }
   }
 
-  [_overlay_window setFrameOrigin:point_on_screen];
+  [_controller->overlay_window() setFrameOrigin:point_on_screen];
 }
 
 @end
@@ -284,8 +314,8 @@
 ImmersiveModeController::ImmersiveModeController(NSWindow* browser_widget,
                                                  NSWindow* overlay_widget,
                                                  base::OnceClosure callback)
-    : browser_widget_(browser_widget),
-      overlay_widget_(overlay_widget),
+    : browser_window_(browser_widget),
+      overlay_window_(overlay_widget),
       weak_ptr_factory_(this) {
   immersive_mode_window_observer_.reset([[ImmersiveModeWindowObserver alloc]
       initWithController:weak_ptr_factory_.GetWeakPtr()]);
@@ -295,20 +325,20 @@
   // bit. We do not want a separator. Pre-macOS 11 there is no titlebar
   // separator.
   if (@available(macOS 11.0, *)) {
-    browser_widget_.titlebarSeparatorStyle = NSTitlebarSeparatorStyleNone;
+    browser_window_.titlebarSeparatorStyle = NSTitlebarSeparatorStyleNone;
   }
 
   // Create a new NSTitlebarAccessoryViewController that will host the
   // overlay_view_.
   immersive_mode_titlebar_view_controller_.reset(
       [[ImmersiveModeTitlebarViewController alloc]
-           initWithOverlayWindow:overlay_widget_
+           initWithOverlayWindow:overlay_window_
           viewWillAppearCallback:std::move(callback)]);
 
   // Create a NSWindow delegate that will be used to map the AppKit created
   // NSWindow to the overlay view widget's NSWindow.
   immersive_mode_mapper_.reset([[ImmersiveModeMapper alloc] init]);
-  immersive_mode_mapper_.get().originalHostingWindow = overlay_widget_;
+  immersive_mode_mapper_.get().originalHostingWindow = overlay_window_;
   immersive_mode_titlebar_view_controller_.get().view =
       [[ImmersiveModeView alloc]
           initWithImmersiveModeDelegate:immersive_mode_mapper_.get()];
@@ -317,7 +347,7 @@
   // view will be re-parented into the AppKit created NSWindow.
   BridgedContentView* overlay_content_view =
       base::mac::ObjCCastStrict<BridgedContentView>(
-          overlay_widget_.contentView);
+          overlay_window_.contentView);
   [overlay_content_view retain];
   [overlay_content_view removeFromSuperview];
 
@@ -326,15 +356,15 @@
   // window.
   ImmersiveModeTitlebarObserver* titlebar_observer =
       [[[ImmersiveModeTitlebarObserver alloc]
-          initWithOverlayWindow:overlay_widget_
-                    overlayView:overlay_content_view] autorelease];
+          initWithController:weak_ptr_factory_.GetWeakPtr()
+                 overlayView:overlay_content_view] autorelease];
   [immersive_mode_titlebar_view_controller_
       setTitlebarObserver:titlebar_observer];
 
   // The original content view (top chrome) has been moved to the AppKit
   // created NSWindow. Create a new content view but reuse the original bridge
   // so that mouse drags are handled.
-  overlay_widget_.contentView =
+  overlay_window_.contentView =
       [[[BridgedContentView alloc] initWithBridge:overlay_content_view.bridge
                                            bounds:gfx::Rect()] autorelease];
 
@@ -350,6 +380,9 @@
       [[NSTitlebarAccessoryViewController alloc] init]);
   thin_titlebar_view_controller_.get().view =
       [[[NSView alloc] init] autorelease];
+  thin_titlebar_view_controller_.get().view.wantsLayer = YES;
+  thin_titlebar_view_controller_.get().view.layer.backgroundColor =
+      NSColor.blackColor.CGColor;
   thin_titlebar_view_controller_.get().layoutAttribute =
       NSLayoutAttributeBottom;
   thin_titlebar_view_controller_.get().fullScreenMinHeight =
@@ -358,7 +391,7 @@
   // Move sub-widgets from the browser widget to the overlay widget so that
   // they are rendered above the toolbar.
   ObserveOverlayChildWindows();
-  ReparentChildWindows(browser_widget_, overlay_widget_);
+  ReparentChildWindows(browser_window_, overlay_window_);
 }
 
 ImmersiveModeController::~ImmersiveModeController() {
@@ -370,25 +403,25 @@
   NSView* overlay_content_view =
       immersive_mode_titlebar_view_controller_.get().view.subviews.firstObject;
   [overlay_content_view removeFromSuperview];
-  overlay_widget_.contentView = overlay_content_view;
+  overlay_window_.contentView = overlay_content_view;
   [immersive_mode_titlebar_view_controller_ removeFromParentViewController];
   [immersive_mode_titlebar_view_controller_.get().view release];
   immersive_mode_titlebar_view_controller_.reset();
-  browser_widget_.styleMask |= NSWindowStyleMaskFullSizeContentView;
+  browser_window_.styleMask |= NSWindowStyleMaskFullSizeContentView;
   if (@available(macOS 11.0, *)) {
-    browser_widget_.titlebarSeparatorStyle = NSTitlebarSeparatorStyleAutomatic;
+    browser_window_.titlebarSeparatorStyle = NSTitlebarSeparatorStyleAutomatic;
   }
 
   // Move sub-widgets back to the browser widget.
-  ReparentChildWindows(overlay_widget_, browser_widget_);
+  ReparentChildWindows(overlay_window_, browser_window_);
 }
 
 void ImmersiveModeController::Enable() {
   DCHECK(!enabled_);
   enabled_ = true;
-  [browser_widget_ addTitlebarAccessoryViewController:
+  [browser_window_ addTitlebarAccessoryViewController:
                        immersive_mode_titlebar_view_controller_];
-  [browser_widget_
+  [browser_window_
       addTitlebarAccessoryViewController:thin_titlebar_view_controller_];
   NSRect frame = thin_titlebar_view_controller_.get().view.frame;
   frame.size.height = kThinControllerHeight;
@@ -422,7 +455,7 @@
       immersive_mode_titlebar_view_controller_.get().fullScreenMinHeight =
           immersive_mode_titlebar_view_controller_.get().view.frame.size.height;
       thin_titlebar_view_controller_.get().hidden = YES;
-      browser_widget_.styleMask &= ~NSWindowStyleMaskFullSizeContentView;
+      browser_window_.styleMask &= ~NSWindowStyleMaskFullSizeContentView;
 
       // Toggling the controller will allow the content view to resize below Top
       // Chrome.
@@ -438,7 +471,7 @@
       thin_titlebar_view_controller_.get().hidden = NO;
 
       immersive_mode_titlebar_view_controller_.get().fullScreenMinHeight = 0;
-      browser_widget_.styleMask |= NSWindowStyleMaskFullSizeContentView;
+      browser_window_.styleMask |= NSWindowStyleMaskFullSizeContentView;
       break;
     case mojom::ToolbarVisibilityStyle::kNone:
       thin_titlebar_view_controller_.get().hidden = YES;
@@ -457,8 +490,15 @@
 // will reveal and auto-hide itself based on mouse movement (controlled by
 // AppKit).
 void ImmersiveModeController::SetTitlebarPinned(bool pinned) {
-  // Remove the current, if any, clear controller from the window.
-  [clear_titlebar_view_controller_.get() removeFromParentViewController];
+  // Remove current, if any, clear controllers from the window. For some reason
+  // -removeFromParentViewController does not always remove the controller.
+  // Attempt to remove the current and any stale controllers.
+  for (NSTitlebarAccessoryViewController* c in browser_window_
+           .titlebarAccessoryViewControllers) {
+    if ([c isKindOfClass:[ClearTitlebarViewController class]]) {
+      [c removeFromParentViewController];
+    }
+  }
 
   if (!pinned) {
     clear_titlebar_view_controller_.reset();
@@ -466,13 +506,13 @@
   }
 
   clear_titlebar_view_controller_.reset([[ClearTitlebarViewController alloc]
-      initWithHeight:browser_widget_.contentView.frame.size.height -
+      initWithHeight:browser_window_.contentView.frame.size.height -
                      kThinControllerHeight]);
   clear_titlebar_view_controller_.get().view =
       [[[NSView alloc] init] autorelease];
   clear_titlebar_view_controller_.get().layoutAttribute =
       NSLayoutAttributeBottom;
-  [browser_widget_
+  [browser_window_
       addTitlebarAccessoryViewController:clear_titlebar_view_controller_];
 }
 
@@ -486,7 +526,7 @@
                 context:nullptr];
   };
   NativeWidgetMacNSWindow* overlay_window =
-      base::mac::ObjCCastStrict<NativeWidgetMacNSWindow>(overlay_widget_);
+      base::mac::ObjCCastStrict<NativeWidgetMacNSWindow>(overlay_window_);
   overlay_window.childWindowAddedHandler = ^(NSWindow* child) {
     // Ignore non-visible children.
     if (!child.visible) {
diff --git a/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm b/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
index 5085d15..fd71a76d 100644
--- a/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
+++ b/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
@@ -1301,7 +1301,7 @@
   // break cross-display fullscreen transitions by losing focus of the
   // transitioning window (crbug.com/1338659) or changing the z-order of
   // windows on the previous space. Making the window key here seems to
-  // alleviate those apparent defects (crbug.com/1392542). 
+  // alleviate those apparent defects (crbug.com/1392542).
   if (is_key_window)
     [window_ makeKeyAndOrderFront:nil];
 }
@@ -1591,7 +1591,7 @@
       if (child_window.parentWindow == window)
         continue;
       if (immersive_mode_controller_ &&
-          immersive_mode_controller_->overlay_widget() == child_window) {
+          immersive_mode_controller_->overlay_window() == child_window) {
         continue;
       }
       [window addChildWindow:child_window ordered:NSWindowAbove];
diff --git a/components/reporting/storage/storage_queue.cc b/components/reporting/storage/storage_queue.cc
index 9b0550b7..8f0a9d9 100644
--- a/components/reporting/storage/storage_queue.cc
+++ b/components/reporting/storage/storage_queue.cc
@@ -1529,6 +1529,14 @@
     ScopedReservation scoped_reservation(
         wrapped_record.ByteSizeLong(),
         storage_queue_->options().memory_resource());
+    // Inject "memory unavailable" failure, if requested.
+    if (storage_queue_->test_injection_handler_ &&
+        !storage_queue_->test_injection_handler_
+             .Run(test::StorageQueueOperationKind::kWrappedRecordLowMemory,
+                  storage_queue_->next_sequencing_id_)
+             .ok()) {
+      scoped_reservation.Reduce(0);
+    }
     if (!scoped_reservation.reserved()) {
       Schedule(&ReadContext::Response, base::Unretained(this),
                Status(error::RESOURCE_EXHAUSTED,
@@ -1604,6 +1612,14 @@
     ScopedReservation scoped_reservation(
         encrypted_record_result.ValueOrDie().ByteSizeLong(),
         storage_queue_->options().memory_resource());
+    // Inject "memory unavailable" failure, if requested.
+    if (storage_queue_->test_injection_handler_ &&
+        !storage_queue_->test_injection_handler_
+             .Run(test::StorageQueueOperationKind::kEncryptedRecordLowMemory,
+                  storage_queue_->next_sequencing_id_)
+             .ok()) {
+      scoped_reservation.Reduce(0);
+    }
     if (!scoped_reservation.reserved()) {
       Schedule(&ReadContext::Response, base::Unretained(this),
                Status(error::RESOURCE_EXHAUSTED,
@@ -1789,7 +1805,12 @@
 }
 
 Status StorageQueue::ReserveNewRecordDiskSpace(const size_t total_size) {
-  if (!options_.disk_space_resource()->Reserve(total_size)) {
+  if ((test_injection_handler_ &&  // Test only: Simulate failure if requested
+       !test_injection_handler_
+            .Run(test::StorageQueueOperationKind::kWriteLowDiskSpace,
+                 next_sequencing_id_)
+            .ok()) ||
+      !options_.disk_space_resource()->Reserve(total_size)) {
     const uint64_t space_used = options_.disk_space_resource()->GetUsed();
     const uint64_t space_total = options_.disk_space_resource()->GetTotal();
     return Status(
@@ -2080,6 +2101,7 @@
     scoped_refptr<ResourceManager> memory_resource,
     scoped_refptr<ResourceManager> disk_space_resource,
     scoped_refptr<RefCountedClosureList> completion_closure_list) {
+  // Reserve specified disk space for the file.
   if (!disk_space_resource->Reserve(size)) {
     LOG(WARNING) << "Disk space exceeded adding file "
                  << filename.MaybeAsASCII();
@@ -2088,6 +2110,7 @@
         base::StrCat({"Not enough disk space available to include file=",
                       filename.MaybeAsASCII()}));
   }
+
   // Cannot use base::MakeRefCounted, since the constructor is private.
   return scoped_refptr<StorageQueue::SingleFile>(
       new SingleFile(filename, size, memory_resource, disk_space_resource,
diff --git a/components/reporting/storage/storage_queue.h b/components/reporting/storage/storage_queue.h
index a2c93c06..39edd8b 100644
--- a/components/reporting/storage/storage_queue.h
+++ b/components/reporting/storage/storage_queue.h
@@ -47,6 +47,9 @@
   kReadBlock,
   kWriteBlock,
   kWriteMetadata,
+  kWrappedRecordLowMemory,
+  kEncryptedRecordLowMemory,
+  kWriteLowDiskSpace,
 };
 
 }  // namespace test
diff --git a/components/reporting/storage/storage_queue_unittest.cc b/components/reporting/storage/storage_queue_unittest.cc
index dbaa07e6..635ccae 100644
--- a/components/reporting/storage/storage_queue_unittest.cc
+++ b/components/reporting/storage/storage_queue_unittest.cc
@@ -2173,12 +2173,17 @@
 TEST_P(StorageQueueTest, WriteRecordWithInsufficientDiskSpace) {
   CreateTestStorageQueueOrDie(BuildStorageQueueOptionsOnlyManual());
 
-  // Update total disk space and reset after running the write operation so it
-  // does not affect other tests
-  const auto original_disk_space = options_.disk_space_resource()->GetTotal();
-  options_.disk_space_resource()->Test_SetTotal(0);
+  // Inject simulated failures.
+  auto inject = InjectFailures();
+  EXPECT_CALL(
+      *inject,
+      Call(Eq(test::StorageQueueOperationKind::kWriteLowDiskSpace), Eq(0)))
+      .WillRepeatedly(WithArg<1>(Invoke([](int64_t seq_id) {
+        return Status(error::INTERNAL,
+                      base::StrCat({"Simulated data write low disk space, seq=",
+                                    base::NumberToString(seq_id)}));
+      })));
   Status write_result = WriteString(kData[0]);
-  options_.disk_space_resource()->Test_SetTotal(original_disk_space);
   EXPECT_FALSE(write_result.ok());
   EXPECT_EQ(write_result.error_code(), error::RESOURCE_EXHAUSTED);
 }
@@ -2196,6 +2201,64 @@
   EXPECT_EQ(write_result.error_code(), error::RESOURCE_EXHAUSTED);
 }
 
+TEST_P(StorageQueueTest, WrappedRecordWithInsufficientMemoryWithFailure) {
+  CreateTestStorageQueueOrDie(BuildStorageQueueOptionsOnlyManual());
+
+  // Inject "low memory" error multiple times, then retire and return success.
+  auto inject = InjectFailures();
+  EXPECT_CALL(
+      *inject,
+      Call(Eq(test::StorageQueueOperationKind::kWrappedRecordLowMemory), Eq(0)))
+      .WillRepeatedly(WithArg<1>(Invoke([](int64_t seq_id) {
+        return Status(error::RESOURCE_EXHAUSTED,
+                      base::StrCat({"Not enough memory for WrappedRecord, seq=",
+                                    base::NumberToString(seq_id)}));
+      })))
+      .RetiresOnSaturation();
+  Record record;
+  record.set_data(std::string(kData[0]));
+  record.set_destination(UPLOAD_EVENTS);
+  if (!dm_token_.empty()) {
+    record.set_dm_token(dm_token_);
+  }
+  test::TestEvent<Status> write_event;
+  LOG(ERROR) << "Write data='" << record.data() << "'";
+  storage_queue_->Write(std::move(record), write_event.cb());
+  Status write_result = write_event.result();
+  EXPECT_FALSE(write_result.ok());
+  EXPECT_EQ(write_result.error_code(), error::RESOURCE_EXHAUSTED);
+}
+
+TEST_P(StorageQueueTest, EncryptedRecordWithInsufficientMemoryWithFailure) {
+  CreateTestStorageQueueOrDie(BuildStorageQueueOptionsOnlyManual());
+
+  // Inject "low memory" error multiple times, then retire and return success.
+  auto inject = InjectFailures();
+  EXPECT_CALL(
+      *inject,
+      Call(Eq(test::StorageQueueOperationKind::kEncryptedRecordLowMemory),
+           Eq(0)))
+      .WillRepeatedly(WithArg<1>(Invoke([](int64_t seq_id) {
+        return Status(
+            error::RESOURCE_EXHAUSTED,
+            base::StrCat({"Not enough memory for EncryptedRecord, seq=",
+                          base::NumberToString(seq_id)}));
+      })))
+      .RetiresOnSaturation();
+  Record record;
+  record.set_data(std::string(kData[0]));
+  record.set_destination(UPLOAD_EVENTS);
+  if (!dm_token_.empty()) {
+    record.set_dm_token(dm_token_);
+  }
+  test::TestEvent<Status> write_event;
+  LOG(ERROR) << "Write data='" << record.data() << "'";
+  storage_queue_->Write(std::move(record), write_event.cb());
+  Status write_result = write_event.result();
+  EXPECT_FALSE(write_result.ok());
+  EXPECT_EQ(write_result.error_code(), error::RESOURCE_EXHAUSTED);
+}
+
 TEST_P(StorageQueueTest, WriteRecordWithReservedSpace) {
   CreateTestStorageQueueOrDie(BuildStorageQueueOptionsOnlyManual());
 
diff --git a/components/saved_tab_groups/saved_tab_group_model.cc b/components/saved_tab_groups/saved_tab_group_model.cc
index 7c29cd8..e0ee868 100644
--- a/components/saved_tab_groups/saved_tab_group_model.cc
+++ b/components/saved_tab_groups/saved_tab_group_model.cc
@@ -226,11 +226,12 @@
   if (!Contains(group_id))
     return;
 
+  const base::GUID tab_id = tab.saved_tab_guid();
   absl::optional<int> group_index = GetIndexOf(group_id);
   saved_tab_groups_[group_index.value()].AddTab(index, tab);
 
   for (auto& observer : observers_)
-    observer.SavedTabGroupUpdatedLocally(group_id);
+    observer.SavedTabGroupUpdatedLocally(group_id, tab_id);
 }
 
 void SavedTabGroupModel::RemoveTabFromGroup(const base::GUID& group_id,
@@ -252,7 +253,7 @@
   // TODO(dljames): Update to use SavedTabGroupRemoveLocally and update the API
   // to pass a group_id and an optional tab_id.
   for (auto& observer : observers_)
-    observer.SavedTabGroupUpdatedLocally(group_id);
+    observer.SavedTabGroupUpdatedLocally(group_id, tab_id);
 }
 
 void SavedTabGroupModel::ReplaceTabInGroupAt(const base::GUID& group_id,
@@ -261,11 +262,14 @@
   if (!Contains(group_id))
     return;
 
+  const base::GUID guid = new_tab.saved_tab_guid();
   absl::optional<int> index = GetIndexOf(group_id);
   saved_tab_groups_[index.value()].ReplaceTabAt(tab_id, new_tab);
 
-  for (auto& observer : observers_)
-    observer.SavedTabGroupUpdatedLocally(group_id);
+  for (auto& observer : observers_) {
+    observer.SavedTabGroupUpdatedLocally(group_id, tab_id);
+    observer.SavedTabGroupUpdatedLocally(group_id, guid);
+  }
 }
 
 void SavedTabGroupModel::MoveTabInGroupTo(const base::GUID& group_id,
@@ -278,7 +282,7 @@
   saved_tab_groups_[index.value()].MoveTab(tab_id, new_index);
 
   for (auto& observer : observers_)
-    observer.SavedTabGroupUpdatedLocally(group_id);
+    observer.SavedTabGroupUpdatedLocally(group_id, tab_id);
 }
 
 std::unique_ptr<sync_pb::SavedTabGroupSpecifics> SavedTabGroupModel::MergeGroup(
@@ -315,7 +319,7 @@
   tab->MergeTab(std::move(sync_specific));
 
   for (auto& observer : observers_)
-    observer.SavedTabGroupUpdatedFromSync(tab->saved_group_guid());
+    observer.SavedTabGroupUpdatedFromSync(group_id, tab->saved_group_guid());
 
   return tab->ToSpecifics();
 }
diff --git a/components/saved_tab_groups/saved_tab_group_model_observer.h b/components/saved_tab_groups/saved_tab_group_model_observer.h
index f1eee43..4b4a9aed 100644
--- a/components/saved_tab_groups/saved_tab_group_model_observer.h
+++ b/components/saved_tab_groups/saved_tab_group_model_observer.h
@@ -9,6 +9,7 @@
 #include "components/saved_tab_groups/saved_tab_group_model.h"
 #include "components/saved_tab_groups/saved_tab_group_tab.h"
 #include "components/tab_groups/tab_group_id.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 // Serves to notify any SavedTabGroupModel listeners that a change has occurred
 // supply the SavedTabGroup that was changed.
@@ -25,10 +26,13 @@
   virtual void SavedTabGroupRemovedLocally(const SavedTabGroup* removed_group) {
   }
 
-  // Called when the title, tabs, or color change.
-  // TODO(dljames): Update parameters to take 2 guids. 1 for the group and an
-  // optional guid for the tab. Do the same for the sync version.
-  virtual void SavedTabGroupUpdatedLocally(const base::GUID& guid) {}
+  // Called when the title, tabs, or color change. `group_guid` denotes the
+  // group that is currently being updated. `tab_guid` denotes if a tab in this
+  // group was changed (added, removed, updated). Otherwise, only the group is
+  // being changed.
+  virtual void SavedTabGroupUpdatedLocally(
+      const base::GUID& group_guid,
+      const absl::optional<base::GUID>& tab_guid = absl::nullopt) {}
 
   // Called when the order of saved tab groups in the bookmark bar are changed.
   // TODO(crbug/1372052): Figure out if we can maintain ordering of groups and
@@ -43,7 +47,13 @@
   virtual void SavedTabGroupRemovedFromSync(
       const SavedTabGroup* removed_group) {}
 
-  virtual void SavedTabGroupUpdatedFromSync(const base::GUID& guid) {}
+  // Called when the title, tabs, or color change. `group_guid` denotes the
+  // group that is currently being updated. `tab_guid` denotes if a tab in this
+  // group was changed (added, removed, updated). Specifically, this is called
+  // when addressing merge conflicts for duplicate groups and tabs.
+  virtual void SavedTabGroupUpdatedFromSync(
+      const base::GUID& group_guid,
+      const absl::optional<base::GUID>& tab_guid = absl::nullopt) {}
 
  protected:
   SavedTabGroupModelObserver() = default;
diff --git a/components/saved_tab_groups/saved_tab_group_model_unittest.cc b/components/saved_tab_groups/saved_tab_group_model_unittest.cc
index a38d946..8b28dc4 100644
--- a/components/saved_tab_groups/saved_tab_group_model_unittest.cc
+++ b/components/saved_tab_groups/saved_tab_group_model_unittest.cc
@@ -129,9 +129,12 @@
     retrieved_guid_ = removed_group->saved_guid();
   }
 
-  void SavedTabGroupUpdatedLocally(const base::GUID& guid) override {
-    retrieved_group_.emplace_back(*saved_tab_group_model_->Get(guid));
-    retrieved_index_ = saved_tab_group_model_->GetIndexOf(guid).value_or(-1);
+  void SavedTabGroupUpdatedLocally(
+      const base::GUID& group_guid,
+      const absl::optional<base::GUID>& tab_guid = absl::nullopt) override {
+    retrieved_group_.emplace_back(*saved_tab_group_model_->Get(group_guid));
+    retrieved_index_ =
+        saved_tab_group_model_->GetIndexOf(group_guid).value_or(-1);
   }
 
   void SavedTabGroupAddedFromSync(const base::GUID& guid) override {
@@ -144,9 +147,12 @@
     retrieved_guid_ = removed_group->saved_guid();
   }
 
-  void SavedTabGroupUpdatedFromSync(const base::GUID& guid) override {
-    retrieved_group_.emplace_back(*saved_tab_group_model_->Get(guid));
-    retrieved_index_ = saved_tab_group_model_->GetIndexOf(guid).value_or(-1);
+  void SavedTabGroupUpdatedFromSync(
+      const base::GUID& group_guid,
+      const absl::optional<base::GUID>& tab_guid = absl::nullopt) override {
+    retrieved_group_.emplace_back(*saved_tab_group_model_->Get(group_guid));
+    retrieved_index_ =
+        saved_tab_group_model_->GetIndexOf(group_guid).value_or(-1);
   }
 
   void SavedTabGroupReorderedLocally() override { reordered_called_ = true; }
@@ -1002,4 +1008,4 @@
             saved_tab_group_model_->GetGroupContainingTab(GenerateNextGUID()));
   EXPECT_EQ(nullptr,
             saved_tab_group_model_->GetGroupContainingTab(base::Token()));
-}
\ No newline at end of file
+}
diff --git a/components/saved_tab_groups/saved_tab_group_sync_bridge.cc b/components/saved_tab_groups/saved_tab_group_sync_bridge.cc
index f64eba4..4e0447a 100644
--- a/components/saved_tab_groups/saved_tab_group_sync_bridge.cc
+++ b/components/saved_tab_groups/saved_tab_group_sync_bridge.cc
@@ -231,15 +231,25 @@
 }
 
 void SavedTabGroupSyncBridge::SavedTabGroupUpdatedLocally(
-    const base::GUID& guid) {
+    const base::GUID& group_guid,
+    const absl::optional<base::GUID>& tab_guid) {
   std::unique_ptr<syncer::ModelTypeStore::WriteBatch> write_batch =
       store_->CreateWriteBatch();
 
-  const SavedTabGroup* group = model_->Get(guid);
+  const SavedTabGroup* group = model_->Get(group_guid);
   DCHECK(group);
-  UpsertEntitySpecific(group->ToSpecifics(), write_batch.get());
-  for (const SavedTabGroupTab& tab : group->saved_tabs())
-    UpsertEntitySpecific(tab.ToSpecifics(), write_batch.get());
+
+  if (tab_guid.has_value()) {
+    if (!group->ContainsTab(tab_guid.value())) {
+      RemoveEntitySpecific(tab_guid.value(), write_batch.get());
+    } else {
+      int tab_index = group->GetIndexOfTab(tab_guid.value()).value();
+      UpsertEntitySpecific(group->saved_tabs()[tab_index].ToSpecifics(),
+                           write_batch.get());
+    }
+  } else {
+    UpsertEntitySpecific(group->ToSpecifics(), write_batch.get());
+  }
 
   store_->CommitWriteBatch(
       std::move(write_batch),
diff --git a/components/saved_tab_groups/saved_tab_group_sync_bridge.h b/components/saved_tab_groups/saved_tab_group_sync_bridge.h
index dc6e6ce..0e882054 100644
--- a/components/saved_tab_groups/saved_tab_group_sync_bridge.h
+++ b/components/saved_tab_groups/saved_tab_group_sync_bridge.h
@@ -16,6 +16,7 @@
 #include "components/sync/model/model_type_change_processor.h"
 #include "components/sync/model/model_type_store.h"
 #include "components/sync/model/model_type_sync_bridge.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 class SavedTabGroupModel;
 
@@ -59,7 +60,9 @@
   // SavedTabGroupModelObserver
   void SavedTabGroupAddedLocally(const base::GUID& guid) override;
   void SavedTabGroupRemovedLocally(const SavedTabGroup* removed_group) override;
-  void SavedTabGroupUpdatedLocally(const base::GUID& guid) override;
+  void SavedTabGroupUpdatedLocally(
+      const base::GUID& group_guid,
+      const absl::optional<base::GUID>& tab_guid = absl::nullopt) override;
   void SavedTabGroupReorderedLocally() override;
 
  private:
diff --git a/components/saved_tab_groups/saved_tab_group_sync_bridge_unittest.cc b/components/saved_tab_groups/saved_tab_group_sync_bridge_unittest.cc
index f60b8a8..88ff2ee 100644
--- a/components/saved_tab_groups/saved_tab_group_sync_bridge_unittest.cc
+++ b/components/saved_tab_groups/saved_tab_group_sync_bridge_unittest.cc
@@ -614,14 +614,10 @@
   base::GUID tab_2_guid = tab_2.saved_tab_guid();
   saved_tab_group_model_.Add(std::move(group));
 
-  EXPECT_CALL(processor_, Put(tab_1_guid.AsLowercaseString(), _, _));
-  EXPECT_CALL(processor_, Put(tab_2_guid.AsLowercaseString(), _, _));
   EXPECT_CALL(processor_, Put(group_guid.AsLowercaseString(), _, _));
+  EXPECT_CALL(processor_, Put(tab_1_guid.AsLowercaseString(), _, _)).Times(0);
+  EXPECT_CALL(processor_, Put(tab_2_guid.AsLowercaseString(), _, _)).Times(0);
 
-  // TODO(dljames): For now, updating a groups visual data also notifies
-  // observers of the tabs in the group. This is done for simplicity. Once the
-  // observer function changes are made this test will fail and we can remove
-  // the Put() calls for the tabs.
   tab_groups::TabGroupVisualData visual_data(
       u"New Title", tab_groups::TabGroupColorId::kYellow);
   saved_tab_group_model_.UpdateVisualData(group_guid, &visual_data);
@@ -646,10 +642,10 @@
   base::GUID tab_3_guid = tab_3.saved_tab_guid();
   saved_tab_group_model_.Add(std::move(group));
 
-  EXPECT_CALL(processor_, Put(tab_1_guid.AsLowercaseString(), _, _));
-  EXPECT_CALL(processor_, Put(tab_2_guid.AsLowercaseString(), _, _));
   EXPECT_CALL(processor_, Put(tab_3_guid.AsLowercaseString(), _, _));
-  EXPECT_CALL(processor_, Put(group_guid.AsLowercaseString(), _, _));
+  EXPECT_CALL(processor_, Put(tab_1_guid.AsLowercaseString(), _, _)).Times(0);
+  EXPECT_CALL(processor_, Put(tab_2_guid.AsLowercaseString(), _, _)).Times(0);
+  EXPECT_CALL(processor_, Put(group_guid.AsLowercaseString(), _, _)).Times(0);
 
   // TODO(dljames): Because `tab_3` was added to the middle of the group, only
   // `tab_2` will have its position updated. Once tab ordering is implemented,
@@ -659,8 +655,6 @@
 }
 
 // Verify that locally removed tabs remove the correct tabs from the processor.
-// TODO(dljames): Update the test with delete calls once the observer API is
-// updated.
 TEST_F(SavedTabGroupSyncBridgeTest, RemoveTabLocally) {
   EXPECT_TRUE(saved_tab_group_model_.saved_tab_groups().empty());
 
@@ -676,9 +670,9 @@
   base::GUID tab_2_guid = tab_2.saved_tab_guid();
   saved_tab_group_model_.Add(std::move(group));
 
-  EXPECT_CALL(processor_, Put(tab_1_guid.AsLowercaseString(), _, _)).Times(0);
-  EXPECT_CALL(processor_, Put(tab_2_guid.AsLowercaseString(), _, _));
-  EXPECT_CALL(processor_, Put(group_guid.AsLowercaseString(), _, _));
+  EXPECT_CALL(processor_, Delete(tab_1_guid.AsLowercaseString(), _));
+  EXPECT_CALL(processor_, Put(tab_2_guid.AsLowercaseString(), _, _)).Times(0);
+  EXPECT_CALL(processor_, Put(group_guid.AsLowercaseString(), _, _)).Times(0);
 
   saved_tab_group_model_.RemoveTabFromGroup(group_guid, tab_1_guid);
 }
@@ -702,12 +696,10 @@
   base::GUID tab_3_guid = tab_3.saved_tab_guid();
   saved_tab_group_model_.Add(std::move(group));
 
-  // TODO(dljames): Ensure Delete() is called on tab_1 once the observer api
-  // changes are made.
-  EXPECT_CALL(processor_, Put(tab_1_guid.AsLowercaseString(), _, _)).Times(0);
-  EXPECT_CALL(processor_, Put(tab_2_guid.AsLowercaseString(), _, _));
+  EXPECT_CALL(processor_, Delete(tab_1_guid.AsLowercaseString(), _));
   EXPECT_CALL(processor_, Put(tab_3_guid.AsLowercaseString(), _, _));
-  EXPECT_CALL(processor_, Put(group_guid.AsLowercaseString(), _, _));
+  EXPECT_CALL(processor_, Put(tab_2_guid.AsLowercaseString(), _, _)).Times(0);
+  EXPECT_CALL(processor_, Put(group_guid.AsLowercaseString(), _, _)).Times(0);
 
   saved_tab_group_model_.ReplaceTabInGroupAt(group_guid, tab_1_guid, tab_3);
 }
diff --git a/components/url_formatter/spoof_checks/top_domains/top_domain_state_generator.cc b/components/url_formatter/spoof_checks/top_domains/top_domain_state_generator.cc
index 2e170f1..b3d8574 100644
--- a/components/url_formatter/spoof_checks/top_domains/top_domain_state_generator.cc
+++ b/components/url_formatter/spoof_checks/top_domains/top_domain_state_generator.cc
@@ -108,7 +108,9 @@
   // most space efficient Huffman table for the given inputs. This table is used
   // for the second run.
 
+  // Tables must outlive `trie_entries` and `raw_trie_entries`.
   HuffmanRepresentationTable approximate_table = ApproximateHuffman(entries);
+  HuffmanRepresentationTable optimal_table;
   HuffmanBuilder huffman_builder;
 
   // Create trie entries for the first pass.
@@ -126,7 +128,7 @@
   if (!writer.WriteEntries(raw_trie_entries, &root_position))
     return std::string();
 
-  HuffmanRepresentationTable optimal_table = huffman_builder.ToTable();
+  optimal_table = huffman_builder.ToTable();
   TrieWriter new_writer(optimal_table, &huffman_builder);
 
   // Create trie entries using the optimal table for the second pass.
diff --git a/components/viz/service/display_embedder/output_presenter_gl.cc b/components/viz/service/display_embedder/output_presenter_gl.cc
index ae3b11d..c8f5020 100644
--- a/components/viz/service/display_embedder/output_presenter_gl.cc
+++ b/components/viz/service/display_embedder/output_presenter_gl.cc
@@ -18,7 +18,6 @@
 #include "components/viz/service/display_embedder/skia_output_surface_dependency.h"
 #include "gpu/command_buffer/common/shared_image_usage.h"
 #include "gpu/command_buffer/service/shared_context_state.h"
-#include "gpu/ipc/common/gpu_surface_lookup.h"
 #include "ui/display/types/display_snapshot.h"
 #include "ui/gfx/buffer_format_util.h"
 #include "ui/gfx/geometry/rect_conversions.h"
@@ -29,6 +28,7 @@
 #include "ui/gl/gl_surface.h"
 
 #if BUILDFLAG(IS_ANDROID)
+#include "gpu/ipc/common/gpu_surface_lookup.h"
 #include "ui/gl/android/scoped_a_native_window.h"
 #include "ui/gl/android/scoped_java_surface.h"
 #include "ui/gl/gl_surface_egl_surface_control.h"
diff --git a/components/viz/service/display_embedder/skia_output_device_dcomp.cc b/components/viz/service/display_embedder/skia_output_device_dcomp.cc
index 2efce022..d528235 100644
--- a/components/viz/service/display_embedder/skia_output_device_dcomp.cc
+++ b/components/viz/service/display_embedder/skia_output_device_dcomp.cc
@@ -90,7 +90,7 @@
     gpu::MailboxManager* mailbox_manager,
     gpu::SharedImageRepresentationFactory* shared_image_representation_factory,
     gpu::SharedContextState* context_state,
-    scoped_refptr<gl::GLSurface> gl_surface,
+    gl::GLSurface* gl_surface,
     scoped_refptr<gpu::gles2::FeatureInfo> feature_info,
     gpu::MemoryTracker* memory_tracker,
     DidSwapBufferCompleteCallback did_swap_buffer_complete_callback)
@@ -99,89 +99,60 @@
                        std::move(did_swap_buffer_complete_callback)),
       mailbox_manager_(mailbox_manager),
       shared_image_representation_factory_(shared_image_representation_factory),
-      context_state_(context_state),
-      gl_surface_(std::move(gl_surface)),
-      supports_async_swap_(gl_surface_->SupportsAsyncSwap()) {
+      context_state_(context_state) {
+  DCHECK(gl_surface->SupportsPostSubBuffer());
+  DCHECK(!gl_surface->SupportsAsyncSwap());
+  DCHECK(!feature_info->workarounds()
+              .disable_post_sub_buffers_for_onscreen_surfaces);
+  DCHECK(gl_surface->SupportsDCLayers());
+  DCHECK_EQ(gl_surface->GetOrigin(), gfx::SurfaceOrigin::kTopLeft);
+  DCHECK(gl_surface->SupportsGpuVSync());
+  DCHECK(!gl_surface->SupportsCommitOverlayPlanes());
+
   capabilities_.uses_default_gl_framebuffer = true;
-  capabilities_.output_surface_origin = gl_surface_->GetOrigin();
-  capabilities_.supports_post_sub_buffer = gl_surface_->SupportsPostSubBuffer();
-  if (gl_surface_->SupportsDCLayers()) {
-    // DWM handles preserving the contents of the backbuffer in Present1, so we
-    // don't need to have SkiaOutputSurface handle it.
-    capabilities_.preserve_buffer_content = false;
-    capabilities_.number_of_buffers =
-        gl::DirectCompositionRootSurfaceBufferCount();
-    capabilities_.supports_delegated_ink = gl_surface_->SupportsDelegatedInk();
-  }
-  if (feature_info->workarounds()
-          .disable_post_sub_buffers_for_onscreen_surfaces) {
-    capabilities_.supports_post_sub_buffer = false;
-  }
+  capabilities_.output_surface_origin = gfx::SurfaceOrigin::kTopLeft;
+  capabilities_.supports_post_sub_buffer = true;
+  // DWM handles preserving the contents of the backbuffer in Present1, so we
+  // don't need to have SkiaOutputSurface handle it.
+  capabilities_.preserve_buffer_content = false;
+  capabilities_.number_of_buffers =
+      gl::DirectCompositionRootSurfaceBufferCount();
+  capabilities_.supports_delegated_ink = gl_surface->SupportsDelegatedInk();
   if (feature_info->workarounds().supports_two_yuv_hardware_overlays) {
     capabilities_.supports_two_yuv_hardware_overlays = true;
   }
   capabilities_.pending_swap_params.max_pending_swaps =
-      gl_surface_->GetBufferCount() - 1;
-  capabilities_.supports_commit_overlay_planes =
-      gl_surface_->SupportsCommitOverlayPlanes();
-  capabilities_.supports_gpu_vsync = gl_surface_->SupportsGpuVSync();
-  capabilities_.supports_dc_layers = gl_surface_->SupportsDCLayers();
+      gl_surface->GetBufferCount() - 1;
+  capabilities_.supports_commit_overlay_planes = false;
+  capabilities_.supports_gpu_vsync = true;
+  capabilities_.supports_dc_layers = true;
 
   DCHECK(context_state_);
-  DCHECK(gl_surface_);
+  DCHECK(gl_surface);
 
-  if (gl_surface_->SupportsSwapTimestamps()) {
-    gl_surface_->SetEnableSwapTimestamps();
+  if (gl_surface->SupportsSwapTimestamps()) {
+    gl_surface->SetEnableSwapTimestamps();
 
     // Changes to swap timestamp queries are only picked up when making current.
     context_state_->ReleaseCurrent(nullptr);
-    context_state_->MakeCurrent(gl_surface_.get());
+    context_state_->MakeCurrent(gl_surface);
   }
 
   DCHECK(context_state_->gr_context());
   DCHECK(context_state_->context());
 
-  GrDirectContext* gr_context = context_state_->gr_context();
-  gl::CurrentGL* current_gl = context_state_->context()->GetCurrentGL();
-
-  // Get alpha bits from the default frame buffer.
-  int alpha_bits = 0;
-  glBindFramebufferEXT(GL_FRAMEBUFFER, 0);
-  gr_context->resetContext(kRenderTarget_GrGLBackendState);
-  const auto* version = current_gl->Version.get();
-  if (version->is_desktop_core_profile) {
-    glGetFramebufferAttachmentParameterivEXT(
-        GL_FRAMEBUFFER, GL_BACK_LEFT, GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE,
-        &alpha_bits);
-  } else {
-    glGetIntegerv(GL_ALPHA_BITS, &alpha_bits);
-  }
-  CHECK_GL_ERROR();
-
-  auto color_type = kRGBA_8888_SkColorType;
-
-  if (alpha_bits == 0) {
-    color_type = gl_surface_->GetFormat().GetBufferSize() == 16
-                     ? kRGB_565_SkColorType
-                     : kRGB_888x_SkColorType;
-    // Skia disables RGBx on some GPUs, fallback to RGBA if it's the
-    // case. This doesn't change framebuffer itself, as we already allocated it,
-    // but will change any temporary buffer Skia needs to allocate.
-    if (!context_state_->gr_context()
-             ->defaultBackendFormat(color_type, GrRenderable::kYes)
-             .isValid()) {
-      color_type = kRGBA_8888_SkColorType;
-    }
-  }
   // SRGB
+  constexpr SkColorType kSrgbColorType = kRGBA_8888_SkColorType;
+  // TODO(tangm): switch to kRGB_888x_SkColorType
+  constexpr SkColorType kSrgbColorTypeOpaque = kRGBA_8888_SkColorType;
   capabilities_.sk_color_types[static_cast<int>(gfx::BufferFormat::RGBA_8888)] =
-      color_type;
+      kSrgbColorType;
   capabilities_.sk_color_types[static_cast<int>(gfx::BufferFormat::RGBX_8888)] =
-      color_type;
+      kSrgbColorTypeOpaque;
   capabilities_.sk_color_types[static_cast<int>(gfx::BufferFormat::BGRA_8888)] =
-      color_type;
+      kSrgbColorType;
   capabilities_.sk_color_types[static_cast<int>(gfx::BufferFormat::BGRX_8888)] =
-      color_type;
+      kSrgbColorTypeOpaque;
   // HDR10
   capabilities_
       .sk_color_types[static_cast<int>(gfx::BufferFormat::RGBA_1010102)] =
@@ -191,12 +162,127 @@
       kRGBA_F16_SkColorType;
 }
 
-SkiaOutputDeviceDComp::~SkiaOutputDeviceDComp() {
+SkiaOutputDeviceDComp::~SkiaOutputDeviceDComp() = default;
+
+void SkiaOutputDeviceDComp::SwapBuffers(BufferPresentedCallback feedback,
+                                        OutputSurfaceFrame frame) {
+  NOTREACHED();
+}
+
+void SkiaOutputDeviceDComp::PostSubBuffer(const gfx::Rect& rect,
+                                          BufferPresentedCallback feedback,
+                                          OutputSurfaceFrame frame) {
+  StartSwapBuffers({});
+
+  gfx::SwapResult result =
+      DoPostSubBuffer(rect, std::move(feedback), frame.data);
+
+  // Remove entries from |overlays_| for textures that weren't scheduled as an
+  // overlay this frame.
+  if (!overlays_.empty()) {
+    base::EraseIf(overlays_, [this](auto& entry) {
+      const gpu::Mailbox& mailbox = entry.first;
+      return !scheduled_overlay_mailboxes_.contains(mailbox);
+    });
+    scheduled_overlay_mailboxes_.clear();
+    // End access for the remaining overlays that were scheduled this frame.
+    for (auto& kv : overlays_)
+      kv.second.EndOverlayAccess();
+  }
+
+  FinishSwapBuffers(gfx::SwapCompletionResult(result), GetRootSurfaceSize(),
+                    std::move(frame));
+}
+
+void SkiaOutputDeviceDComp::ScheduleOverlays(
+    SkiaOutputSurface::OverlayList overlays) {
+  for (auto& dc_layer : overlays) {
+    auto params = std::make_unique<ui::DCRendererLayerParams>();
+    // Get GLImages for DC layer textures.
+    bool success = true;
+    for (size_t i = 0; i < DCLayerOverlay::kNumResources; ++i) {
+      const gpu::Mailbox& mailbox = dc_layer.mailbox[i];
+      if (i > 0 && mailbox.IsZero())
+        break;
+
+      auto* read_access = BeginOverlayAccess(mailbox);
+      if (!read_access) {
+        success = false;
+        break;
+      }
+
+      if (auto dcomp_surface_proxy = read_access->GetDCOMPSurfaceProxy()) {
+        params->dcomp_surface_proxy = std::move(dcomp_surface_proxy);
+      } else if (auto* image = read_access->gl_image()) {
+        image->SetColorSpace(dc_layer.color_space);
+        params->images[i] = std::move(image);
+      } else {
+        success = false;
+        break;
+      }
+
+      scheduled_overlay_mailboxes_.insert(mailbox);
+    }
+
+    if (!success) {
+      DLOG(ERROR) << "Failed to get GLImage for DC layer.";
+      continue;
+    }
+
+    params->z_order = dc_layer.z_order;
+    params->content_rect = dc_layer.content_rect;
+    params->quad_rect = dc_layer.quad_rect;
+    DCHECK(dc_layer.transform.IsFlat());
+    params->transform = dc_layer.transform;
+    params->clip_rect = dc_layer.clip_rect;
+    params->protected_video_type = dc_layer.protected_video_type;
+    params->hdr_metadata = dc_layer.hdr_metadata;
+    params->is_video_fullscreen_letterboxing =
+        dc_layer.is_video_fullscreen_letterboxing;
+
+    // Schedule DC layer overlay to be presented at next SwapBuffers().
+    if (!ScheduleDCLayer(std::move(params)))
+      DLOG(ERROR) << "ScheduleDCLayer failed";
+  }
+}
+
+gpu::OverlayImageRepresentation::ScopedReadAccess*
+SkiaOutputDeviceDComp::BeginOverlayAccess(const gpu::Mailbox& mailbox) {
+  auto it = overlays_.find(mailbox);
+  if (it != overlays_.end())
+    return it->second.BeginOverlayAccess();
+
+  auto overlay = shared_image_representation_factory_->ProduceOverlay(mailbox);
+  if (!overlay)
+    return nullptr;
+
+  std::tie(it, std::ignore) = overlays_.emplace(mailbox, std::move(overlay));
+  return it->second.BeginOverlayAccess();
+}
+
+SkiaOutputDeviceDCompGLSurface::SkiaOutputDeviceDCompGLSurface(
+    gpu::MailboxManager* mailbox_manager,
+    gpu::SharedImageRepresentationFactory* shared_image_representation_factory,
+    gpu::SharedContextState* context_state,
+    scoped_refptr<gl::GLSurface> gl_surface,
+    scoped_refptr<gpu::gles2::FeatureInfo> feature_info,
+    gpu::MemoryTracker* memory_tracker,
+    DidSwapBufferCompleteCallback did_swap_buffer_complete_callback)
+    : SkiaOutputDeviceDComp(mailbox_manager,
+                            shared_image_representation_factory,
+                            context_state,
+                            gl_surface.get(),
+                            std::move(feature_info),
+                            memory_tracker,
+                            std::move(did_swap_buffer_complete_callback)),
+      gl_surface_(std::move(gl_surface)) {}
+
+SkiaOutputDeviceDCompGLSurface::~SkiaOutputDeviceDCompGLSurface() {
   // gl_surface_ will be destructed soon.
   memory_type_tracker_->TrackMemFree(backbuffer_estimated_size_);
 }
 
-bool SkiaOutputDeviceDComp::Reshape(
+bool SkiaOutputDeviceDCompGLSurface::Reshape(
     const SkSurfaceCharacterization& characterization,
     const gfx::ColorSpace& color_space,
     float device_scale_factor,
@@ -277,198 +363,42 @@
   return !!sk_surface_;
 }
 
-void SkiaOutputDeviceDComp::SwapBuffers(BufferPresentedCallback feedback,
-                                        OutputSurfaceFrame frame) {
-  StartSwapBuffers({});
-
-  gfx::Size surface_size =
-      gfx::Size(sk_surface_->width(), sk_surface_->height());
-
-  auto data = frame.data;
-  if (supports_async_swap_) {
-    auto callback = base::BindOnce(
-        &SkiaOutputDeviceDComp::DoFinishSwapBuffersAsync,
-        weak_ptr_factory_.GetWeakPtr(), surface_size, std::move(frame));
-    gl_surface_->SwapBuffersAsync(std::move(callback), std::move(feedback),
-                                  data);
-  } else {
-    gfx::SwapResult result =
-        gl_surface_->SwapBuffers(std::move(feedback), data);
-    DoFinishSwapBuffers(surface_size, std::move(frame),
-                        gfx::SwapCompletionResult(result));
-  }
-}
-
-void SkiaOutputDeviceDComp::PostSubBuffer(const gfx::Rect& rect,
-                                          BufferPresentedCallback feedback,
-                                          OutputSurfaceFrame frame) {
-  StartSwapBuffers({});
-
-  gfx::Size surface_size =
-      gfx::Size(sk_surface_->width(), sk_surface_->height());
-
-  auto data = frame.data;
-  if (supports_async_swap_) {
-    auto callback = base::BindOnce(
-        &SkiaOutputDeviceDComp::DoFinishSwapBuffersAsync,
-        weak_ptr_factory_.GetWeakPtr(), surface_size, std::move(frame));
-    gl_surface_->PostSubBufferAsync(rect.x(), rect.y(), rect.width(),
-                                    rect.height(), std::move(callback),
-                                    std::move(feedback), data);
-  } else {
-    gfx::SwapResult result =
-        gl_surface_->PostSubBuffer(rect.x(), rect.y(), rect.width(),
-                                   rect.height(), std::move(feedback), data);
-    DoFinishSwapBuffers(surface_size, std::move(frame),
-                        gfx::SwapCompletionResult(result));
-  }
-}
-
-void SkiaOutputDeviceDComp::CommitOverlayPlanes(
-    BufferPresentedCallback feedback,
-    OutputSurfaceFrame frame) {
-  StartSwapBuffers({});
-
-  gfx::Size surface_size =
-      gfx::Size(sk_surface_->width(), sk_surface_->height());
-
-  auto data = frame.data;
-  if (supports_async_swap_) {
-    auto callback = base::BindOnce(
-        &SkiaOutputDeviceDComp::DoFinishSwapBuffersAsync,
-        weak_ptr_factory_.GetWeakPtr(), surface_size, std::move(frame));
-    gl_surface_->CommitOverlayPlanesAsync(std::move(callback),
-                                          std::move(feedback), data);
-  } else {
-    gfx::SwapResult result =
-        gl_surface_->CommitOverlayPlanes(std::move(feedback), data);
-    DoFinishSwapBuffers(surface_size, std::move(frame),
-                        gfx::SwapCompletionResult(result));
-  }
-}
-
-void SkiaOutputDeviceDComp::DoFinishSwapBuffersAsync(
-    const gfx::Size& size,
-    OutputSurfaceFrame frame,
-    gfx::SwapCompletionResult result) {
-  DCHECK(result.release_fence.is_null());
-  FinishSwapBuffers(std::move(result), size, std::move(frame));
-}
-
-void SkiaOutputDeviceDComp::DoFinishSwapBuffers(
-    const gfx::Size& size,
-    OutputSurfaceFrame frame,
-    gfx::SwapCompletionResult result) {
-  DCHECK(result.release_fence.is_null());
-
-  // Remove entries from |overlays_| for textures that weren't scheduled as an
-  // overlay this frame.
-  if (!overlays_.empty()) {
-    base::EraseIf(overlays_, [this](auto& entry) {
-      const gpu::Mailbox& mailbox = entry.first;
-      return !scheduled_overlay_mailboxes_.contains(mailbox);
-    });
-    scheduled_overlay_mailboxes_.clear();
-    // End access for the remaining overlays that were scheduled this frame.
-    for (auto& kv : overlays_)
-      kv.second.EndOverlayAccess();
-  }
-
-  FinishSwapBuffers(std::move(result), size, std::move(frame));
-}
-
-bool SkiaOutputDeviceDComp::SetDrawRectangle(const gfx::Rect& draw_rectangle) {
+bool SkiaOutputDeviceDCompGLSurface::SetDrawRectangle(
+    const gfx::Rect& draw_rectangle) {
   return gl_surface_->SetDrawRectangle(draw_rectangle);
 }
 
-void SkiaOutputDeviceDComp::SetGpuVSyncEnabled(bool enabled) {
-  gl_surface_->SetGpuVSyncEnabled(enabled);
-}
-
-void SkiaOutputDeviceDComp::SetEnableDCLayers(bool enable) {
+void SkiaOutputDeviceDCompGLSurface::SetEnableDCLayers(bool enable) {
   gl_surface_->SetEnableDCLayers(enable);
 }
 
-void SkiaOutputDeviceDComp::ScheduleOverlays(
-    SkiaOutputSurface::OverlayList overlays) {
-  for (auto& dc_layer : overlays) {
-    auto params = std::make_unique<ui::DCRendererLayerParams>();
-    // Get GLImages for DC layer textures.
-    bool success = true;
-    for (size_t i = 0; i < DCLayerOverlay::kNumResources; ++i) {
-      const gpu::Mailbox& mailbox = dc_layer.mailbox[i];
-      if (i > 0 && mailbox.IsZero())
-        break;
-
-      auto* read_access = BeginOverlayAccess(mailbox);
-      if (!read_access) {
-        success = false;
-        break;
-      }
-
-      if (auto dcomp_surface_proxy = read_access->GetDCOMPSurfaceProxy()) {
-        params->dcomp_surface_proxy = std::move(dcomp_surface_proxy);
-      } else if (auto* image = read_access->gl_image()) {
-        image->SetColorSpace(dc_layer.color_space);
-        params->images[i] = std::move(image);
-      } else {
-        success = false;
-        break;
-      }
-
-      scheduled_overlay_mailboxes_.insert(mailbox);
-    }
-
-    if (!success) {
-      DLOG(ERROR) << "Failed to get GLImage for DC layer.";
-      continue;
-    }
-
-    params->z_order = dc_layer.z_order;
-    params->content_rect = dc_layer.content_rect;
-    params->quad_rect = dc_layer.quad_rect;
-    DCHECK(dc_layer.transform.IsFlat());
-    params->transform = dc_layer.transform;
-    params->clip_rect = dc_layer.clip_rect;
-    params->protected_video_type = dc_layer.protected_video_type;
-    params->hdr_metadata = dc_layer.hdr_metadata;
-    params->is_video_fullscreen_letterboxing =
-        dc_layer.is_video_fullscreen_letterboxing;
-
-    // Schedule DC layer overlay to be presented at next SwapBuffers().
-    if (!gl_surface_->ScheduleDCLayer(std::move(params)))
-      DLOG(ERROR) << "ScheduleDCLayer failed";
-  }
+void SkiaOutputDeviceDCompGLSurface::SetGpuVSyncEnabled(bool enabled) {
+  gl_surface_->SetGpuVSyncEnabled(enabled);
 }
 
-void SkiaOutputDeviceDComp::EnsureBackbuffer() {
-  gl_surface_->SetBackbufferAllocation(true);
-}
-
-void SkiaOutputDeviceDComp::DiscardBackbuffer() {
-  gl_surface_->SetBackbufferAllocation(false);
-}
-
-SkSurface* SkiaOutputDeviceDComp::BeginPaint(
+SkSurface* SkiaOutputDeviceDCompGLSurface::BeginPaint(
     std::vector<GrBackendSemaphore>* end_semaphores) {
   DCHECK(sk_surface_);
   return sk_surface_.get();
 }
 
-void SkiaOutputDeviceDComp::EndPaint() {}
+void SkiaOutputDeviceDCompGLSurface::EndPaint() {}
 
-gpu::OverlayImageRepresentation::ScopedReadAccess*
-SkiaOutputDeviceDComp::BeginOverlayAccess(const gpu::Mailbox& mailbox) {
-  auto it = overlays_.find(mailbox);
-  if (it != overlays_.end())
-    return it->second.BeginOverlayAccess();
+bool SkiaOutputDeviceDCompGLSurface::ScheduleDCLayer(
+    std::unique_ptr<ui::DCRendererLayerParams> params) {
+  return gl_surface_->ScheduleDCLayer(std::move(params));
+}
 
-  auto overlay = shared_image_representation_factory_->ProduceOverlay(mailbox);
-  if (!overlay)
-    return nullptr;
+gfx::Size SkiaOutputDeviceDCompGLSurface::GetRootSurfaceSize() const {
+  return gl_surface_->GetSize();
+}
 
-  std::tie(it, std::ignore) = overlays_.emplace(mailbox, std::move(overlay));
-  return it->second.BeginOverlayAccess();
+gfx::SwapResult SkiaOutputDeviceDCompGLSurface::DoPostSubBuffer(
+    const gfx::Rect& rect,
+    BufferPresentedCallback feedback,
+    gl::FrameData data) {
+  return gl_surface_->PostSubBuffer(rect.x(), rect.y(), rect.width(),
+                                    rect.height(), std::move(feedback), data);
 }
 
 }  // namespace viz
diff --git a/components/viz/service/display_embedder/skia_output_device_dcomp.h b/components/viz/service/display_embedder/skia_output_device_dcomp.h
index 3718f500..5aa6fbaf 100644
--- a/components/viz/service/display_embedder/skia_output_device_dcomp.h
+++ b/components/viz/service/display_embedder/skia_output_device_dcomp.h
@@ -32,64 +32,49 @@
 
 namespace viz {
 
-class SkiaOutputDeviceDComp final : public SkiaOutputDevice {
+// Base class for DComp-backed OutputDevices.
+class SkiaOutputDeviceDComp : public SkiaOutputDevice {
  public:
-  SkiaOutputDeviceDComp(
-      gpu::MailboxManager* mailbox_manager,
-      gpu::SharedImageRepresentationFactory*
-          shared_image_representation_factory,
-      gpu::SharedContextState* context_state,
-      scoped_refptr<gl::GLSurface> gl_surface,
-      scoped_refptr<gpu::gles2::FeatureInfo> feature_info,
-      gpu::MemoryTracker* memory_tracker,
-      DidSwapBufferCompleteCallback did_swap_buffer_complete_callback);
-
   SkiaOutputDeviceDComp(const SkiaOutputDeviceDComp&) = delete;
   SkiaOutputDeviceDComp& operator=(const SkiaOutputDeviceDComp&) = delete;
 
   ~SkiaOutputDeviceDComp() override;
 
   // SkiaOutputDevice implementation:
-  bool Reshape(const SkSurfaceCharacterization& characterization,
-               const gfx::ColorSpace& color_space,
-               float device_scale_factor,
-               gfx::OverlayTransform transform) override;
   void SwapBuffers(BufferPresentedCallback feedback,
                    OutputSurfaceFrame frame) override;
   void PostSubBuffer(const gfx::Rect& rect,
                      BufferPresentedCallback feedback,
                      OutputSurfaceFrame frame) override;
-  void CommitOverlayPlanes(BufferPresentedCallback feedback,
-                           OutputSurfaceFrame frame) override;
-  bool SetDrawRectangle(const gfx::Rect& draw_rectangle) override;
-  void SetGpuVSyncEnabled(bool enabled) override;
-  void SetEnableDCLayers(bool enable) override;
   void ScheduleOverlays(SkiaOutputSurface::OverlayList overlays) override;
-  void EnsureBackbuffer() override;
-  void DiscardBackbuffer() override;
-  SkSurface* BeginPaint(
-      std::vector<GrBackendSemaphore>* end_semaphores) override;
-  void EndPaint() override;
 
- private:
+ protected:
+  SkiaOutputDeviceDComp(
+      gpu::MailboxManager* mailbox_manager,
+      gpu::SharedImageRepresentationFactory*
+          shared_image_representation_factory,
+      gpu::SharedContextState* context_state,
+      gl::GLSurface* gl_surface,
+      scoped_refptr<gpu::gles2::FeatureInfo> feature_info,
+      gpu::MemoryTracker* memory_tracker,
+      DidSwapBufferCompleteCallback did_swap_buffer_complete_callback);
+
   class OverlayData;
 
-  // Use instead of calling FinishSwapBuffers() directly. On Windows this cleans
-  // up old entries in |overlays_|.
-  void DoFinishSwapBuffers(const gfx::Size& size,
-                           OutputSurfaceFrame frame,
-                           gfx::SwapCompletionResult result);
-  // Used as callback for SwapBuffersAsync and PostSubBufferAsync to finish
-  // operation
-  void DoFinishSwapBuffersAsync(const gfx::Size& size,
-                                OutputSurfaceFrame frame,
-                                gfx::SwapCompletionResult result);
-
   gpu::OverlayImageRepresentation::ScopedReadAccess* BeginOverlayAccess(
       const gpu::Mailbox& mailbox);
 
   void CreateSkSurface();
 
+  virtual bool ScheduleDCLayer(
+      std::unique_ptr<ui::DCRendererLayerParams> params) = 0;
+
+  virtual gfx::Size GetRootSurfaceSize() const = 0;
+
+  virtual gfx::SwapResult DoPostSubBuffer(const gfx::Rect& rect,
+                                          BufferPresentedCallback feedback,
+                                          gl::FrameData data) = 0;
+
   // Mailboxes of overlays scheduled in the current frame.
   base::flat_set<gpu::Mailbox> scheduled_overlay_mailboxes_;
 
@@ -102,21 +87,57 @@
       shared_image_representation_factory_;
 
   const raw_ptr<gpu::SharedContextState> context_state_;
-  scoped_refptr<gl::GLSurface> gl_surface_;
-  const bool supports_async_swap_;
-
-  uint64_t backbuffer_estimated_size_ = 0;
-
-  gfx::Size size_;
-  SkColorType color_type_;
-  gfx::ColorSpace color_space_;
-  GrGLFramebufferInfo framebuffer_info_ = {};
-  int sample_count_ = 1;
-  sk_sp<SkSurface> sk_surface_;
 
   base::WeakPtrFactory<SkiaOutputDeviceDComp> weak_ptr_factory_{this};
 };
 
+// A DComp-backed OutputDevice whose root surface is wrapped in a GLSurface.
+class VIZ_SERVICE_EXPORT SkiaOutputDeviceDCompGLSurface final
+    : public SkiaOutputDeviceDComp {
+ public:
+  SkiaOutputDeviceDCompGLSurface(
+      gpu::MailboxManager* mailbox_manager,
+      gpu::SharedImageRepresentationFactory*
+          shared_image_representation_factory,
+      gpu::SharedContextState* context_state,
+      scoped_refptr<gl::GLSurface> gl_surface,
+      scoped_refptr<gpu::gles2::FeatureInfo> feature_info,
+      gpu::MemoryTracker* memory_tracker,
+      DidSwapBufferCompleteCallback did_swap_buffer_complete_callback);
+
+  ~SkiaOutputDeviceDCompGLSurface() override;
+
+  // SkiaOutputDevice implementation:
+  bool Reshape(const SkSurfaceCharacterization& characterization,
+               const gfx::ColorSpace& color_space,
+               float device_scale_factor,
+               gfx::OverlayTransform transform) override;
+  bool SetDrawRectangle(const gfx::Rect& draw_rectangle) override;
+  void SetEnableDCLayers(bool enable) override;
+  void SetGpuVSyncEnabled(bool enabled) override;
+  SkSurface* BeginPaint(
+      std::vector<GrBackendSemaphore>* end_semaphores) override;
+  void EndPaint() override;
+
+ protected:
+  bool ScheduleDCLayer(
+      std::unique_ptr<ui::DCRendererLayerParams> params) override;
+  gfx::Size GetRootSurfaceSize() const override;
+  gfx::SwapResult DoPostSubBuffer(const gfx::Rect& rect,
+                                  BufferPresentedCallback feedback,
+                                  gl::FrameData data) override;
+
+ private:
+  scoped_refptr<gl::GLSurface> gl_surface_;
+
+  gfx::Size size_;
+  gfx::ColorSpace color_space_;
+  GrGLFramebufferInfo framebuffer_info_ = {};
+  sk_sp<SkSurface> sk_surface_;
+
+  uint64_t backbuffer_estimated_size_ = 0;
+};
+
 }  // namespace viz
 
 #endif  // COMPONENTS_VIZ_SERVICE_DISPLAY_EMBEDDER_SKIA_OUTPUT_DEVICE_DCOMP_H_
diff --git a/components/viz/service/display_embedder/skia_output_device_vulkan.cc b/components/viz/service/display_embedder/skia_output_device_vulkan.cc
index 11529228..d5da933 100644
--- a/components/viz/service/display_embedder/skia_output_device_vulkan.cc
+++ b/components/viz/service/display_embedder/skia_output_device_vulkan.cc
@@ -14,7 +14,6 @@
 #include "components/viz/common/gpu/vulkan_context_provider.h"
 #include "gpu/command_buffer/service/memory_tracking.h"
 #include "gpu/config/gpu_finch_features.h"
-#include "gpu/ipc/common/gpu_surface_lookup.h"
 #include "gpu/vulkan/vulkan_fence_helper.h"
 #include "gpu/vulkan/vulkan_function_pointers.h"
 #include "gpu/vulkan/vulkan_implementation.h"
@@ -28,6 +27,7 @@
 
 #if BUILDFLAG(IS_ANDROID)
 #include <android/native_window_jni.h>
+#include "gpu/ipc/common/gpu_surface_lookup.h"
 #include "ui/gl/android/scoped_a_native_window.h"
 #include "ui/gl/android/scoped_java_surface.h"
 #endif
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
index 59b7970..394aec6 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
+++ b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
@@ -1750,7 +1750,7 @@
         } else {
 #if BUILDFLAG(IS_WIN)
           if (gl_surface_->SupportsDCLayers()) {
-            output_device_ = std::make_unique<SkiaOutputDeviceDComp>(
+            output_device_ = std::make_unique<SkiaOutputDeviceDCompGLSurface>(
                 dependency_->GetMailboxManager(),
                 shared_image_representation_factory_.get(),
                 context_state_.get(), gl_surface_, feature_info_,
diff --git a/components/webxr/android/arcore_java_utils.cc b/components/webxr/android/arcore_java_utils.cc
index 028a871..7d41876 100644
--- a/components/webxr/android/arcore_java_utils.cc
+++ b/components/webxr/android/arcore_java_utils.cc
@@ -13,6 +13,7 @@
 #include "device/vr/android/arcore/arcore_shim.h"
 #include "gpu/ipc/common/gpu_surface_tracker.h"
 #include "ui/android/window_android.h"
+#include "ui/gl/android/scoped_java_surface.h"
 
 using base::android::AttachCurrentThread;
 using base::android::ScopedJavaLocalRef;
@@ -80,7 +81,8 @@
   gpu::SurfaceHandle surface_handle =
       gpu::GpuSurfaceTracker::Get()->AddSurfaceForNativeWidget(
           gpu::GpuSurfaceTracker::SurfaceRecord(
-              window, surface, /*can_be_used_with_surface_control=*/false));
+              gl::ScopedJavaSurface(surface, /*auto_release=*/false),
+              /*can_be_used_with_surface_control=*/false));
   ui::WindowAndroid* root_window =
       ui::WindowAndroid::FromJavaWindowAndroid(java_root_window);
   display::Display::Rotation display_rotation =
diff --git a/components/webxr/mailbox_to_surface_bridge_impl.cc b/components/webxr/mailbox_to_surface_bridge_impl.cc
index eed2e4b..c54cfae3 100644
--- a/components/webxr/mailbox_to_surface_bridge_impl.cc
+++ b/components/webxr/mailbox_to_surface_bridge_impl.cc
@@ -220,10 +220,9 @@
   gpu::GpuSurfaceTracker* tracker = gpu::GpuSurfaceTracker::Get();
   ANativeWindow_acquire(window);
   // Skip ANativeWindow_setBuffersGeometry, the default size appears to work.
-  surface_ = std::make_unique<gl::ScopedJavaSurface>(surface_texture);
   surface_handle_ =
       tracker->AddSurfaceForNativeWidget(gpu::GpuSurfaceTracker::SurfaceRecord(
-          window, surface_->j_surface(),
+          gl::ScopedJavaSurface(surface_texture),
           false /* can_be_used_with_surface_control */));
   // Unregistering happens in the destructor.
   ANativeWindow_release(window);
diff --git a/components/webxr/mailbox_to_surface_bridge_impl.h b/components/webxr/mailbox_to_surface_bridge_impl.h
index 2eb12c30..fc996c05d 100644
--- a/components/webxr/mailbox_to_surface_bridge_impl.h
+++ b/components/webxr/mailbox_to_surface_bridge_impl.h
@@ -88,7 +88,6 @@
   void DrawQuad(unsigned int textureHandle, const gfx::Transform& uv_transform);
 
   scoped_refptr<viz::ContextProvider> context_provider_;
-  std::unique_ptr<gl::ScopedJavaSurface> surface_;
   raw_ptr<gpu::gles2::GLES2Interface> gl_ = nullptr;
   raw_ptr<gpu::ContextSupport> context_support_ = nullptr;
   int surface_handle_ = gpu::kNullSurfaceHandle;
diff --git a/content/app/android/content_child_process_service_delegate.cc b/content/app/android/content_child_process_service_delegate.cc
index fbdd977..7acb6a65d 100644
--- a/content/app/android/content_child_process_service_delegate.cc
+++ b/content/app/android/content_child_process_service_delegate.cc
@@ -76,7 +76,8 @@
       return gl::ScopedJavaSurface();
 
     gl::ScopedJavaSurface surface(
-        content::JNI_SurfaceWrapper_getSurface(env, surface_wrapper));
+        content::JNI_SurfaceWrapper_takeSurface(env, surface_wrapper),
+        /*auto_release=*/true);
     DCHECK(!surface.j_surface().is_null());
 
     *can_be_used_with_surface_control =
diff --git a/content/browser/android/dialog_overlay_impl.cc b/content/browser/android/dialog_overlay_impl.cc
index 379bf3c..9e5a1b7 100644
--- a/content/browser/android/dialog_overlay_impl.cc
+++ b/content/browser/android/dialog_overlay_impl.cc
@@ -327,8 +327,8 @@
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   return gpu::GpuSurfaceTracker::Get()->AddSurfaceForNativeWidget(
       gpu::GpuSurfaceTracker::SurfaceRecord(
-          gfx::kNullAcceleratedWidget, surface,
-          false /* can_be_used_with_surface_control */));
+          gl::ScopedJavaSurface(surface, /*auto_release=*/false),
+          /*can_be_used_with_surface_control=*/false));
 }
 
 static void JNI_DialogOverlayImpl_UnregisterSurface(
diff --git a/content/browser/android/gpu_process_callback.cc b/content/browser/android/gpu_process_callback.cc
index aabd7bc..bfafcc1d 100644
--- a/content/browser/android/gpu_process_callback.cc
+++ b/content/browser/android/gpu_process_callback.cc
@@ -30,7 +30,7 @@
   base::android::ScopedJavaGlobalRef<jobject> jsurface;
   jsurface.Reset(env, surface);
   ScopedSurfaceRequestManager::GetInstance()->FulfillScopedSurfaceRequest(
-      requestToken, gl::ScopedJavaSurface(jsurface));
+      requestToken, gl::ScopedJavaSurface(jsurface, /*auto_release=*/true));
 }
 
 base::android::ScopedJavaLocalRef<jobject>
diff --git a/content/browser/attribution_reporting/attribution_internals.mojom b/content/browser/attribution_reporting/attribution_internals.mojom
index dd9fb0c..8d908b6 100644
--- a/content/browser/attribution_reporting/attribution_internals.mojom
+++ b/content/browser/attribution_reporting/attribution_internals.mojom
@@ -89,8 +89,9 @@
   DebugReportStatus status;
 };
 
-struct DebugKey {
-  uint64 value;
+union SourceDebugKey {
+  uint64 debug_key;
+  uint64 cleared_debug_key;
 };
 
 // Struct representing a stored attribution source that will be displayed by WebUI.
@@ -106,7 +107,7 @@
   double aggregatable_report_window_time;
   attribution_reporting.mojom.SourceType source_type;
   int64 priority;
-  DebugKey? debug_key;
+  SourceDebugKey? debug_key;
   array<uint64> dedup_keys;
   map<string, array<string>> filter_data;
   // The value is a hex-encoded unsigned 128-bit integer.
@@ -130,6 +131,10 @@
   Attributability attributability;
 };
 
+struct TriggerDebugKey {
+  uint64 value;
+};
+
 struct WebUITrigger {
   double trigger_time;
   url.mojom.Origin destination_origin;
@@ -137,6 +142,7 @@
   // String instead of mojo_base.mojom.DictionaryValue because the value is
   // simply displayed in the UI, never inspected.
   string registration_json;
+  TriggerDebugKey? cleared_debug_key;
 
   enum Status {
     // Shared statuses:
@@ -176,21 +182,6 @@
   attribution_reporting.mojom.SourceRegistrationError error;
 };
 
-// Struct containing details for a debug key that was removed from a source or
-// trigger due to a missing cookie to be displayed as a log by WebUI
-struct ClearedDebugKey {
-  double time;
-  url.mojom.Origin reporting_origin;
-  DebugKey cleared_debug_key;
-
-  enum Type {
-    kTrigger,
-    kSource,
-  };
-
-  Type cleared_from;
-};
-
 // Observer for events relevant to the attribution internals WebUI.
 interface Observer {
   // Called when the sources in storage changed, indicating that the observer
@@ -219,9 +210,6 @@
 
   // Called when the source header registration JSON parser fails.
   OnFailedSourceRegistration(FailedSourceRegistration failure);
-
-  // Called when a debug key was removed from a source or trigger due to a missing cookie.
-  OnDebugKeyCleared(ClearedDebugKey notification);
 };
 
 // Mojo interface for the attribution internals WebUI to communicate with the
diff --git a/content/browser/attribution_reporting/attribution_internals_browsertest.cc b/content/browser/attribution_reporting/attribution_internals_browsertest.cc
index abe25398..d95e016 100644
--- a/content/browser/attribution_reporting/attribution_internals_browsertest.cc
+++ b/content/browser/attribution_reporting/attribution_internals_browsertest.cc
@@ -299,7 +299,8 @@
 
   manager()->NotifySourceHandled(
       SourceBuilder(now + base::Hours(5)).Build(),
-      StorableSource::Result::kInsufficientSourceCapacity);
+      StorableSource::Result::kInsufficientSourceCapacity,
+      /*cleared_debug_key=*/987);
 
   manager()->NotifySourceHandled(
       SourceBuilder(now + base::Hours(6)).Build(),
@@ -327,6 +328,7 @@
           table.children[1].children[13]?.innerText === "1300 / 65536" &&
           table.children[0].children[14]?.innerText === "19" &&
           table.children[1].children[14]?.innerText === "" &&
+          table.children[4].children[14]?.innerText === 'Cleared (was 987)' &&
           table.children[0].children[15]?.innerText === "" &&
           table.children[1].children[15]?.innerText === "13, 17" &&
           table.children[0].children[16]?.innerText === "" &&
@@ -392,89 +394,6 @@
 }
 
 IN_PROC_BROWSER_TEST_F(AttributionInternalsWebUiBrowserTest,
-                       ClearedDebugKeyFromSource_LogShown) {
-  ASSERT_TRUE(NavigateToURL(shell(), GURL(kAttributionInternalsUrl)));
-
-  static constexpr char wait_script[] = R"(
-    const table = document.querySelector('#logTable')
-        .shadowRoot.querySelector('tbody');
-
-    const description = '<a href="https://github.com/WICG/attribution-report' +
-                    'ing-api/blob/main/EVENT.md#attribution-success-debugging-' +
-                    'reports" target="_blank">Cleared Debug Key</a>';
-    const metadata = '<dl><dt>Cleared Debug Key</dt><dd>1234</dd>' +
-                     '<dt>From</dt><dd>Source</dd>'+
-                     '<dt>Reporting Origin</dt><dd>https://report.test</dd></dl>';
-
-    let obs = new MutationObserver((_, obs) => {
-      if (table.children.length === 1 &&
-          table.children[0].children[1]?.innerHTML === description &&
-          table.children[0].children[2]?.innerHTML === metadata
-      ) {
-        obs.disconnect();
-        document.title = $1;
-      }
-    });
-    obs.observe(table, {childList: true, subtree: true, characterData: true});)";
-
-  ASSERT_TRUE(ExecJsInWebUI(JsReplace(wait_script, kCompleteTitle)));
-
-  TitleWatcher title_watcher(shell()->web_contents(), kCompleteTitle);
-
-  manager()->NotifySourceHandled(SourceBuilder().Build(),
-                                 StorableSource::Result::kSuccess,
-                                 /*cleared_debug_key=*/1234);
-
-  EXPECT_EQ(kCompleteTitle, title_watcher.WaitAndGetTitle());
-}
-
-IN_PROC_BROWSER_TEST_F(AttributionInternalsWebUiBrowserTest,
-                       ClearedDebugKeyFromTrigger_LogShown) {
-  ASSERT_TRUE(NavigateToURL(shell(), GURL(kAttributionInternalsUrl)));
-
-  static constexpr char wait_script[] = R"(
-    const table = document.querySelector('#logTable')
-        .shadowRoot.querySelector('tbody');
-
-    const description = '<a href="https://github.com/WICG/attribution-report' +
-                    'ing-api/blob/main/EVENT.md#attribution-success-debugging-' +
-                    'reports" target="_blank">Cleared Debug Key</a>';
-    const metadata = '<dl><dt>Cleared Debug Key</dt><dd>1234</dd>' +
-                     '<dt>From</dt><dd>Trigger</dd>'+
-                     '<dt>Reporting Origin</dt><dd>https://report.test</dd></dl>';
-
-    let obs = new MutationObserver((_, obs) => {
-      if (table.children.length === 1 &&
-          table.children[0].children[1]?.innerHTML === description &&
-          table.children[0].children[2]?.innerHTML === metadata
-      ) {
-        obs.disconnect();
-        document.title = $1;
-      }
-    });
-    obs.observe(table, {childList: true, subtree: true, characterData: true});)";
-
-  ASSERT_TRUE(ExecJsInWebUI(JsReplace(wait_script, kCompleteTitle)));
-
-  TitleWatcher title_watcher(shell()->web_contents(), kCompleteTitle);
-
-  manager()->NotifyTriggerHandled(
-      DefaultTrigger(),
-      CreateReportResult(
-          /*trigger_time=*/base::Time::Now(),
-          /*event_level_status=*/AttributionTrigger::EventLevelResult::kSuccess,
-          /*aggregatable_status=*/
-          AttributionTrigger::AggregatableResult::kSuccess,
-          /*replaced_event_level_report=*/absl::nullopt,
-          /*new_event_level_report=*/IrreleventEventLevelReport(),
-          /*new_aggregatable_report=*/IrreleventAggregatableReport(),
-          /*source=*/SourceBuilder().BuildStored()),
-      /*cleared_debug_key=*/1234);
-
-  EXPECT_EQ(kCompleteTitle, title_watcher.WaitAndGetTitle());
-}
-
-IN_PROC_BROWSER_TEST_F(AttributionInternalsWebUiBrowserTest,
                        WebUIShownWithNoReports_NoReportsDisplayed) {
   ASSERT_TRUE(NavigateToURL(shell(), GURL(kAttributionInternalsUrl)));
 
@@ -1091,12 +1010,14 @@
       let table = document.querySelector('#triggerTable')
           .shadowRoot.querySelector('tbody');
       let obs = new MutationObserver((_, obs) => {
-        if (table.children.length === 1 &&
+        if (table.children.length === 2 &&
             table.children[0].children[1]?.innerText === "Success: Report stored" &&
             table.children[0].children[2]?.innerText === "Success: Report stored" &&
             table.children[0].children[3]?.innerText === "https://d.test" &&
             table.children[0].children[4]?.innerText === "https://r.test" &&
-            table.children[0].children[5]?.innerText.includes('{')) {
+            table.children[0].children[5]?.innerText.includes('{') &&
+            table.children[0].children[6]?.innerText === '' &&
+            table.children[1].children[6]?.innerText === '123') {
           obs.disconnect();
           document.title = $1;
         }
@@ -1106,7 +1027,8 @@
 
   auto notify_trigger_handled =
       [&](AttributionTrigger::EventLevelResult event_status,
-          AttributionTrigger::AggregatableResult aggregatable_status) {
+          AttributionTrigger::AggregatableResult aggregatable_status,
+          absl::optional<uint64_t> cleared_debug_key = absl::nullopt) {
         static int offset_hours = 0;
         manager()->NotifyTriggerHandled(
             trigger,
@@ -1116,12 +1038,17 @@
                 /*replaced_event_level_report=*/absl::nullopt,
                 /*new_event_level_report=*/IrreleventEventLevelReport(),
                 /*new_aggregatable_report=*/IrreleventAggregatableReport(),
-                /*source=*/SourceBuilder().BuildStored()));
+                /*source=*/SourceBuilder().BuildStored()),
+            cleared_debug_key);
       };
 
   notify_trigger_handled(AttributionTrigger::EventLevelResult::kSuccess,
                          AttributionTrigger::AggregatableResult::kSuccess);
 
+  notify_trigger_handled(AttributionTrigger::EventLevelResult::kSuccess,
+                         AttributionTrigger::AggregatableResult::kSuccess,
+                         /*cleared_debug_key=*/123);
+
   // TODO(apaseltiner): Add tests for other statuses.
 
   TitleWatcher title_watcher(shell()->web_contents(), kCompleteTitle);
diff --git a/content/browser/attribution_reporting/attribution_internals_handler_impl.cc b/content/browser/attribution_reporting/attribution_internals_handler_impl.cc
index 1896281..f0b6bf0 100644
--- a/content/browser/attribution_reporting/attribution_internals_handler_impl.cc
+++ b/content/browser/attribution_reporting/attribution_internals_handler_impl.cc
@@ -54,34 +54,38 @@
 using Attributability =
     ::attribution_internals::mojom::WebUISource::Attributability;
 
-using SourceOrTrigger = ::attribution_internals::mojom::ClearedDebugKey::Type;
 using Empty = ::attribution_internals::mojom::Empty;
 using ReportStatus = ::attribution_internals::mojom::ReportStatus;
 using ReportStatusPtr = ::attribution_internals::mojom::ReportStatusPtr;
 
 using ::attribution_internals::mojom::WebUIDebugReport;
 
-attribution_internals::mojom::DebugKeyPtr WebUIDebugKey(
-    absl::optional<uint64_t> debug_key) {
-  return debug_key ? attribution_internals::mojom::DebugKey::New(*debug_key)
-                   : nullptr;
-}
-
 attribution_internals::mojom::WebUISourcePtr WebUISource(
     const CommonSourceInfo& source,
     Attributability attributability,
     const std::vector<uint64_t>& dedup_keys,
     int64_t aggregatable_budget_consumed,
     const std::vector<uint64_t>& aggregatable_dedup_keys,
-    bool debug_reporting_enabled) {
+    bool debug_reporting_enabled,
+    absl::optional<uint64_t> cleared_debug_key) {
   DCHECK_GE(aggregatable_budget_consumed, 0);
+
+  attribution_internals::mojom::SourceDebugKeyPtr debug_key =
+      cleared_debug_key
+          ? attribution_internals::mojom::SourceDebugKey::NewClearedDebugKey(
+                *cleared_debug_key)
+          : (source.debug_key()
+                 ? attribution_internals::mojom::SourceDebugKey::NewDebugKey(
+                       *source.debug_key())
+                 : nullptr);
+
   return attribution_internals::mojom::WebUISource::New(
       source.source_event_id(), source.source_origin(),
       source.DestinationSite().Serialize(), source.reporting_origin(),
       source.source_time().ToJsTime(), source.expiry_time().ToJsTime(),
       source.event_report_window_time().ToJsTime(),
       source.aggregatable_report_window_time().ToJsTime(), source.source_type(),
-      source.priority(), WebUIDebugKey(source.debug_key()), dedup_keys,
+      source.priority(), std::move(debug_key), dedup_keys,
       source.filter_data().filter_values(),
       base::MakeFlatMap<std::string, std::string>(
           source.aggregation_keys().keys(), {},
@@ -125,7 +129,8 @@
     web_ui_sources.push_back(WebUISource(
         source.common_info(), attributability, source.dedup_keys(),
         source.aggregatable_budget_consumed(), source.aggregatable_dedup_keys(),
-        /*debug_reporting_enabled=*/false));
+        /*debug_reporting_enabled=*/false,
+        /*cleared_debug_key=*/absl::nullopt));
   }
 
   std::move(web_ui_callback).Run(std::move(web_ui_sources));
@@ -298,18 +303,6 @@
     absl::optional<uint64_t> cleared_debug_key,
     StorableSource::Result result) {
   Attributability attributability;
-  if (cleared_debug_key.has_value()) {
-    auto web_ui_log = attribution_internals::mojom::ClearedDebugKey::New();
-    web_ui_log->cleared_debug_key = WebUIDebugKey(cleared_debug_key.value());
-    web_ui_log->time = source.common_info().source_time().ToJsTime();
-    web_ui_log->reporting_origin = source.common_info().reporting_origin();
-    web_ui_log->cleared_from = SourceOrTrigger::kSource;
-
-    for (auto& observer : observers_) {
-      observer->OnDebugKeyCleared(web_ui_log.Clone());
-    }
-  }
-
   switch (result) {
     case StorableSource::Result::kSuccess:
     // TODO(linnan): Consider displaying source noised in internals UI.
@@ -335,7 +328,8 @@
   auto web_ui_source =
       WebUISource(source.common_info(), attributability, /*dedup_keys=*/{},
                   /*aggregatable_budget_consumed=*/0,
-                  /*aggregatable_dedup_keys=*/{}, source.debug_reporting());
+                  /*aggregatable_dedup_keys=*/{}, source.debug_reporting(),
+                  cleared_debug_key);
 
   for (auto& observer : observers_) {
     observer->OnSourceRejected(web_ui_source.Clone());
@@ -496,17 +490,6 @@
   const attribution_reporting::TriggerRegistration& registration =
       trigger.registration();
 
-  if (cleared_debug_key.has_value()) {
-    auto web_ui_log = attribution_internals::mojom::ClearedDebugKey::New();
-    web_ui_log->cleared_debug_key = WebUIDebugKey(cleared_debug_key.value());
-    web_ui_log->time = result.trigger_time().ToJsTime();
-    web_ui_log->reporting_origin = trigger.reporting_origin();
-    web_ui_log->cleared_from = SourceOrTrigger::kTrigger;
-
-    for (auto& observer : observers_) {
-      observer->OnDebugKeyCleared(web_ui_log.Clone());
-    }
-  }
   auto web_ui_trigger = attribution_internals::mojom::WebUITrigger::New();
   web_ui_trigger->trigger_time = result.trigger_time().ToJsTime();
   web_ui_trigger->destination_origin = trigger.destination_origin();
@@ -514,6 +497,10 @@
   web_ui_trigger->registration_json =
       SerializeAttributionJson(registration.ToJson(),
                                /*pretty_print=*/true);
+  web_ui_trigger->cleared_debug_key =
+      cleared_debug_key ? attribution_internals::mojom::TriggerDebugKey::New(
+                              *cleared_debug_key)
+                        : nullptr;
   web_ui_trigger->event_level_status =
       GetWebUITriggerStatus(result.event_level_status());
   web_ui_trigger->aggregatable_status =
diff --git a/content/browser/back_forward_cache_no_store_browsertest.cc b/content/browser/back_forward_cache_no_store_browsertest.cc
index 3262bdcc..194674c 100644
--- a/content/browser/back_forward_cache_no_store_browsertest.cc
+++ b/content/browser/back_forward_cache_no_store_browsertest.cc
@@ -750,12 +750,12 @@
 namespace {
 // Causes a fetch using the "Authorization" header to start and complete in the
 // target frame.
-void UseAuthorizationHeaderFetch(const ToRenderFrameHost& execution_target,
-                                 const GURL& url) {
+void UseAuthorizationHeader(const ToRenderFrameHost& execution_target,
+                            GURL url) {
   ASSERT_EQ(42, EvalJs(execution_target, JsReplace(
                                              R"(
       fetch($1, {headers: {Authorization: 'foo'}})
-          .then(p => {
+          .then(p   => {
               // Ensure that we drain the pipe to avoid blocking on network
               // activity.
               p.text();
@@ -765,23 +765,6 @@
                                              url)));
 }
 
-// Causes an XHR using the "Authorization" header to start and complete in the
-// target frame.
-void UseAuthorizationHeaderXhr(const ToRenderFrameHost& execution_target,
-                               const GURL& url) {
-  ASSERT_EQ(42, EvalJs(execution_target, JsReplace(
-                                             R"(
-      const xhr = new XMLHttpRequest();
-      xhr.open('GET', $1);
-      xhr.setRequestHeader('Authorization', 'foo');
-      xhr.send();
-      new Promise(resolve => {
-        xhr.onload = () => {resolve(42)};
-      });
-      )",
-                                             url)));
-}
-
 // Creates an iframe in the target frame with this url. It waits until the frame
 // has loaded.
 void CreateIframe(const ToRenderFrameHost& execution_target, GURL url) {
@@ -798,49 +781,9 @@
 }
 }  // namespace
 
-enum class RequestType {
-  kFetch,
-  kXhr,
-};
-
-class BackForwardCacheAuthorizationHeaderBrowserTest
-    : public BackForwardCacheBrowserTest,
-      public ::testing::WithParamInterface<RequestType> {
- public:
-  // Provides meaningful param names instead of /0 and /1.
-  static std::string DescribeParams(
-      const ::testing::TestParamInfo<ParamType>& info) {
-    switch (info.param) {
-      case RequestType::kFetch:
-        return "Fetch";
-      case RequestType::kXhr:
-        return "XHR";
-    }
-  }
-
- protected:
-  // Make a request using the appropriate method.
-  void UseAuthorizationHeader(const ToRenderFrameHost& execution_target,
-                              GURL url) {
-    switch (GetParam()) {
-      case RequestType::kFetch:
-        UseAuthorizationHeaderFetch(execution_target, url);
-        break;
-      case RequestType::kXhr:
-        UseAuthorizationHeaderXhr(execution_target, url);
-        break;
-    }
-  }
-};
-INSTANTIATE_TEST_SUITE_P(
-    All,
-    BackForwardCacheAuthorizationHeaderBrowserTest,
-    ::testing::Values(RequestType::kFetch, RequestType::kXhr),
-    &BackForwardCacheAuthorizationHeaderBrowserTest::DescribeParams);
-
-// Test that a page without CCNS that makes a request with the "Authorization"
+// Test that a page without CCNS that makes a fetch with the "Authorization"
 // header does not log the header.
-IN_PROC_BROWSER_TEST_P(BackForwardCacheAuthorizationHeaderBrowserTest,
+IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                        AuthorizationHeaderNotLogged) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
@@ -851,7 +794,7 @@
   ASSERT_TRUE(NavigateToURL(shell(), url_a));
   RenderFrameHostImplWrapper rfh_a(current_frame_host());
 
-  // Make a request with the "Authorization" header in the main frame.
+  // Do a fetch with the "Authorization" header in the main frame.
   UseAuthorizationHeader(shell(), url_a);
 
   // Navigate away.
@@ -865,9 +808,9 @@
   ExpectRestored(FROM_HERE);
 }
 
-// Test that a page with CCNS that makes a request with the "Authorization"
-// header logs the header.
-IN_PROC_BROWSER_TEST_P(BackForwardCacheAuthorizationHeaderBrowserTest,
+// Test that a page with CCNS that makes a fetch with the "Authorization" header
+// logs the header.
+IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                        AuthorizationHeaderLoggedMainFrame) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
@@ -880,7 +823,7 @@
   ASSERT_TRUE(NavigateToURL(shell(), url_a_no_store));
   RenderFrameHostImplWrapper rfh_a(current_frame_host());
 
-  // Make a request with the "Authorization" header in the main frame.
+  // Do a fetch with the "Authorization" header in the main frame.
   UseAuthorizationHeader(shell(), url_a_2);
 
   // Navigate away.
@@ -897,10 +840,9 @@
                     {}, {}, {}, FROM_HERE);
 }
 
-// Test that a page with CCNS that makes a request with the "Authorization"
-// header in a same-as-root-origin subframe of a cross-origin subframe logs the
-// header.
-IN_PROC_BROWSER_TEST_P(BackForwardCacheAuthorizationHeaderBrowserTest,
+// Test that a page with CCNS that makes a fetch with the "Authorization" header
+// in a same-as-root-origin subframe of a cross-origin subframe logs the header.
+IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                        AuthorizationHeaderSameOriginSubFrameLogged) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
@@ -914,8 +856,7 @@
   RenderFrameHostImplWrapper rfh_a(current_frame_host());
 
   // Create a cross-origin iframe with same-as-root-origin iframe inside that
-  // and make a request with the "Authorization" header in that grand-child
-  // iframe.
+  // and do a fetch with the "Authorization" header in that grand-child iframe.
   CreateIframe(rfh_a.get(), url_b);
   CreateIframe(DescendantRenderFrameHostImplAt(rfh_a.get(), {0}), url_a_2);
 
@@ -936,10 +877,10 @@
                     {}, {}, {}, FROM_HERE);
 }
 
-// Test that a page with CCNS that makes a request with the "Authorization"
-// header in a same-origin subframe logs the header in the correct place in the
-// tree of reasons.
-IN_PROC_BROWSER_TEST_P(BackForwardCacheAuthorizationHeaderBrowserTest,
+// Test that a page with CCNS that makes a fetch with the "Authorization" header
+// in a same-origin subframe logs the header in the correct place in the tree of
+// reasons.
+IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                        AuthorizationHeaderSubFrameTree) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
@@ -952,7 +893,7 @@
   ASSERT_TRUE(NavigateToURL(shell(), url_a_no_store));
   RenderFrameHostImplWrapper rfh_a(current_frame_host());
 
-  // Create a same-origin iframe make a request with the "Authorization" header.
+  // Create a same-origin iframe do a fetch with the "Authorization" header.
   CreateIframe(rfh_a.get(), url_a_2);
 
   UseAuthorizationHeader(DescendantRenderFrameHostImplAt(rfh_a.get(), {0}),
@@ -989,9 +930,9 @@
                       {subframe_result})));
 }
 
-// Test that a page with CCNS that makes a request with the "Authorization"
-// header in a cross-origin subframe does not log the header.
-IN_PROC_BROWSER_TEST_P(BackForwardCacheAuthorizationHeaderBrowserTest,
+// Test that a page with CCNS that makes a fetch with the "Authorization" header
+// in a cross-origin subframe does not log the header.
+IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
                        AuthorizationHeaderCrossOriginSubFrameNotLogged) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
@@ -1004,8 +945,8 @@
   ASSERT_TRUE(NavigateToURL(shell(), url_a_no_store));
   RenderFrameHostImplWrapper rfh_a(current_frame_host());
 
-  // Create an same-origin iframe and make a request with the "Authorization"
-  // header in that iframe.
+  // Create an same-origin iframe and do a fetch with the "Authorization" header
+  // in that iframe.
   CreateIframe(rfh_a.get(), url_b);
 
   UseAuthorizationHeader(DescendantRenderFrameHostImplAt(rfh_a.get(), {0}),
@@ -1069,9 +1010,7 @@
 }
 
 // Test that a page with CCNS that makes a fetch with the "Authorization" header
-// is blocked even when CCNS pages are allowed to be restored. This only tests
-// fetch, the blocking mechanism is the same for all kinds of requests, so if it
-// works for one it will work for all.
+// is blocked even when CCNS pages are allowed to be restored.
 IN_PROC_BROWSER_TEST_F(
     BackForwardCacheBrowserTestRestoreCacheControlNoStoreUnlessCookieChange,
     AuthorizationHeaderBlocks) {
@@ -1086,8 +1025,8 @@
   ASSERT_TRUE(NavigateToURL(shell(), url_a_no_store));
   RenderFrameHostImplWrapper rfh_a(current_frame_host());
 
-  // Make a request with the "Authorization" header in the main frame.
-  UseAuthorizationHeaderFetch(shell(), url_a_2);
+  // Do a fetch with the "Authorization" header in the main frame.
+  UseAuthorizationHeader(shell(), url_a_2);
 
   // Navigate away.
   ASSERT_TRUE(NavigateToURL(shell(), url_b));
diff --git a/content/browser/child_process_security_policy_unittest.cc b/content/browser/child_process_security_policy_unittest.cc
index 25d1eaf..12805d2 100644
--- a/content/browser/child_process_security_policy_unittest.cc
+++ b/content/browser/child_process_security_policy_unittest.cc
@@ -3221,6 +3221,9 @@
                        ProcessLock::FromSiteInfo(bar_instance->GetSiteInfo()));
       },
       "Cannot lock an already used process to .*bar\\.com");
+
+  // We need to remove it otherwise other tests may fail.
+  p->Remove(kRendererID);
 }
 
 }  // namespace content
diff --git a/content/browser/file_system/file_system_manager_impl.cc b/content/browser/file_system/file_system_manager_impl.cc
index cf6639ba..21cd61d 100644
--- a/content/browser/file_system/file_system_manager_impl.cc
+++ b/content/browser/file_system/file_system_manager_impl.cc
@@ -993,6 +993,28 @@
     RegisterBlobCallback callback) {
   storage::FileSystemURL crack_url =
       context_->CrackURL(url, receivers_.current_context());
+
+  content::GetUIThreadTaskRunner({})->PostTaskAndReplyWithResult(
+      FROM_HERE,
+      // security_policy_ is a singleton so refcounting is unnecessary
+      base::BindOnce(&ChildProcessSecurityPolicyImpl::CanReadFileSystemFile,
+                     base::Unretained(security_policy_), process_id_,
+                     crack_url),
+      base::BindOnce(&FileSystemManagerImpl::ContinueRegisterBlob,
+                     weak_factory_.GetWeakPtr(), content_type, url, length,
+                     expected_modification_time, std::move(callback),
+                     crack_url));
+}
+
+void FileSystemManagerImpl::ContinueRegisterBlob(
+    const std::string& content_type,
+    const GURL& url,
+    uint64_t length,
+    absl::optional<base::Time> expected_modification_time,
+    RegisterBlobCallback callback,
+    storage::FileSystemURL crack_url,
+    bool security_check_success) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
   std::string uuid = base::GenerateGUID();
   mojo::PendingRemote<blink::mojom::Blob> blob_remote;
   mojo::PendingReceiver<blink::mojom::Blob> blob_receiver =
@@ -1000,7 +1022,7 @@
 
   if (crack_url.is_valid() &&
       context_->GetFileSystemBackend(crack_url.type()) &&
-      security_policy_->CanReadFileSystemFile(process_id_, crack_url)) {
+      security_check_success) {
     blob_storage_context_->CreateFileSystemBlob(
         context_, std::move(blob_receiver), crack_url, uuid, content_type,
         length, expected_modification_time.value_or(base::Time()));
diff --git a/content/browser/file_system/file_system_manager_impl.h b/content/browser/file_system/file_system_manager_impl.h
index 4e84e97..d89a708 100644
--- a/content/browser/file_system/file_system_manager_impl.h
+++ b/content/browser/file_system/file_system_manager_impl.h
@@ -209,6 +209,15 @@
   void ContinueCreateSnapshotFile(const storage::FileSystemURL& url,
                                   CreateSnapshotFileCallback callback,
                                   bool security_check_success);
+  void ContinueRegisterBlob(
+      const std::string& content_type,
+      const GURL& url,
+      uint64_t length,
+      absl::optional<base::Time> expected_modification_time,
+      RegisterBlobCallback callback,
+      storage::FileSystemURL crack_url,
+      bool security_check_success);
+
   void Cancel(
       OperationID op_id,
       blink::mojom::FileSystemCancellableOperation::CancelCallback callback);
diff --git a/content/browser/gpu/gpu_process_host.cc b/content/browser/gpu/gpu_process_host.cc
index 63f23b3..de753e5 100644
--- a/content/browser/gpu/gpu_process_host.cc
+++ b/content/browser/gpu/gpu_process_host.cc
@@ -111,10 +111,6 @@
 #include "content/common/zygote/zygote_handle_impl_linux.h"
 #endif
 
-#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_ANDROID)
-#include "gpu/ipc/common/gpu_surface_tracker.h"
-#endif
-
 #if BUILDFLAG(IS_MAC)
 #include "content/browser/gpu/ca_transaction_gpu_coordinator.h"
 #endif
diff --git a/content/browser/loader/web_transport_browsertest.cc b/content/browser/loader/web_transport_browsertest.cc
index 72730c9a..a198023 100644
--- a/content/browser/loader/web_transport_browsertest.cc
+++ b/content/browser/loader/web_transport_browsertest.cc
@@ -108,5 +108,44 @@
   ASSERT_TRUE(WaitForTitle(u"PASS", {u"FAIL"}));
 }
 
+// A test that aims to reproduce https://crbug.com/1369030 -- note that since
+// the bug in question is a race condition, this test will probably be flaky if
+// this is actually broken.
+IN_PROC_BROWSER_TEST_F(WebTransportBrowserTest, EchoLargeBidirectionalStreams) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  ASSERT_TRUE(
+      NavigateToURL(shell(), embedded_test_server()->GetURL("/title2.html")));
+
+  ASSERT_TRUE(WaitForTitle(u"Title Of Awesomeness"));
+
+  ASSERT_TRUE(
+      ExecJs(shell(), base::StringPrintf(R"JS(
+    async function run() {
+      const transport = new WebTransport('https://localhost:%d/echo');
+      await transport.ready;
+
+      const numBytes = 1024 * 1024;
+      const numStreams = 5;
+      for (let i = 0; i < numStreams; i++) {
+        const stream = await transport.createBidirectionalStream();
+        const writer = stream.writable.getWriter();
+        await writer.write(new Uint8Array(numBytes));
+        await writer.close();
+        let response = await (new Response(stream.readable).arrayBuffer());
+        if (response.byteLength != numBytes) {
+          throw Error('Size mismatch, received size: '
+                         + response.byteLength.toString());
+        }
+      }
+    }
+
+    run().then(() => { document.title = 'PASS'; },
+               (e) => { console.log(e); document.title = 'FAIL'; });
+)JS",
+                                         server_.server_address().port())));
+
+  ASSERT_TRUE(WaitForTitle(u"PASS", {u"FAIL"}));
+}
+
 }  // namespace
 }  // namespace content
diff --git a/content/browser/media/cdm_registry_impl.cc b/content/browser/media/cdm_registry_impl.cc
index 3dc2047..d577857d 100644
--- a/content/browser/media/cdm_registry_impl.cc
+++ b/content/browser/media/cdm_registry_impl.cc
@@ -188,29 +188,6 @@
                               {media::CdmSessionType::kTemporary});
 }
 
-// Software secure capability can be obtained synchronously in all supported
-// cases. If needed, this can be easily converted to an asynchronous call.
-absl::optional<media::CdmCapability> GetSoftwareSecureCapability(
-    const CdmRegistryImpl& cdm_registry_impl,
-    const std::string& key_system) {
-  auto cdm_info = cdm_registry_impl.GetCdmInfo(
-      key_system, CdmInfo::Robustness::kSoftwareSecure);
-  if (!cdm_info) {
-    ReportSoftwareSecureCdmAvailableUMA(key_system, false);
-    return absl::nullopt;
-  }
-
-  ReportSoftwareSecureCdmAvailableUMA(key_system, true);
-
-  if (!cdm_info->capability) {
-    DVLOG(1) << "Lazy initialization of SoftwareSecure CdmCapability not "
-                "supported!";
-    return absl::nullopt;
-  }
-
-  return cdm_info->capability;
-}
-
 #if BUILDFLAG(IS_WIN)
 bool IsMediaFoundationHardwareSecurityDisabledByGpuFeature() {
   auto* gpu_data_manager = GpuDataManagerImpl::GetInstance();
@@ -226,71 +203,6 @@
 }
 #endif  // BUILDFLAG(IS_WIN)
 
-// Trying to get hardware secure capability synchronously. If lazy
-// initialization is needed, set `lazy_initialize` to true.
-std::tuple<absl::optional<media::CdmCapability>, CdmInfo::Status>
-GetHardwareSecureCapability(const CdmRegistryImpl& cdm_registry_impl,
-                            const std::string& key_system) {
-  using Status = CdmInfo::Status;
-
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-  if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
-          switches::kLacrosUseChromeosProtectedMedia)) {
-    return {absl::nullopt, Status::kHardwareSecureDecryptionDisabled};
-  }
-#elif !BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA)
-  if (!media::IsHardwareSecureDecryptionEnabled()) {
-    DVLOG(1) << "Hardware secure decryption disabled";
-    return {absl::nullopt, Status::kHardwareSecureDecryptionDisabled};
-  }
-#endif  // !BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA)
-
-  // Secure codecs override takes precedence over other checks.
-  auto overridden_capability =
-      GetHardwareSecureCapabilityOverriddenFromCommandLine();
-  if (overridden_capability) {
-    DVLOG(1) << "Hardware secure codecs overridden from command line";
-    return {overridden_capability, Status::kCommandLineOverridden};
-  }
-
-  // Hardware secure video codecs need hardware video decoder support.
-  // TODO(xhwang): Make sure this check is as close as possible to the check
-  // in the render process. For example, also check check GPU features like
-  // GPU_FEATURE_TYPE_ACCELERATED_VIDEO_DECODE.
-  auto* command_line = base::CommandLine::ForCurrentProcess();
-  if (command_line &&
-      command_line->HasSwitch(switches::kDisableAcceleratedVideoDecode)) {
-    DVLOG(1) << "Hardware security not supported because accelerated video "
-                "decode disabled";
-    return {absl::nullopt, Status::kAcceleratedVideoDecodeDisabled};
-  }
-
-#if BUILDFLAG(IS_WIN)
-  if (IsMediaFoundationHardwareSecurityDisabledByGpuFeature()) {
-    DVLOG(1) << "Hardware security not supported: GPU workarounds";
-    return {absl::nullopt, Status::kGpuFeatureDisabled};
-  }
-
-  if (IsGpuHardwareCompositionDisabled()) {
-    DVLOG(1) << "Hardware security not supported: GPU composition disabled";
-    return {absl::nullopt, Status::kGpuCompositionDisabled};
-  }
-#endif  // BUILDFLAG(IS_WIN)
-
-  auto cdm_info = cdm_registry_impl.GetCdmInfo(
-      key_system, CdmInfo::Robustness::kHardwareSecure);
-  if (!cdm_info) {
-    DVLOG(1) << "No Hardware secure decryption CDM registered";
-    return {absl::nullopt, Status::kEnabled};
-  }
-
-  DCHECK(!(cdm_info->status == CdmInfo::Status::kUninitialized &&
-           cdm_info->capability))
-      << "Capability should not have value if uninitialized.";
-
-  return {cdm_info->capability, cdm_info->status};
-}
-
 }  // namespace
 
 // static
@@ -326,7 +238,7 @@
 
 void CdmRegistryImpl::RegisterCdm(const CdmInfo& info) {
   DVLOG(1) << __func__ << ": key_system=" << info.key_system
-           << ", robustness=" << static_cast<int>(info.robustness)
+           << ", robustness=" << info.robustness
            << ", status=" << static_cast<int>(info.status);
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
@@ -399,7 +311,8 @@
 std::unique_ptr<CdmInfo> CdmRegistryImpl::GetCdmInfo(
     const std::string& key_system,
     CdmInfo::Robustness robustness) const {
-  DVLOG(2) << __func__;
+  DVLOG(2) << __func__ << ": key_system=" << key_system
+           << ", robustness=" << robustness;
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   for (const auto& cdm : cdms_) {
@@ -417,7 +330,7 @@
 
   key_system_capabilities_update_callbacks_.AddUnsafe(cb);
 
-  if (!pending_lazy_initialize_key_systems_.empty()) {
+  if (!pending_lazy_initializations_.empty()) {
     // Lazy initializing some key systems. All callbacks will be notified when
     // that's finished.
     return;
@@ -431,6 +344,84 @@
   FinalizeKeySystemCapabilities();
 }
 
+std::pair<absl::optional<media::CdmCapability>, CdmInfo::Status>
+CdmRegistryImpl::GetCapability(const std::string& key_system,
+                               CdmInfo::Robustness robustness) {
+  DVLOG(2) << __func__ << ": key_system=" << key_system
+           << ", robustness=" << robustness;
+  using Status = CdmInfo::Status;
+
+  if (robustness == CdmInfo::Robustness::kHardwareSecure) {
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+    if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
+            switches::kLacrosUseChromeosProtectedMedia)) {
+      return {absl::nullopt, Status::kHardwareSecureDecryptionDisabled};
+    }
+#elif !BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA)
+    if (!media::IsHardwareSecureDecryptionEnabled()) {
+      DVLOG(1) << "Hardware secure decryption disabled";
+      return {absl::nullopt, Status::kHardwareSecureDecryptionDisabled};
+    }
+#endif  // !BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA)
+
+    // Secure codecs override takes precedence over other checks.
+    auto overridden_capability =
+        GetHardwareSecureCapabilityOverriddenFromCommandLine();
+    if (overridden_capability) {
+      DVLOG(1) << "Hardware secure codecs overridden from command line";
+      return {overridden_capability, Status::kCommandLineOverridden};
+    }
+
+    // Hardware secure video codecs need hardware video decoder support.
+    // TODO(xhwang): Make sure this check is as close as possible to the check
+    // in the render process. For example, also check check GPU features like
+    // GPU_FEATURE_TYPE_ACCELERATED_VIDEO_DECODE.
+    auto* command_line = base::CommandLine::ForCurrentProcess();
+    if (command_line &&
+        command_line->HasSwitch(switches::kDisableAcceleratedVideoDecode)) {
+      DVLOG(1) << "Hardware security not supported because accelerated video "
+                  "decode disabled";
+      return {absl::nullopt, Status::kAcceleratedVideoDecodeDisabled};
+    }
+
+#if BUILDFLAG(IS_WIN)
+    if (IsMediaFoundationHardwareSecurityDisabledByGpuFeature()) {
+      DVLOG(1) << "Hardware security not supported: GPU workarounds";
+      return {absl::nullopt, Status::kGpuFeatureDisabled};
+    }
+
+    if (IsGpuHardwareCompositionDisabled()) {
+      DVLOG(1) << "Hardware security not supported: GPU composition disabled";
+      return {absl::nullopt, Status::kGpuCompositionDisabled};
+    }
+#endif  // BUILDFLAG(IS_WIN)
+  }
+
+  auto cdm_info = GetCdmInfo(key_system, robustness);
+  if (!cdm_info) {
+    DVLOG(1) << "No " << robustness << " decryption CDM registered for "
+             << key_system;
+    return {absl::nullopt, Status::kEnabled};
+  }
+
+  DCHECK(!(cdm_info->status == Status::kUninitialized && cdm_info->capability))
+      << "Capability for " << robustness << " " << key_system
+      << " should not have value if uninitialized.";
+
+  return {cdm_info->capability, cdm_info->status};
+}
+
+std::pair<absl::optional<media::CdmCapability>, CdmInfo::Status>
+CdmRegistryImpl::GetFinalCapability(const std::string& key_system,
+                                    CdmInfo::Robustness robustness) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  const auto [capability, status] = GetCapability(key_system, robustness);
+  DCHECK(status != CdmInfo::Status::kUninitialized);
+
+  return {IsEnabled(status) ? capability : absl::nullopt, status};
+}
+
 void CdmRegistryImpl::FinalizeKeySystemCapabilities() {
   DVLOG(2) << __func__;
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -438,7 +429,7 @@
 
   // Abort existing pending LazyInitializeHardwareSecureCapability() operations
   // to avoid updating the observer twice.
-  pending_lazy_initialize_key_systems_.clear();
+  pending_lazy_initializations_.clear();
   weak_ptr_factory_.InvalidateWeakPtrs();
 
   // Get the set of supported key systems in case two CDMs are registered with
@@ -446,87 +437,100 @@
   // while iterating through it.
   std::set<std::string> supported_key_systems = GetSupportedKeySystems();
 
-  // Finalize hardware secure capabilities for all key systems. (Assumes
-  // software secure capabilities are always already finalized.)
+  // Finalize software secure capabilities for all key systems.
   for (const auto& key_system : supported_key_systems) {
-    auto cdm_info =
-        GetCdmInfo(key_system, CdmInfo::Robustness::kHardwareSecure);
-    if (!cdm_info) {
-      DVLOG(1) << "No Hardware secure CDM registered";
-      continue;
+    for (const auto robustness : {CdmInfo::Robustness::kSoftwareSecure,
+                                  CdmInfo::Robustness::kHardwareSecure}) {
+      AttemptToFinalizeKeySystemCapability(key_system, robustness);
     }
-
-    if (cdm_info->status != CdmInfo::Status::kUninitialized) {
-      DVLOG(1) << "Hardware secure capability already finalized";
-      continue;
-    }
-
-    absl::optional<media::CdmCapability> hw_secure_capability;
-    CdmInfo::Status status;
-    std::tie(hw_secure_capability, status) =
-        GetHardwareSecureCapability(*this, key_system);
-    if (status != CdmInfo::Status::kUninitialized) {
-      FinalizeHardwareSecureCapability(key_system, hw_secure_capability,
-                                       status);
-      continue;
-    }
-
-    // Needs lazy initialize. Use BindToCurrentLoop() to force a post.
-    pending_lazy_initialize_key_systems_.insert(key_system);
-    LazyInitializeHardwareSecureCapability(
-        key_system, media::BindToCurrentLoop(base::BindOnce(
-                        &CdmRegistryImpl::OnHardwareSecureCapabilityInitialized,
-                        weak_ptr_factory_.GetWeakPtr(), key_system)));
   }
 
-  // If not empty, we'll handle it in OnHardwareSecureCapabilityInitialized().
-  if (pending_lazy_initialize_key_systems_.empty())
+  // If not empty, we'll handle it in OnCapabilityInitialized().
+  if (pending_lazy_initializations_.empty())
     UpdateAndNotifyKeySystemCapabilities();
 }
 
+void CdmRegistryImpl::AttemptToFinalizeKeySystemCapability(
+    const std::string& key_system,
+    CdmInfo::Robustness robustness) {
+  auto cdm_info = GetCdmInfo(key_system, robustness);
+  if (!cdm_info) {
+    DVLOG(1) << "No " << robustness << " CDM registered for " << key_system;
+    return;
+  }
+
+  if (cdm_info->status != CdmInfo::Status::kUninitialized) {
+    DVLOG(1) << robustness << " capability already finalized for "
+             << key_system;
+    return;
+  }
+
+  const auto [capability, status] = GetCapability(key_system, robustness);
+  if (status != CdmInfo::Status::kUninitialized) {
+    FinalizeCapability(key_system, robustness, capability, status);
+    return;
+  }
+
+  // Needs lazy initialize. Use BindToCurrentLoop() to force a post.
+  pending_lazy_initializations_.insert({key_system, robustness});
+  LazyInitializeCapability(
+      key_system, robustness,
+      media::BindToCurrentLoop(base::BindOnce(
+          &CdmRegistryImpl::OnCapabilityInitialized,
+          weak_ptr_factory_.GetWeakPtr(), key_system, robustness)));
+}
+
 // TODO(xhwang): Find a way to register this as callbacks so we don't have to
 // hardcode platform-specific logic here.
-// TODO(jrummell): Support Android query.
-void CdmRegistryImpl::LazyInitializeHardwareSecureCapability(
+// TODO(crbug.com/853336): Support Android query.
+void CdmRegistryImpl::LazyInitializeCapability(
     const std::string& key_system,
+    CdmInfo::Robustness robustness,
     CdmCapabilityCB cdm_capability_cb) {
   DVLOG(2) << __func__;
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  if (hw_secure_capability_cb_for_testing_) {
-    hw_secure_capability_cb_for_testing_.Run(key_system,
-                                             std::move(cdm_capability_cb));
+  if (capability_cb_for_testing_) {
+    capability_cb_for_testing_.Run(key_system, robustness,
+                                   std::move(cdm_capability_cb));
     return;
   }
 
 #if BUILDFLAG(IS_WIN)
-  auto cdm_info = GetCdmInfo(key_system, CdmInfo::Robustness::kHardwareSecure);
-  DCHECK(cdm_info && !cdm_info->capability);
-  GetMediaFoundationServiceHardwareSecureCdmCapability(
-      key_system, cdm_info->path, std::move(cdm_capability_cb));
-#else
-  std::move(cdm_capability_cb).Run(absl::nullopt);
+  if (robustness == CdmInfo::Robustness::kHardwareSecure) {
+    auto cdm_info =
+        GetCdmInfo(key_system, CdmInfo::Robustness::kHardwareSecure);
+    DCHECK(cdm_info && !cdm_info->capability);
+    GetMediaFoundationServiceHardwareSecureCdmCapability(
+        key_system, cdm_info->path, std::move(cdm_capability_cb));
+    return;
+  }
 #endif  // BUILDFLAG(IS_WIN)
+
+  std::move(cdm_capability_cb).Run(absl::nullopt);
 }
 
-void CdmRegistryImpl::OnHardwareSecureCapabilityInitialized(
+void CdmRegistryImpl::OnCapabilityInitialized(
     const std::string& key_system,
+    const CdmInfo::Robustness robustness,
     absl::optional<media::CdmCapability> cdm_capability) {
   DVLOG(1) << __func__ << ": key_system=" << key_system
+           << ", robustness=" << robustness
            << ", cdm_capability=" << (cdm_capability ? "yes" : "no");
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(pending_lazy_initialize_key_systems_.count(key_system));
+  DCHECK(pending_lazy_initializations_.count({key_system, robustness}));
 
-  FinalizeHardwareSecureCapability(key_system, std::move(cdm_capability),
-                                   CdmInfo::Status::kEnabled);
+  FinalizeCapability(key_system, robustness, std::move(cdm_capability),
+                     CdmInfo::Status::kEnabled);
 
-  pending_lazy_initialize_key_systems_.erase(key_system);
-  if (pending_lazy_initialize_key_systems_.empty())
+  pending_lazy_initializations_.erase({key_system, robustness});
+  if (pending_lazy_initializations_.empty())
     UpdateAndNotifyKeySystemCapabilities();
 }
 
-void CdmRegistryImpl::FinalizeHardwareSecureCapability(
+void CdmRegistryImpl::FinalizeCapability(
     const std::string& key_system,
+    const CdmInfo::Robustness robustness,
     absl::optional<media::CdmCapability> cdm_capability,
     CdmInfo::Status status) {
   DVLOG(2) << __func__;
@@ -535,19 +539,20 @@
 
   auto itr = cdms_.begin();
   for (; itr != cdms_.end(); itr++) {
-    if (itr->robustness == CdmInfo::Robustness::kHardwareSecure &&
-        MatchKeySystem(*itr, key_system)) {
+    if (itr->robustness == robustness && MatchKeySystem(*itr, key_system)) {
       break;
     }
   }
 
   if (itr == cdms_.end()) {
-    DLOG(ERROR) << __func__ << ": Cannot find CdmInfo to finalize";
+    DLOG(ERROR) << __func__ << ": Cannot find CdmInfo to finalize for "
+                << key_system << " with robustness " << robustness;
     return;
   }
 
   if (itr->status != CdmInfo::Status::kUninitialized) {
-    DLOG(ERROR) << __func__ << ": CdmCapability already finalized";
+    DLOG(ERROR) << __func__ << ": CdmCapability already finalized for "
+                << key_system << " with robustness " << robustness;
     return;
   }
 
@@ -588,22 +593,21 @@
 
   std::set<std::string> supported_key_systems = GetSupportedKeySystems();
   for (const auto& key_system : supported_key_systems) {
+    CdmInfo::Status status;
     media::mojom::KeySystemCapability capability;
 
-    // Software secure capability
-    capability.sw_secure_capability =
-        GetSoftwareSecureCapability(*this, key_system);
+    // Software secure capability.
+    std::tie(capability.sw_secure_capability, status) =
+        GetFinalCapability(key_system, CdmInfo::Robustness::kSoftwareSecure);
+    ReportSoftwareSecureCdmAvailableUMA(
+        key_system, capability.sw_secure_capability != absl::nullopt);
 
-    // Hardware secure capability
-    absl::optional<media::CdmCapability> hw_secure_capability;
-    CdmInfo::Status status;
-    std::tie(hw_secure_capability, status) =
-        GetHardwareSecureCapability(*this, key_system);
-    DCHECK(status != CdmInfo::Status::kUninitialized);
+    // Hardware secure capability.
+    std::tie(capability.hw_secure_capability, status) =
+        GetFinalCapability(key_system, CdmInfo::Robustness::kHardwareSecure);
     ReportHardwareSecureCapabilityStatusUMA(
-        key_system, status, base::OptionalToPtr(hw_secure_capability));
-    capability.hw_secure_capability =
-        IsEnabled(status) ? hw_secure_capability : absl::nullopt;
+        key_system, status,
+        base::OptionalToPtr(capability.hw_secure_capability));
 
     if (capability.sw_secure_capability || capability.hw_secure_capability)
       key_system_capabilities[key_system] = std::move(capability);
@@ -612,9 +616,8 @@
   return key_system_capabilities;
 }
 
-void CdmRegistryImpl::SetHardwareSecureCapabilityCBForTesting(
-    HardwareSecureCapabilityCB cb) {
-  hw_secure_capability_cb_for_testing_ = std::move(cb);
+void CdmRegistryImpl::SetCapabilityCBForTesting(CapabilityCB cb) {
+  capability_cb_for_testing_ = std::move(cb);
 }
 
 }  // namespace content
diff --git a/content/browser/media/cdm_registry_impl.h b/content/browser/media/cdm_registry_impl.h
index e4d6d4c..6616e7d9 100644
--- a/content/browser/media/cdm_registry_impl.h
+++ b/content/browser/media/cdm_registry_impl.h
@@ -72,18 +72,45 @@
   CdmRegistryImpl();
   ~CdmRegistryImpl() override;
 
+  // Get the capability for `key_system` with robustness `robustness`
+  // synchronously. If lazy initialization is needed, return
+  // Status::kUninitialized.
+  std::pair<absl::optional<media::CdmCapability>, CdmInfo::Status>
+  GetCapability(const std::string& key_system, CdmInfo::Robustness robustness);
+
+  // Get the capability for `key_system` with robustness `robustness`
+  // synchronously. All initialization should have been completed.
+  std::pair<absl::optional<media::CdmCapability>, CdmInfo::Status>
+  GetFinalCapability(const std::string& key_system,
+                     CdmInfo::Robustness robustness);
+
   // Finalizes KeySystemCapabilities. May lazy initialize CDM capabilities
   // asynchronously if needed.
   void FinalizeKeySystemCapabilities();
 
+  // Attempt to finalize KeySystemCapability for `key_system` with robustness
+  // `robustness`. May lazy initialize it asynchronously if needed.
+  void AttemptToFinalizeKeySystemCapability(const std::string& key_system,
+                                            CdmInfo::Robustness robustness);
+
+  // Callback for when a key system is initialized if lazy initialization
+  // required.
   using CdmCapabilityCB =
       base::OnceCallback<void(absl::optional<media::CdmCapability>)>;
-  void LazyInitializeHardwareSecureCapability(
-      const std::string& key_system,
-      CdmCapabilityCB cdm_capability_cb);
 
-  void OnHardwareSecureCapabilityInitialized(
+  // Lazily initialize `key_system` with robustness `robustness`, calling
+  // `cdm_capability_cb`. Callback may be called synchronously
+  // or asynchronously.
+  void LazyInitializeCapability(const std::string& key_system,
+                                CdmInfo::Robustness robustness,
+                                CdmCapabilityCB cdm_capability_cb);
+
+  // Called when initialization of `key_system` with robustness `robustness`
+  // is complete. `cdm_capability` will be absl::nullopt if the key system
+  // with specified robustness isn't supported.
+  void OnCapabilityInitialized(
       const std::string& key_system,
+      const CdmInfo::Robustness robustness,
       absl::optional<media::CdmCapability> cdm_capability);
 
   // Finalizes the CdmInfo corresponding to `key_system` and `robustness` if its
@@ -91,21 +118,28 @@
   // exist, or if the CdmInfo's CdmCapability is not null. The CdmInfo will be
   // removed if `cdm_capability` is null, since the CDM does not support any
   // capability.
-  void FinalizeHardwareSecureCapability(
-      const std::string& key_system,
-      absl::optional<media::CdmCapability> cdm_capability,
-      CdmInfo::Status status);
+  void FinalizeCapability(const std::string& key_system,
+                          const CdmInfo::Robustness robustness,
+                          absl::optional<media::CdmCapability> cdm_capability,
+                          CdmInfo::Status status);
 
+  // When capabilities for all registered key systems have been determined,
+  // notify all observers with the updated values. No notification is done
+  // if the capabilities have not changed.
   void UpdateAndNotifyKeySystemCapabilities();
 
+  // Returns the set of all registered key systems.
   std::set<std::string> GetSupportedKeySystems() const;
 
+  // Returns the capabailities for all registered key systems.
   KeySystemCapabilities GetKeySystemCapabilities();
 
-  // Sets a callback to query for hardware secure capability for testing.
-  using HardwareSecureCapabilityCB =
-      base::RepeatingCallback<void(const std::string&, CdmCapabilityCB)>;
-  void SetHardwareSecureCapabilityCBForTesting(HardwareSecureCapabilityCB cb);
+  // Sets callbacks to query for secure capability for testing.
+  using CapabilityCB =
+      base::RepeatingCallback<void(const std::string&,
+                                   const CdmInfo::Robustness robustness,
+                                   CdmCapabilityCB)>;
+  void SetCapabilityCBForTesting(CapabilityCB cb);
 
   std::vector<CdmInfo> cdms_ GUARDED_BY_CONTEXT(sequence_checker_);
 
@@ -116,11 +150,12 @@
   // Cached current KeySystemCapabilities value.
   absl::optional<KeySystemCapabilities> key_system_capabilities_;
 
-  // Key systems pending CdmCapability lazy initialization.
-  std::set<std::string> pending_lazy_initialize_key_systems_;
+  // Key system and robustness pairs pending CdmCapability lazy initialization.
+  std::set<std::pair<std::string, CdmInfo::Robustness>>
+      pending_lazy_initializations_;
 
-  // A callback for testing to avoid hardware dependency.
-  HardwareSecureCapabilityCB hw_secure_capability_cb_for_testing_;
+  // Callback for testing to avoid device dependency.
+  CapabilityCB capability_cb_for_testing_;
 
   SEQUENCE_CHECKER(sequence_checker_);
 
diff --git a/content/browser/media/cdm_registry_impl_unittest.cc b/content/browser/media/cdm_registry_impl_unittest.cc
index 59a4ad2..b375511b 100644
--- a/content/browser/media/cdm_registry_impl_unittest.cc
+++ b/content/browser/media/cdm_registry_impl_unittest.cc
@@ -111,14 +111,14 @@
     gpu_data_manager->UpdateGpuInfo(gpu_info, absl::nullopt);
 #endif  // BUILDFLAG(IS_WIN)
 
-    cdm_registry_.SetHardwareSecureCapabilityCBForTesting(
-        hw_secure_capability_cb_.Get());
+    cdm_registry_.SetCapabilityCBForTesting(capability_cb_.Get());
   }
 
   void OnKeySystemCapabilitiesUpdated(
       int observer_id,
       base::OnceClosure done_cb,
       KeySystemCapabilities key_system_capabilities) {
+    DVLOG(1) << __func__;
     results_[observer_id].push_back(std::move(key_system_capabilities));
     std::move(done_cb).Run();
   }
@@ -159,7 +159,17 @@
                      base::FilePath::FromUTF8Unsafe(kTestPath)));
   }
 
-  void RegisterForLazyInitialization() {
+  void RegisterForLazySoftwareSecureInitialization() {
+    // Register a CdmInfo without CdmCapability to allow lazy initialization.
+    Register(CdmInfo(kTestKeySystem, CdmInfo::Robustness::kSoftwareSecure,
+                     absl::nullopt, kTestCdmType));
+    auto cdm_info = cdm_registry_.GetCdmInfo(
+        kTestKeySystem, CdmInfo::Robustness::kSoftwareSecure);
+    ASSERT_TRUE(cdm_info);
+    ASSERT_FALSE(cdm_info->capability);
+  }
+
+  void RegisterForLazyHardwareSecureInitialization() {
     // Register a CdmInfo without CdmCapability to allow lazy initialization.
     Register(CdmInfo(kTestKeySystem, CdmInfo::Robustness::kHardwareSecure,
                      absl::nullopt, kTestCdmType));
@@ -187,6 +197,7 @@
   }
 
   void GetKeySystemCapabilities() {
+    DVLOG(1) << __func__;
     base::RunLoop run_loop;
     cdm_registry_.ObserveKeySystemCapabilities(base::BindRepeating(
         &CdmRegistryImplTest::OnKeySystemCapabilitiesUpdated,
@@ -229,10 +240,9 @@
   BrowserTaskEnvironment task_environment_;
 
   CdmRegistryImpl cdm_registry_;
-  base::MockCallback<CdmRegistryImpl::HardwareSecureCapabilityCB>
-      hw_secure_capability_cb_;
+  base::MockCallback<CdmRegistryImpl::CapabilityCB> capability_cb_;
 
-  // Map of "obserber ID" to the list of updated KeySystemCapabilities.
+  // Map of "observer ID" to the list of updated KeySystemCapabilities.
   std::map<int, std::vector<KeySystemCapabilities>> results_;
 };
 
@@ -397,12 +407,34 @@
   ASSERT_EQ(support.hw_secure_capability.value(), GetTestCdmCapability());
 }
 
-TEST_F(CdmRegistryImplTest, KeySystemCapabilities_LazyInitialize_Supported) {
-  RegisterForLazyInitialization();
+TEST_F(CdmRegistryImplTest,
+       KeySystemCapabilities_LazySoftwareSecureInitialize_Supported) {
+  RegisterForLazySoftwareSecureInitialization();
   SelectHardwareSecureDecryption(true);
 
-  EXPECT_CALL(hw_secure_capability_cb_, Run(kTestKeySystem, _))
-      .WillOnce(RunOnceCallback<1>(GetTestCdmCapability()));
+  EXPECT_CALL(capability_cb_,
+              Run(kTestKeySystem, Robustness::kSoftwareSecure, _))
+      .WillOnce(RunOnceCallback<2>(GetTestCdmCapability()));
+  GetKeySystemCapabilities();
+
+  ASSERT_TRUE(results_.count(kObserver1));
+  ASSERT_EQ(results_[kObserver1].size(), 1u);
+  auto& key_system_capabilities = results_[kObserver1][0];
+  ASSERT_EQ(key_system_capabilities.size(), 1u);
+  ASSERT_TRUE(key_system_capabilities.count(kTestKeySystem));
+  const auto& support = key_system_capabilities[kTestKeySystem];
+  ASSERT_EQ(support.sw_secure_capability.value(), GetTestCdmCapability());
+  ASSERT_FALSE(support.hw_secure_capability);
+}
+
+TEST_F(CdmRegistryImplTest,
+       KeySystemCapabilities_LazyHardwareSecureInitialize_Supported) {
+  RegisterForLazyHardwareSecureInitialization();
+  SelectHardwareSecureDecryption(true);
+
+  EXPECT_CALL(capability_cb_,
+              Run(kTestKeySystem, Robustness::kHardwareSecure, _))
+      .WillOnce(RunOnceCallback<2>(GetTestCdmCapability()));
   GetKeySystemCapabilities();
 
   ASSERT_TRUE(results_.count(kObserver1));
@@ -415,12 +447,35 @@
   ASSERT_EQ(support.hw_secure_capability.value(), GetTestCdmCapability());
 }
 
-TEST_F(CdmRegistryImplTest, KeySystemCapabilities_LazyInitialize_NotSupported) {
-  RegisterForLazyInitialization();
+TEST_F(CdmRegistryImplTest,
+       KeySystemCapabilities_LazySoftwareSecureInitialize_NotSupported) {
+  RegisterForLazySoftwareSecureInitialization();
   SelectHardwareSecureDecryption(true);
 
-  EXPECT_CALL(hw_secure_capability_cb_, Run(kTestKeySystem, _))
-      .WillOnce(RunOnceCallback<1>(absl::nullopt));
+  EXPECT_CALL(capability_cb_,
+              Run(kTestKeySystem, Robustness::kSoftwareSecure, _))
+      .WillOnce(RunOnceCallback<2>(absl::nullopt));
+  GetKeySystemCapabilities();
+
+  ASSERT_TRUE(results_.count(kObserver1));
+  ASSERT_EQ(results_[kObserver1].size(), 1u);
+  auto& key_system_capabilities = results_[kObserver1][0];
+  ASSERT_TRUE(key_system_capabilities.empty());
+
+  auto cdm_info = cdm_registry_.GetCdmInfo(
+      kTestKeySystem, CdmInfo::Robustness::kSoftwareSecure);
+  ASSERT_EQ(cdm_info->status, CdmInfo::Status::kEnabled);
+  ASSERT_FALSE(cdm_info->capability);
+}
+
+TEST_F(CdmRegistryImplTest,
+       KeySystemCapabilities_LazyHardwareSecureInitialize_NotSupported) {
+  RegisterForLazyHardwareSecureInitialization();
+  SelectHardwareSecureDecryption(true);
+
+  EXPECT_CALL(capability_cb_,
+              Run(kTestKeySystem, Robustness::kHardwareSecure, _))
+      .WillOnce(RunOnceCallback<2>(absl::nullopt));
   GetKeySystemCapabilities();
 
   ASSERT_TRUE(results_.count(kObserver1));
@@ -435,7 +490,7 @@
 }
 
 TEST_F(CdmRegistryImplTest, KeySystemCapabilities_HardwareSecureDisabled) {
-  RegisterForLazyInitialization();
+  RegisterForLazyHardwareSecureInitialization();
   SelectHardwareSecureDecryption(false);
   GetKeySystemCapabilities();
 
@@ -452,12 +507,16 @@
 }
 
 TEST_F(CdmRegistryImplTest, KeySystemCapabilities_SoftwareAndHardwareSecure) {
-  Register(GetTestCdmInfo());
-  RegisterForLazyInitialization();
+  RegisterForLazySoftwareSecureInitialization();
+  RegisterForLazyHardwareSecureInitialization();
   SelectHardwareSecureDecryption(true);
 
-  EXPECT_CALL(hw_secure_capability_cb_, Run(kTestKeySystem, _))
-      .WillOnce(RunOnceCallback<1>(GetTestCdmCapability()));
+  EXPECT_CALL(capability_cb_,
+              Run(kTestKeySystem, Robustness::kSoftwareSecure, _))
+      .WillOnce(RunOnceCallback<2>(GetTestCdmCapability()));
+  EXPECT_CALL(capability_cb_,
+              Run(kTestKeySystem, Robustness::kHardwareSecure, _))
+      .WillOnce(RunOnceCallback<2>(GetOtherCdmCapability()));
   GetKeySystemCapabilities();
 
   ASSERT_TRUE(results_.count(kObserver1));
@@ -467,7 +526,7 @@
   ASSERT_TRUE(key_system_capabilities.count(kTestKeySystem));
   const auto& support = key_system_capabilities[kTestKeySystem];
   ASSERT_EQ(support.sw_secure_capability.value(), GetTestCdmCapability());
-  ASSERT_EQ(support.hw_secure_capability.value(), GetTestCdmCapability());
+  ASSERT_EQ(support.hw_secure_capability.value(), GetOtherCdmCapability());
 }
 
 TEST_F(CdmRegistryImplTest, KeySystemCapabilities_MultipleObservers) {
@@ -495,14 +554,49 @@
   ASSERT_EQ(key_system_capabilities, results_[kObserver2][0]);
 }
 
-TEST_F(CdmRegistryImplTest,
-       KeySystemCapabilities_MultipleObservers_PendingLazyInitialize) {
+TEST_F(
+    CdmRegistryImplTest,
+    KeySystemCapabilities_MultipleObservers_PendingLazySoftwareSecureInitialize) {
+  RegisterForLazySoftwareSecureInitialization();
+  SelectHardwareSecureDecryption(false);
+
+  EXPECT_CALL(capability_cb_,
+              Run(kTestKeySystem, Robustness::kSoftwareSecure, _))
+      .WillOnce(RunOnceCallback<2>(GetTestCdmCapability()));
+
+  base::RunLoop run_loop;
+  cdm_registry_.ObserveKeySystemCapabilities(base::BindRepeating(
+      &CdmRegistryImplTest::OnKeySystemCapabilitiesUpdated,
+      base::Unretained(this), kObserver1, base::DoNothing()));
+  cdm_registry_.ObserveKeySystemCapabilities(base::BindRepeating(
+      &CdmRegistryImplTest::OnKeySystemCapabilitiesUpdated,
+      base::Unretained(this), kObserver2, run_loop.QuitClosure()));
+  run_loop.Run();
+
+  ASSERT_TRUE(results_.count(kObserver1));
+  ASSERT_EQ(results_[kObserver1].size(), 1u);
+  auto& key_system_capabilities = results_[kObserver1][0];
+  ASSERT_EQ(key_system_capabilities.size(), 1u);
+  ASSERT_TRUE(key_system_capabilities.count(kTestKeySystem));
+  const auto& support = key_system_capabilities[kTestKeySystem];
+  ASSERT_EQ(support.sw_secure_capability.value(), GetTestCdmCapability());
+  ASSERT_FALSE(support.hw_secure_capability);
+
+  ASSERT_TRUE(results_.count(kObserver2));
+  ASSERT_EQ(results_[kObserver2].size(), 1u);
+  ASSERT_EQ(key_system_capabilities, results_[kObserver2][0]);
+}
+
+TEST_F(
+    CdmRegistryImplTest,
+    KeySystemCapabilities_MultipleObservers_PendingLazyHardwareSecureInitialize) {
   Register(GetTestCdmInfo());
-  RegisterForLazyInitialization();
+  RegisterForLazyHardwareSecureInitialization();
   SelectHardwareSecureDecryption(true);
 
-  EXPECT_CALL(hw_secure_capability_cb_, Run(kTestKeySystem, _))
-      .WillOnce(RunOnceCallback<1>(GetTestCdmCapability()));
+  EXPECT_CALL(capability_cb_,
+              Run(kTestKeySystem, Robustness::kHardwareSecure, _))
+      .WillOnce(RunOnceCallback<2>(GetTestCdmCapability()));
 
   base::RunLoop run_loop;
   cdm_registry_.ObserveKeySystemCapabilities(base::BindRepeating(
@@ -527,14 +621,16 @@
   ASSERT_EQ(key_system_capabilities, results_[kObserver2][0]);
 }
 
-TEST_F(CdmRegistryImplTest,
-       KeySystemCapabilities_MultipleObservers_AfterLazyInitialize) {
+TEST_F(
+    CdmRegistryImplTest,
+    KeySystemCapabilities_MultipleObservers_AfterLazyHardwareSecureInitialize) {
   Register(GetTestCdmInfo());
-  RegisterForLazyInitialization();
+  RegisterForLazyHardwareSecureInitialization();
   SelectHardwareSecureDecryption(true);
 
-  EXPECT_CALL(hw_secure_capability_cb_, Run(kTestKeySystem, _))
-      .WillOnce(RunOnceCallback<1>(GetTestCdmCapability()));
+  EXPECT_CALL(capability_cb_,
+              Run(kTestKeySystem, Robustness::kHardwareSecure, _))
+      .WillOnce(RunOnceCallback<2>(GetTestCdmCapability()));
 
   {
     base::RunLoop run_loop;
@@ -589,9 +685,10 @@
 
   {
     base::RunLoop run_loop;
-    EXPECT_CALL(hw_secure_capability_cb_, Run(kTestKeySystem, _))
-        .WillOnce(RunOnceCallback<1>(GetOtherCdmCapability()));
-    RegisterForLazyInitialization();
+    EXPECT_CALL(capability_cb_,
+                Run(kTestKeySystem, Robustness::kHardwareSecure, _))
+        .WillOnce(RunOnceCallback<2>(GetOtherCdmCapability()));
+    RegisterForLazyHardwareSecureInitialization();
     run_loop.RunUntilIdle();
   }
 
@@ -613,11 +710,13 @@
   // Save the callbacks so we can control when and how they are fired.
   base::OnceCallback<void(absl::optional<media::CdmCapability>)> callback_1,
       callback_2, callback_3;
-  EXPECT_CALL(hw_secure_capability_cb_, Run(kTestKeySystem, _))
-      .WillOnce(MoveArg<1>(&callback_1))
-      .WillOnce(MoveArg<1>(&callback_2));
-  EXPECT_CALL(hw_secure_capability_cb_, Run(kOtherKeySystem, _))
-      .WillOnce(MoveArg<1>(&callback_3));
+  EXPECT_CALL(capability_cb_,
+              Run(kTestKeySystem, Robustness::kHardwareSecure, _))
+      .WillOnce(MoveArg<2>(&callback_1))
+      .WillOnce(MoveArg<2>(&callback_2));
+  EXPECT_CALL(capability_cb_,
+              Run(kOtherKeySystem, Robustness::kHardwareSecure, _))
+      .WillOnce(MoveArg<2>(&callback_3));
 
   // Register CdmInfo for lazy initialization.
   {
@@ -705,7 +804,7 @@
   gpu_info.overlay_info.direct_composition = false;
   GpuDataManagerImpl::GetInstance()->UpdateGpuInfo(gpu_info, absl::nullopt);
 
-  RegisterForLazyInitialization();
+  RegisterForLazyHardwareSecureInitialization();
   SelectHardwareSecureDecryption(true);
   GetKeySystemCapabilities();
 
diff --git a/content/browser/media/media_internals_cdm_helper.cc b/content/browser/media/media_internals_cdm_helper.cc
index e1c6dd4..9b7e9b2 100644
--- a/content/browser/media/media_internals_cdm_helper.cc
+++ b/content/browser/media/media_internals_cdm_helper.cc
@@ -19,15 +19,6 @@
 
 namespace {
 
-std::string GetCdmInfoRobustnessName(CdmInfo::Robustness robustness) {
-  switch (robustness) {
-    case CdmInfo::Robustness::kHardwareSecure:
-      return "Hardware Secure";
-    case CdmInfo::Robustness::kSoftwareSecure:
-      return "Software Secure";
-  }
-}
-
 std::string GetCdmInfoCapabilityStatusName(CdmInfo::Status status) {
   switch (status) {
     case CdmInfo::Status::kUninitialized:
diff --git a/content/browser/preloading/prefetch/prefetch_document_manager.cc b/content/browser/preloading/prefetch/prefetch_document_manager.cc
index 15e248e..5dbbafd9 100644
--- a/content/browser/preloading/prefetch/prefetch_document_manager.cc
+++ b/content/browser/preloading/prefetch/prefetch_document_manager.cc
@@ -81,7 +81,8 @@
   // No-Vary-Search equivalence. If there is not then stop.
   auto prefetch_iter = all_prefetches_.find(navigation_handle->GetURL());
   if (prefetch_iter == all_prefetches_.end() || !prefetch_iter->second) {
-    if (!base::FeatureList::IsEnabled(
+    if (!no_vary_search_support_enabled_ ||
+        !base::FeatureList::IsEnabled(
             network::features::kPrefetchNoVarySearch)) {
       return;
     }
@@ -299,7 +300,8 @@
 }
 
 void PrefetchDocumentManager::OnPrefetchedHeadReceived(const GURL& url) {
-  if (!base::FeatureList::IsEnabled(network::features::kPrefetchNoVarySearch)) {
+  if (!no_vary_search_support_enabled_ ||
+      !base::FeatureList::IsEnabled(network::features::kPrefetchNoVarySearch)) {
     return;
   }
   // Find the PrefetchContainer associated with |url|.
@@ -317,6 +319,10 @@
   referring_page_metrics_.prefetch_successful_count++;
 }
 
+void PrefetchDocumentManager::EnableNoVarySearchSupport() {
+  no_vary_search_support_enabled_ = true;
+}
+
 DOCUMENT_USER_DATA_KEY_IMPL(PrefetchDocumentManager);
 
 }  // namespace content
diff --git a/content/browser/preloading/prefetch/prefetch_document_manager.h b/content/browser/preloading/prefetch/prefetch_document_manager.h
index aa0a13f..21a4ac8d 100644
--- a/content/browser/preloading/prefetch/prefetch_document_manager.h
+++ b/content/browser/preloading/prefetch/prefetch_document_manager.h
@@ -96,6 +96,8 @@
   // Helper function to get the |NoVarySearchHelper| associated with |this|.
   const NoVarySearchHelper& GetNoVarySearchHelper() const;
 
+  void EnableNoVarySearchSupport();
+
   static void SetPrefetchServiceForTesting(PrefetchService* prefetch_service);
 
  private:
@@ -129,6 +131,8 @@
   // Used through the getter GetNoVarySearchHelper
   NoVarySearchHelper no_vary_search_helper_;
 
+  bool no_vary_search_support_enabled_ = false;
+
   base::WeakPtrFactory<PrefetchDocumentManager> weak_method_factory_{this};
 
   DOCUMENT_USER_DATA_KEY_DECL();
diff --git a/content/browser/preloading/prefetch/prefetch_document_manager_unittest.cc b/content/browser/preloading/prefetch/prefetch_document_manager_unittest.cc
index 718b3fbd..0ed7ae5b 100644
--- a/content/browser/preloading/prefetch/prefetch_document_manager_unittest.cc
+++ b/content/browser/preloading/prefetch/prefetch_document_manager_unittest.cc
@@ -129,6 +129,7 @@
   auto* prefetch_document_manager =
       PrefetchDocumentManager::GetOrCreateForCurrentDocument(
           &GetPrimaryMainFrame());
+  prefetch_document_manager->EnableNoVarySearchSupport();
   {
     // Create list of SpeculationCandidatePtrs.
     std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
@@ -245,6 +246,9 @@
 }
 
 TEST_F(PrefetchDocumentManagerTest, ProcessSpeculationCandidates) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndDisableFeature(
+      network::features::kPrefetchNoVarySearch);
   // Create list of SpeculationCandidatePtrs.
   std::vector<blink::mojom::SpeculationCandidatePtr> candidates;
 
diff --git a/content/browser/preloading/prefetch/prefetch_service_unittest.cc b/content/browser/preloading/prefetch/prefetch_service_unittest.cc
index 6ae34239..11d5c942 100644
--- a/content/browser/preloading/prefetch/prefetch_service_unittest.cc
+++ b/content/browser/preloading/prefetch/prefetch_service_unittest.cc
@@ -261,10 +261,11 @@
   }
 
   virtual void InitScopedFeatureList() {
-    scoped_feature_list_.InitAndEnableFeatureWithParameters(
-        content::features::kPrefetchUseContentRefactor,
-        {{"ineligible_decoy_request_probability", "0"},
-         {"prefetch_container_lifetime_s", "-1"}});
+    scoped_feature_list_.InitWithFeaturesAndParameters(
+        {{content::features::kPrefetchUseContentRefactor,
+          {{"ineligible_decoy_request_probability", "0"},
+           {"prefetch_container_lifetime_s", "-1"}}}},
+        {network::features::kPrefetchNoVarySearch});
   }
 
   void MakePrefetchService(std::unique_ptr<MockPrefetchServiceDelegate>
@@ -280,9 +281,12 @@
 
   // Creates a prefetch request for |url| on the current main frame.
   void MakePrefetchOnMainFrame(const GURL& url,
-                               const PrefetchType& prefetch_type) {
+                               const PrefetchType& prefetch_type,
+                               bool enable_no_vary_search_header = false) {
     PrefetchDocumentManager* prefetch_document_manager =
         PrefetchDocumentManager::GetOrCreateForCurrentDocument(main_rfh());
+    if (enable_no_vary_search_header)
+      prefetch_document_manager->EnableNoVarySearchSupport();
     prefetch_document_manager->PrefetchUrl(url, prefetch_type,
                                            blink::mojom::Referrer(), nullptr);
   }
@@ -587,8 +591,9 @@
   // Enable feature, which means that we should be able to create a
   // PrefetchService instance.
   base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(
-      content::features::kPrefetchUseContentRefactor);
+  scoped_feature_list.InitWithFeatures(
+      {content::features::kPrefetchUseContentRefactor},
+      {network::features::kPrefetchNoVarySearch});
 
   EXPECT_TRUE(PrefetchService::CreateIfPossible(browser_context()));
 }
@@ -597,8 +602,9 @@
   // Disable feature, which means that we shouldn't be able to create a
   // PrefetchService instance.
   base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndDisableFeature(
-      content::features::kPrefetchUseContentRefactor);
+  scoped_feature_list.InitWithFeatures(
+      {}, {content::features::kPrefetchUseContentRefactor,
+           network::features::kPrefetchNoVarySearch});
 
   EXPECT_FALSE(PrefetchService::CreateIfPossible(browser_context()));
 }
@@ -791,11 +797,12 @@
 class PrefetchServiceAllowAllDomainsTest : public PrefetchServiceTest {
  public:
   void InitScopedFeatureList() override {
-    scoped_feature_list_.InitAndEnableFeatureWithParameters(
-        content::features::kPrefetchUseContentRefactor,
-        {{"ineligible_decoy_request_probability", "0"},
-         {"prefetch_container_lifetime_s", "-1"},
-         {"allow_all_domains", "true"}});
+    scoped_feature_list_.InitWithFeaturesAndParameters(
+        {{content::features::kPrefetchUseContentRefactor,
+          {{"ineligible_decoy_request_probability", "0"},
+           {"prefetch_container_lifetime_s", "-1"},
+           {"allow_all_domains", "true"}}}},
+        {network::features::kPrefetchNoVarySearch});
   }
 };
 
@@ -874,11 +881,12 @@
     : public PrefetchServiceTest {
  public:
   void InitScopedFeatureList() override {
-    scoped_feature_list_.InitAndEnableFeatureWithParameters(
-        content::features::kPrefetchUseContentRefactor,
-        {{"ineligible_decoy_request_probability", "0"},
-         {"prefetch_container_lifetime_s", "-1"},
-         {"allow_all_domains_for_extended_preloading", "true"}});
+    scoped_feature_list_.InitWithFeaturesAndParameters(
+        {{content::features::kPrefetchUseContentRefactor,
+          {{"ineligible_decoy_request_probability", "0"},
+           {"prefetch_container_lifetime_s", "-1"},
+           {"allow_all_domains_for_extended_preloading", "true"}}}},
+        {network::features::kPrefetchNoVarySearch});
   }
 };
 
@@ -2263,11 +2271,12 @@
 class PrefetchServiceLimitedPrefetchesTest : public PrefetchServiceTest {
  public:
   void InitScopedFeatureList() override {
-    scoped_feature_list_.InitAndEnableFeatureWithParameters(
-        content::features::kPrefetchUseContentRefactor,
-        {{"ineligible_decoy_request_probability", "0"},
-         {"prefetch_container_lifetime_s", "-1"},
-         {"max_srp_prefetches", "2"}});
+    scoped_feature_list_.InitWithFeaturesAndParameters(
+        {{content::features::kPrefetchUseContentRefactor,
+          {{"ineligible_decoy_request_probability", "0"},
+           {"prefetch_container_lifetime_s", "-1"},
+           {"max_srp_prefetches", "2"}}}},
+        {network::features::kPrefetchNoVarySearch});
   }
 };
 
@@ -2425,11 +2434,12 @@
 class PrefetchServiceWithHTMLOnlyTest : public PrefetchServiceTest {
  public:
   void InitScopedFeatureList() override {
-    scoped_feature_list_.InitAndEnableFeatureWithParameters(
-        content::features::kPrefetchUseContentRefactor,
-        {{"ineligible_decoy_request_probability", "0"},
-         {"prefetch_container_lifetime_s", "-1"},
-         {"html_only", "true"}});
+    scoped_feature_list_.InitWithFeaturesAndParameters(
+        {{content::features::kPrefetchUseContentRefactor,
+          {{"ineligible_decoy_request_probability", "0"},
+           {"prefetch_container_lifetime_s", "-1"},
+           {"html_only", "true"}}}},
+        {network::features::kPrefetchNoVarySearch});
   }
 };
 
@@ -2497,10 +2507,11 @@
 class PrefetchServiceAlwaysMakeDecoyRequestTest : public PrefetchServiceTest {
  public:
   void InitScopedFeatureList() override {
-    scoped_feature_list_.InitAndEnableFeatureWithParameters(
-        content::features::kPrefetchUseContentRefactor,
-        {{"ineligible_decoy_request_probability", "1"},
-         {"prefetch_container_lifetime_s", "-1"}});
+    scoped_feature_list_.InitWithFeaturesAndParameters(
+        {{content::features::kPrefetchUseContentRefactor,
+          {{"ineligible_decoy_request_probability", "1"},
+           {"prefetch_container_lifetime_s", "-1"}}}},
+        {network::features::kPrefetchNoVarySearch});
   }
 };
 
@@ -2971,7 +2982,8 @@
 
   MakePrefetchOnMainFrame(GURL("https://example.com/?a=1"),
                           PrefetchType(/*use_isolated_network_context=*/true,
-                                       /*use_prefetch_proxy=*/true));
+                                       /*use_prefetch_proxy=*/true),
+                          /*enable_no_vary_search_header*/ true);
   base::RunLoop().RunUntilIdle();
 
   VerifyCommonRequestState(GURL("https://example.com/?a=1"),
diff --git a/content/browser/preloading/speculation_rules/speculation_host_impl.cc b/content/browser/preloading/speculation_rules/speculation_host_impl.cc
index c42b2be..0bbc49d 100644
--- a/content/browser/preloading/speculation_rules/speculation_host_impl.cc
+++ b/content/browser/preloading/speculation_rules/speculation_host_impl.cc
@@ -6,6 +6,7 @@
 
 #include <functional>
 
+#include "content/browser/preloading/prefetch/prefetch_document_manager.h"
 #include "content/browser/preloading/preloading_decider.h"
 #include "third_party/blink/public/common/features.h"
 
@@ -72,4 +73,12 @@
   preloading_decider->UpdateSpeculationCandidates(candidates);
 }
 
+void SpeculationHostImpl::EnableNoVarySearchSupport() {
+  auto* prefetch_document_manager =
+      PrefetchDocumentManager::GetOrCreateForCurrentDocument(
+          &render_frame_host());
+  DCHECK(prefetch_document_manager);
+  prefetch_document_manager->EnableNoVarySearchSupport();
+}
+
 }  // namespace content
diff --git a/content/browser/preloading/speculation_rules/speculation_host_impl.h b/content/browser/preloading/speculation_rules/speculation_host_impl.h
index 96acce3..d95679c 100644
--- a/content/browser/preloading/speculation_rules/speculation_host_impl.h
+++ b/content/browser/preloading/speculation_rules/speculation_host_impl.h
@@ -37,6 +37,7 @@
 
   void UpdateSpeculationCandidates(
       std::vector<blink::mojom::SpeculationCandidatePtr> candidates) override;
+  void EnableNoVarySearchSupport() override;
 };
 
 }  // namespace content
diff --git a/content/browser/renderer_host/back_forward_cache_impl.cc b/content/browser/renderer_host/back_forward_cache_impl.cc
index b6d7893..1d0859f2 100644
--- a/content/browser/renderer_host/back_forward_cache_impl.cc
+++ b/content/browser/renderer_host/back_forward_cache_impl.cc
@@ -654,8 +654,8 @@
     const RenderFrameHostImpl& rfh,
     const BackForwardCacheCanStoreDocumentResult& flattened_result,
     const perfetto::StaticString& caller) {
-  LOG(ERROR) << caller.value << ": " << rfh.GetLastCommittedURL() << " : "
-             << flattened_result.ToString();
+  VLOG(1) << caller.value << ": " << rfh.GetLastCommittedURL() << " : "
+          << flattened_result.ToString();
   TRACE_EVENT("navigation", caller,
               ChromeTrackEvent::kBackForwardCacheCanStoreDocumentResult,
               flattened_result);
diff --git a/content/browser/renderer_host/compositor_impl_android.cc b/content/browser/renderer_host/compositor_impl_android.cc
index 96b3bf25..b48bb7c 100644
--- a/content/browser/renderer_host/compositor_impl_android.cc
+++ b/content/browser/renderer_host/compositor_impl_android.cc
@@ -433,36 +433,26 @@
 
 void CompositorImpl::SetSurface(const base::android::JavaRef<jobject>& surface,
                                 bool can_be_used_with_surface_control) {
-  JNIEnv* env = base::android::AttachCurrentThread();
   gpu::GpuSurfaceTracker* tracker = gpu::GpuSurfaceTracker::Get();
 
   if (window_) {
     // Shut down GL context before unregistering surface.
     SetVisible(false);
     tracker->RemoveSurface(surface_handle_);
-    ANativeWindow_release(window_);
     window_ = nullptr;
     surface_handle_ = gpu::kNullSurfaceHandle;
   }
 
-  ANativeWindow* window = nullptr;
-  if (surface) {
-    // Note: This ensures that any local references used by
-    // ANativeWindow_fromSurface are released immediately. This is needed as a
-    // workaround for https://code.google.com/p/android/issues/detail?id=68174
-    base::android::ScopedJavaLocalFrame scoped_local_reference_frame(env);
-    window = ANativeWindow_fromSurface(env, surface.obj());
-  }
+  gl::ScopedJavaSurface scoped_surface(surface, /*auto_release=*/false);
+  gl::ScopedANativeWindow window(scoped_surface);
 
   if (window) {
-    window_ = window;
-    ANativeWindow_acquire(window);
+    window_ = std::move(window);
     // Register first, SetVisible() might create a LayerTreeFrameSink.
     surface_handle_ = tracker->AddSurfaceForNativeWidget(
         gpu::GpuSurfaceTracker::SurfaceRecord(
-            window, surface, can_be_used_with_surface_control));
+            std::move(scoped_surface), can_be_used_with_surface_control));
     SetVisible(true);
-    ANativeWindow_release(window);
   }
 }
 
diff --git a/content/browser/renderer_host/compositor_impl_android.h b/content/browser/renderer_host/compositor_impl_android.h
index 6992231..40d5e6f 100644
--- a/content/browser/renderer_host/compositor_impl_android.h
+++ b/content/browser/renderer_host/compositor_impl_android.h
@@ -40,8 +40,7 @@
 #include "ui/android/window_android_compositor.h"
 #include "ui/compositor/compositor_lock.h"
 #include "ui/display/display_observer.h"
-
-struct ANativeWindow;
+#include "ui/gl/android/scoped_a_native_window.h"
 
 namespace cc {
 class AnimationHost;
@@ -263,7 +262,7 @@
   gfx::Size size_;
   bool requires_alpha_channel_ = false;
 
-  raw_ptr<ANativeWindow> window_;
+  gl::ScopedANativeWindow window_;
   gpu::SurfaceHandle surface_handle_;
   std::unique_ptr<ScopedCachedBackBuffer> cached_back_buffer_;
 
diff --git a/content/browser/resources/attribution_reporting/attribution_internals.ts b/content/browser/resources/attribution_reporting/attribution_internals.ts
index 9ddde4a..e3e86d5 100644
--- a/content/browser/resources/attribution_reporting/attribution_internals.ts
+++ b/content/browser/resources/attribution_reporting/attribution_internals.ts
@@ -9,7 +9,7 @@
 import {getTrustedHTML} from 'chrome://resources/js/static_types.js';
 import {Origin} from 'chrome://resources/mojo/url/mojom/origin.mojom-webui.js';
 
-import {ClearedDebugKey, ClearedDebugKey_Type, FailedSourceRegistration, Handler as AttributionInternalsHandler, HandlerRemote as AttributionInternalsHandlerRemote, ObserverInterface, ObserverReceiver, ReportID, WebUIDebugReport, WebUIReport, WebUISource, WebUISource_Attributability, WebUITrigger, WebUITrigger_Status} from './attribution_internals.mojom-webui.js';
+import {FailedSourceRegistration, Handler as AttributionInternalsHandler, HandlerRemote as AttributionInternalsHandlerRemote, ObserverInterface, ObserverReceiver, ReportID, WebUIDebugReport, WebUIReport, WebUISource, WebUISource_Attributability, WebUITrigger, WebUITrigger_Status} from './attribution_internals.mojom-webui.js';
 import {AttributionInternalsTableElement} from './attribution_internals_table.js';
 import {ReportType, SourceType} from './attribution_reporting.mojom-webui.js';
 import {SourceRegistrationError} from './source_registration_error.mojom-webui.js';
@@ -258,7 +258,15 @@
     this.filterData = JSON.stringify(mojo.filterData, null, ' ');
     this.aggregationKeys =
         JSON.stringify(mojo.aggregationKeys, bigintReplacer, ' ');
-    this.debugKey = mojo.debugKey ? mojo.debugKey.value.toString() : '';
+
+    if (mojo.debugKey?.debugKey !== undefined) {
+      this.debugKey = mojo.debugKey.debugKey.toString();
+    } else if (mojo.debugKey?.clearedDebugKey !== undefined) {
+      this.debugKey = `Cleared (was ${mojo.debugKey.clearedDebugKey})`;
+    } else {
+      this.debugKey = '';
+    }
+
     this.dedupKeys = mojo.dedupKeys.join(', ');
     this.aggregatableBudgetConsumed = mojo.aggregatableBudgetConsumed;
     this.aggregatableDedupKeys = mojo.aggregatableDedupKeys.join(', ');
@@ -343,6 +351,7 @@
   destinationOrigin: string;
   reportingOrigin: string;
   registrationJson: string;
+  clearedDebugKey: string;
   eventLevelStatus: string;
   aggregatableStatus: string;
 
@@ -351,6 +360,8 @@
     this.destinationOrigin = originToText(mojo.destinationOrigin);
     this.reportingOrigin = originToText(mojo.reportingOrigin);
     this.registrationJson = mojo.registrationJson;
+    this.clearedDebugKey =
+        mojo.clearedDebugKey ? `${mojo.clearedDebugKey.value}` : '';
     this.eventLevelStatus = triggerStatusToText(mojo.eventLevelStatus);
     this.aggregatableStatus = triggerStatusToText(mojo.aggregatableStatus);
   }
@@ -373,6 +384,8 @@
               'Reporting Origin', (e) => e.reportingOrigin),
           new CodeColumn<Trigger>(
               'Registration JSON', (e) => e.registrationJson),
+          new ValueColumn<Trigger, string>(
+              'Cleared Debug Key', (e) => e.clearedDebugKey),
         ],
         0,  // Sort by trigger time by default.
         'No triggers.',
@@ -717,49 +730,6 @@
   abstract renderMetadata(td: HTMLElement): void;
 }
 
-const CLEARED_DEBUG_KEY_COLS: Array<Column<ClearedDebugKeyLog>> = [
-  new ValueColumn<ClearedDebugKeyLog, string>(
-      'Cleared Debug Key', e => e.clearedDebugKey),
-  new ValueColumn<ClearedDebugKeyLog, string>('From', e => e.clearedFrom),
-  new ValueColumn<ClearedDebugKeyLog, string>(
-      'Reporting Origin', e => e.reportingOrigin),
-];
-
-class ClearedDebugKeyLog extends Log {
-  readonly clearedFrom: string;
-  readonly clearedDebugKey: string;
-
-  constructor(mojo: ClearedDebugKey) {
-    super(mojo);
-
-    this.clearedDebugKey = `${mojo.clearedDebugKey.value}`;
-
-    switch (mojo.clearedFrom) {
-      case (ClearedDebugKey_Type.kSource):
-        this.clearedFrom = 'Source';
-        break;
-      case (ClearedDebugKey_Type.kTrigger):
-        this.clearedFrom = 'Trigger';
-        break;
-      default:
-        this.clearedFrom = 'Unknown type';
-        break;
-    }
-  }
-
-  renderDescription(td: HTMLElement): void {
-    renderA(
-        td,
-        'Cleared Debug Key',
-        'https://github.com/WICG/attribution-reporting-api/blob/main/EVENT.md#attribution-success-debugging-reports',
-    );
-  }
-
-  renderMetadata(td: HTMLElement) {
-    renderDL(td, this, CLEARED_DEBUG_KEY_COLS);
-  }
-}
-
 const FAILED_SOURCE_REGISTRATION_COLS:
     Array<Column<FailedSourceRegistrationLog>> = [
       new ValueColumn<FailedSourceRegistrationLog, string>(
@@ -1151,11 +1121,6 @@
     assert(logTableModel);
     logTableModel.addLog(new FailedSourceRegistrationLog(mojo));
   }
-
-  onDebugKeyCleared(mojo: ClearedDebugKey) {
-    assert(logTableModel);
-    logTableModel.addLog(new ClearedDebugKeyLog(mojo));
-  }
 }
 
 function installUnreadIndicator(model: TableModel<any>, tab: HTMLElement|null) {
diff --git a/content/browser/shared_storage/shared_storage_document_service_impl.cc b/content/browser/shared_storage/shared_storage_document_service_impl.cc
index 9af47aa8..9eb65f2e 100644
--- a/content/browser/shared_storage/shared_storage_document_service_impl.cc
+++ b/content/browser/shared_storage/shared_storage_document_service_impl.cc
@@ -344,8 +344,8 @@
     return true;
 
   return GetContentClient()->browser()->IsSharedStorageAllowed(
-      &render_frame_host(), main_frame_origin_,
-      render_frame_host().GetLastCommittedOrigin());
+      render_frame_host().GetBrowserContext(), &render_frame_host(),
+      main_frame_origin_, render_frame_host().GetLastCommittedOrigin());
 }
 
 std::string SharedStorageDocumentServiceImpl::SerializeLastCommittedOrigin()
diff --git a/content/browser/shared_storage/shared_storage_worklet_host.cc b/content/browser/shared_storage/shared_storage_worklet_host.cc
index ed6958d..ecce4f40 100644
--- a/content/browser/shared_storage/shared_storage_worklet_host.cc
+++ b/content/browser/shared_storage/shared_storage_worklet_host.cc
@@ -864,9 +864,10 @@
 }
 
 bool SharedStorageWorkletHost::IsSharedStorageAllowed() {
+  RenderFrameHost* rfh =
+      document_service_ ? &(document_service_->render_frame_host()) : nullptr;
   return GetContentClient()->browser()->IsSharedStorageAllowed(
-      &(document_service_->render_frame_host()), main_frame_origin_,
-      shared_storage_origin_);
+      browser_context_, rfh, main_frame_origin_, shared_storage_origin_);
 }
 
 }  // namespace content
diff --git a/content/common/android/surface_wrapper.cc b/content/common/android/surface_wrapper.cc
index fed61b3..ba00294 100644
--- a/content/common/android/surface_wrapper.cc
+++ b/content/common/android/surface_wrapper.cc
@@ -21,10 +21,10 @@
   return Java_SurfaceWrapper_canBeUsedWithSurfaceControl(env, obj);
 }
 
-base::android::ScopedJavaLocalRef<jobject> JNI_SurfaceWrapper_getSurface(
+base::android::ScopedJavaLocalRef<jobject> JNI_SurfaceWrapper_takeSurface(
     JNIEnv* env,
     const base::android::JavaRef<jobject>& obj) {
-  return Java_SurfaceWrapper_getSurface(env, obj);
+  return Java_SurfaceWrapper_takeSurface(env, obj);
 }
 
 }  // namespace content.
diff --git a/content/common/android/surface_wrapper.h b/content/common/android/surface_wrapper.h
index 0dde535..c6223dcc 100644
--- a/content/common/android/surface_wrapper.h
+++ b/content/common/android/surface_wrapper.h
@@ -18,7 +18,7 @@
     JNIEnv* env,
     const base::android::JavaRef<jobject>& obj);
 
-base::android::ScopedJavaLocalRef<jobject> JNI_SurfaceWrapper_getSurface(
+base::android::ScopedJavaLocalRef<jobject> JNI_SurfaceWrapper_takeSurface(
     JNIEnv* env,
     const base::android::JavaRef<jobject>& obj);
 
diff --git a/content/public/android/java/src/org/chromium/content/common/SurfaceWrapper.java b/content/public/android/java/src/org/chromium/content/common/SurfaceWrapper.java
index 44818a0..ed896228 100644
--- a/content/public/android/java/src/org/chromium/content/common/SurfaceWrapper.java
+++ b/content/public/android/java/src/org/chromium/content/common/SurfaceWrapper.java
@@ -18,21 +18,23 @@
 @JNINamespace("content")
 @MainDex
 public class SurfaceWrapper implements Parcelable {
-    private final Surface mSurface;
+    private Surface mSurface;
     private final boolean mCanBeUsedWithSurfaceControl;
 
-    public SurfaceWrapper(Surface surface, boolean canBeUsedWithSurfaceControl) {
+    private SurfaceWrapper(Surface surface, boolean canBeUsedWithSurfaceControl) {
         mSurface = surface;
         mCanBeUsedWithSurfaceControl = canBeUsedWithSurfaceControl;
     }
 
     @CalledByNative
-    public Surface getSurface() {
-        return mSurface;
+    private Surface takeSurface() {
+        Surface surface = mSurface;
+        mSurface = null;
+        return surface;
     }
 
     @CalledByNative
-    public boolean canBeUsedWithSurfaceControl() {
+    private boolean canBeUsedWithSurfaceControl() {
         return mCanBeUsedWithSurfaceControl;
     }
 
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index 110e353..b075b996 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -511,6 +511,7 @@
 }
 
 bool ContentBrowserClient::IsSharedStorageAllowed(
+    content::BrowserContext* browser_context,
     content::RenderFrameHost* rfh,
     const url::Origin& top_frame_origin,
     const url::Origin& accessing_origin) {
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index 99f1c92..450b57f 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -882,7 +882,10 @@
 
   // Allows the embedder to control if Shared Storage API operations can happen
   // in a given context.
-  virtual bool IsSharedStorageAllowed(content::RenderFrameHost* rfh,
+  //
+  // Note that `rfh` can be nullptr.
+  virtual bool IsSharedStorageAllowed(content::BrowserContext* browser_context,
+                                      content::RenderFrameHost* rfh,
                                       const url::Origin& top_frame_origin,
                                       const url::Origin& accessing_origin);
 
diff --git a/content/public/common/BUILD.gn b/content/public/common/BUILD.gn
index 80c8aa2..292a43ab 100644
--- a/content/public/common/BUILD.gn
+++ b/content/public/common/BUILD.gn
@@ -153,6 +153,7 @@
 
   sources = [
     "bindings_policy.h",
+    "cdm_info.cc",
     "cdm_info.h",
     "child_process_host.h",
     "child_process_host_delegate.h",
diff --git a/content/public/common/cdm_info.cc b/content/public/common/cdm_info.cc
new file mode 100644
index 0000000..36e73b2
--- /dev/null
+++ b/content/public/common/cdm_info.cc
@@ -0,0 +1,23 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <iostream>
+
+#include "cdm_info.h"
+
+#include "base/notreached.h"
+
+namespace content {
+
+std::string GetCdmInfoRobustnessName(CdmInfo::Robustness robustness) {
+  switch (robustness) {
+    case CdmInfo::Robustness::kHardwareSecure:
+      return "Hardware Secure";
+    case CdmInfo::Robustness::kSoftwareSecure:
+      return "Software Secure";
+  }
+  NOTREACHED();
+}
+
+}  // namespace content
\ No newline at end of file
diff --git a/content/public/common/cdm_info.h b/content/public/common/cdm_info.h
index 80a08df..2733702b 100644
--- a/content/public/common/cdm_info.h
+++ b/content/public/common/cdm_info.h
@@ -5,6 +5,7 @@
 #ifndef CONTENT_PUBLIC_COMMON_CDM_INFO_H_
 #define CONTENT_PUBLIC_COMMON_CDM_INFO_H_
 
+#include <iosfwd>
 #include <string>
 #include <vector>
 
@@ -104,6 +105,14 @@
   base::FilePath path;
 };
 
+CONTENT_EXPORT std::string GetCdmInfoRobustnessName(
+    CdmInfo::Robustness robustness);
+
+inline std::ostream& operator<<(std::ostream& os,
+                                CdmInfo::Robustness robustness) {
+  return os << GetCdmInfoRobustnessName(robustness);
+}
+
 }  // namespace content
 
 #endif  // CONTENT_PUBLIC_COMMON_CDM_INFO_H_
diff --git a/device/fido/features.cc b/device/fido/features.cc
index 69d89d34..361f2dc 100644
--- a/device/fido/features.cc
+++ b/device/fido/features.cc
@@ -52,4 +52,8 @@
              "DisableWebAuthnWithBrokenCerts",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+BASE_FEATURE(kWebAuthnNoPasskeysError,
+             "WebAuthenticationNoPasskeysError",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 }  // namespace device
diff --git a/device/fido/features.h b/device/fido/features.h
index c8a846c1..7ba3470 100644
--- a/device/fido/features.h
+++ b/device/fido/features.h
@@ -53,6 +53,10 @@
 COMPONENT_EXPORT(DEVICE_FIDO)
 BASE_DECLARE_FEATURE(kDisableWebAuthnWithBrokenCerts);
 
+// Enable a special-case dialog for when there are no internal credentials.
+COMPONENT_EXPORT(DEVICE_FIDO)
+BASE_DECLARE_FEATURE(kWebAuthnNoPasskeysError);
+
 }  // namespace device
 
 #endif  // DEVICE_FIDO_FEATURES_H_
diff --git a/device/fido/fido_request_handler_base.h b/device/fido/fido_request_handler_base.h
index 66566100..b6f57a5 100644
--- a/device/fido/fido_request_handler_base.h
+++ b/device/fido/fido_request_handler_base.h
@@ -123,6 +123,18 @@
     // authenticator and thus have privacy implications.
     ResidentKeyRequirement resident_key_requirement =
         ResidentKeyRequirement::kDiscouraged;
+
+    // transport_list_did_include_internal is set to true during a getAssertion
+    // request if at least one element of the allowList included the "internal"
+    // transport, or didn't have any transports.
+    //
+    // An embedder may use this to show a more precise UI when no transports
+    // are available. If the lack of transports is because the allowList only
+    // contained NFC-based credentials, and there's no NFC support, then that
+    // might be meaningfully different from the case where the allowList
+    // contained credentials that could have been on the local device but
+    // weren't.
+    bool transport_list_did_include_internal = false;
   };
 
   class COMPONENT_EXPORT(DEVICE_FIDO) Observer {
diff --git a/device/fido/get_assertion_request_handler.cc b/device/fido/get_assertion_request_handler.cc
index 393f8d6..7780b113 100644
--- a/device/fido/get_assertion_request_handler.cc
+++ b/device/fido/get_assertion_request_handler.cc
@@ -10,6 +10,7 @@
 #include <vector>
 
 #include "base/bind.h"
+#include "base/containers/contains.h"
 #include "base/containers/cxx20_erase.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/ranges/algorithm.h"
@@ -322,6 +323,13 @@
       request_.allow_list.empty();
   transport_availability_info().is_off_the_record_context =
       request_.is_off_the_record_context;
+  transport_availability_info().transport_list_did_include_internal =
+      std::any_of(request_.allow_list.begin(), request_.allow_list.end(),
+                  [](const PublicKeyCredentialDescriptor& cred) {
+                    return cred.transports.empty() ||
+                           base::Contains(cred.transports,
+                                          FidoTransportProtocol::kInternal);
+                  });
 
   if (request_.allow_list.empty()) {
     // Resident credential requests always involve user verification.
diff --git a/docs/adding_to_third_party.md b/docs/adding_to_third_party.md
index 98531ca1..1ee92840 100644
--- a/docs/adding_to_third_party.md
+++ b/docs/adding_to_third_party.md
@@ -27,9 +27,29 @@
 * Motivation of your project
 * Design docs
 * Additional checkout size
+   * If the increase is significant (e.g., 20+ MB), can we consider limiting the
+   files to be checked in?
 * Build time increase
-* Binary size increase on Android ([official](https://www.chromium.org/developers/gn-build-configuration)  builds)
+   * If the increase is significant (e.g., 30+ seconds), can we consider making
+   this an optional build target?
+* Binary size increase on Android ([official](https://www.chromium.org/developers/gn-build-configuration) builds)
+   * Any 16 KB increase on Android is flagged on the build bots and
+   justification is needed.
 * Binary size increase on Windows
+* Is this library maintained on all platforms that we will use it on?
+   * If not, will the Chrome org be expected to maintain this for some or all
+   platforms?
+* Does it have any performance / memory implications (esp. on Android)? Was the
+library designed with intended use on Android?
+* Do we really need the library? Is there any alternative such as an existing
+library already in Chromium? If introducing a library with similar functionality
+as existing, will it be easy for another developer to understand which should be
+used where? Will you commit to consolidating uses in Chromium and remove the
+alternative libraries?
+* For desktop (Win/Mac/Linux/ChromeOS), does the dependency introduce closed
+source components (e.g., binaries, WASM binaries, obfuscated code)? If yes,
+please reach out to Chrome ATLs.
+
 
 Googlers can access [go/chrome-atls](https://goto.google.com/chrome-atls) and review
 existing topics in g/chrome-atls, and can also come to office hours to ask
diff --git a/docs/speed/perf_lab_platforms.md b/docs/speed/perf_lab_platforms.md
index 908a401..f49c865 100644
--- a/docs/speed/perf_lab_platforms.md
+++ b/docs/speed/perf_lab_platforms.md
@@ -8,7 +8,7 @@
 
  * [android-go-perf](https://ci.chromium.org/p/chrome/builders/ci/android-go-perf): Android O (gobo).
  * [android-go-perf-pgo](https://ci.chromium.org/p/chrome/builders/ci/android-go-perf-pgo): Android O (gobo).
- * [android-go-wembley-perf](https://ci.chromium.org/p/chrome/builders/ci/android-go-wembley-perf): Android M | MASTER.
+ * [android-go-wembley-perf](https://ci.chromium.org/p/chrome/builders/ci/android-go-wembley-perf): Android 9131443.
  * [android-go_webview-perf](https://ci.chromium.org/p/chrome/builders/ci/android-go_webview-perf): Android OPM1.171019.021 (gobo).
  * [android-pixel2-perf](https://ci.chromium.org/p/chrome/builders/ci/android-pixel2-perf): Android OPM1.171019.021.
  * [android-pixel2-perf-calibration](https://ci.chromium.org/p/chrome/builders/ci/android-pixel2-perf-calibration): Android OPM1.171019.021.
diff --git a/extensions/browser/api/management/management_api.cc b/extensions/browser/api/management/management_api.cc
index b4427c47..9a4ea8c 100644
--- a/extensions/browser/api/management/management_api.cc
+++ b/extensions/browser/api/management/management_api.cc
@@ -360,8 +360,7 @@
   std::string error;
   scoped_refptr<Extension> extension =
       Extension::Create(base::FilePath(), ManifestLocation::kInvalidLocation,
-                        base::DictAdapterForMigration(*parsed_manifest),
-                        Extension::NO_FLAGS, &error);
+                        *parsed_manifest, Extension::NO_FLAGS, &error);
   // TODO(lazyboy): Do we need to use |error|?
   if (!extension) {
     Respond(Error(keys::kExtensionCreateError));
diff --git a/extensions/browser/image_loader_unittest.cc b/extensions/browser/image_loader_unittest.cc
index 30b1c04..e2177ce 100644
--- a/extensions/browser/image_loader_unittest.cc
+++ b/extensions/browser/image_loader_unittest.cc
@@ -79,19 +79,20 @@
     std::string error;
     JSONFileValueDeserializer deserializer(
         extension_dir.AppendASCII("manifest.json"));
-    std::unique_ptr<base::DictionaryValue> valid_value =
-        base::DictionaryValue::From(
-            deserializer.Deserialize(&error_code, &error));
+    std::unique_ptr<base::Value> valid_value =
+        deserializer.Deserialize(&error_code, &error);
     EXPECT_EQ(0, error_code) << error;
     if (error_code != 0)
       return nullptr;
 
     EXPECT_TRUE(valid_value.get());
-    if (!valid_value)
+    EXPECT_TRUE(valid_value->is_dict());
+    if (!valid_value || !valid_value->is_dict()) {
       return nullptr;
+    }
 
-    return Extension::Create(
-        extension_dir, location, *valid_value, Extension::NO_FLAGS, &error);
+    return Extension::Create(extension_dir, location, valid_value->GetDict(),
+                             Extension::NO_FLAGS, &error);
   }
 
   gfx::Image image_;
diff --git a/extensions/browser/sandboxed_unpacker.cc b/extensions/browser/sandboxed_unpacker.cc
index 182ba5bd..e478f9f 100644
--- a/extensions/browser/sandboxed_unpacker.cc
+++ b/extensions/browser/sandboxed_unpacker.cc
@@ -538,8 +538,7 @@
 
   std::string error_msg;
   scoped_refptr<Extension> extension(
-      Extension::Create(extension_root_, location_,
-                        base::Value::AsDictionaryValue(manifest.value()),
+      Extension::Create(extension_root_, location_, manifest->GetDict(),
                         creation_flags_, extension_id_, &error_msg));
   if (!extension) {
     ReportUnpackExtensionFailed(error_msg);
diff --git a/extensions/common/extension.cc b/extensions/common/extension.cc
index 9cdcae6..df8bc5b 100644
--- a/extensions/common/extension.cc
+++ b/extensions/common/extension.cc
@@ -140,7 +140,7 @@
 
 // Computes the |extension_id| from the given parameters. On success, returns
 // true. On failure, populates |error| and returns false.
-bool ComputeExtensionID(const base::DictAdapterForMigration& manifest,
+bool ComputeExtensionID(const base::Value::Dict& manifest,
                         const base::FilePath& path,
                         int creation_flags,
                         std::u16string* error,
@@ -221,12 +221,11 @@
 }
 
 // static
-scoped_refptr<Extension> Extension::Create(
-    const base::FilePath& path,
-    ManifestLocation location,
-    const base::DictAdapterForMigration& value,
-    int flags,
-    std::string* utf8_error) {
+scoped_refptr<Extension> Extension::Create(const base::FilePath& path,
+                                           ManifestLocation location,
+                                           const base::Value::Dict& value,
+                                           int flags,
+                                           std::string* utf8_error) {
   return Extension::Create(path,
                            location,
                            value,
@@ -237,13 +236,12 @@
 
 // TODO(sungguk): Continue removing std::string errors and replacing
 // with std::u16string. See http://crbug.com/71980.
-scoped_refptr<Extension> Extension::Create(
-    const base::FilePath& path,
-    ManifestLocation location,
-    const base::DictAdapterForMigration& value,
-    int flags,
-    const std::string& explicit_id,
-    std::string* utf8_error) {
+scoped_refptr<Extension> Extension::Create(const base::FilePath& path,
+                                           ManifestLocation location,
+                                           const base::Value::Dict& value,
+                                           int flags,
+                                           const std::string& explicit_id,
+                                           std::string* utf8_error) {
   base::ElapsedTimer timer;
   DCHECK(utf8_error);
   std::u16string error;
diff --git a/extensions/common/extension.h b/extensions/common/extension.h
index 0862216d..91698320 100644
--- a/extensions/common/extension.h
+++ b/extensions/common/extension.h
@@ -32,10 +32,6 @@
 #error "Extensions must be enabled"
 #endif
 
-namespace base {
-class DictAdapterForMigration;
-}
-
 namespace extensions {
 class HashedExtensionId;
 class PermissionsData;
@@ -156,22 +152,20 @@
   Extension(const Extension&) = delete;
   Extension& operator=(const Extension&) = delete;
 
-  static scoped_refptr<Extension> Create(
-      const base::FilePath& path,
-      mojom::ManifestLocation location,
-      const base::DictAdapterForMigration& value,
-      int flags,
-      std::string* error);
+  static scoped_refptr<Extension> Create(const base::FilePath& path,
+                                         mojom::ManifestLocation location,
+                                         const base::Value::Dict& value,
+                                         int flags,
+                                         std::string* error);
 
   // In a few special circumstances, we want to create an Extension and give it
   // an explicit id. Most consumers should just use the other Create() method.
-  static scoped_refptr<Extension> Create(
-      const base::FilePath& path,
-      mojom::ManifestLocation location,
-      const base::DictAdapterForMigration& value,
-      int flags,
-      const ExtensionId& explicit_id,
-      std::string* error);
+  static scoped_refptr<Extension> Create(const base::FilePath& path,
+                                         mojom::ManifestLocation location,
+                                         const base::Value::Dict& value,
+                                         int flags,
+                                         const ExtensionId& explicit_id,
+                                         std::string* error);
 
   // Valid schemes for web extent URLPatterns.
   static const int kValidWebExtentSchemes;
@@ -307,9 +301,7 @@
   const std::vector<InstallWarning>& install_warnings() const {
     return install_warnings_;
   }
-  const extensions::Manifest* manifest() const {
-    return manifest_.get();
-  }
+  const extensions::Manifest* manifest() const { return manifest_.get(); }
   bool wants_file_access() const { return wants_file_access_; }
   // TODO(rdevlin.cronin): This is needed for ContentScriptsHandler, and should
   // be moved out as part of crbug.com/159265. This should not be used anywhere
@@ -344,7 +336,7 @@
   bool is_extension() const;            // Regular browser extension, not an app
   bool is_shared_module() const;        // Shared module
   bool is_theme() const;                // Theme
-  bool is_login_screen_extension() const;  // Extension on login screen.
+  bool is_login_screen_extension() const;     // Extension on login screen.
   bool is_chromeos_system_extension() const;  // ChromeOS System Extension.
 
   // True if this is a platform app, hosted app, or legacy packaged app.
@@ -493,7 +485,7 @@
   base::GUID guid_;
 };
 
-typedef std::vector<scoped_refptr<const Extension> > ExtensionList;
+typedef std::vector<scoped_refptr<const Extension>> ExtensionList;
 
 // Handy struct to pass core extension info around.
 struct ExtensionInfo {
diff --git a/extensions/common/extension_builder.cc b/extensions/common/extension_builder.cc
index 9207b4d5..bc9b698 100644
--- a/extensions/common/extension_builder.cc
+++ b/extensions/common/extension_builder.cc
@@ -144,11 +144,17 @@
     id_ = crx_file::id_util::GenerateId(manifest_data_->name);
 
   std::string error;
+
+  // This allows `*manifest_value` to be passed as a reference instead of
+  // needing to be cloned.
+  absl::optional<base::Value::Dict> manifest_data_value;
+  if (manifest_data_) {
+    manifest_data_value = manifest_data_->GetValue();
+  }
   scoped_refptr<const Extension> extension = Extension::Create(
       path_, location_,
-      manifest_data_ ? base::DictAdapterForMigration(manifest_data_->GetValue())
-                     : *manifest_value_,
-      flags_, id_, &error);
+      manifest_data_value ? *manifest_data_value : *manifest_value_, flags_,
+      id_, &error);
 
   CHECK(error.empty()) << error;
   CHECK(extension);
diff --git a/extensions/common/features/simple_feature_unittest.cc b/extensions/common/features/simple_feature_unittest.cc
index 8894dbb..520a8f2 100644
--- a/extensions/common/features/simple_feature_unittest.cc
+++ b/extensions/common/features/simple_feature_unittest.cc
@@ -338,11 +338,11 @@
   feature.set_min_manifest_version(21);
   feature.set_max_manifest_version(25);
 
-  base::DictionaryValue manifest;
-  manifest.SetStringKey("name", "test");
-  manifest.SetStringKey("version", "1");
-  manifest.SetIntKey("manifest_version", 21);
-  manifest.SetStringPath("app.launch.local_path", "foo.html");
+  base::Value::Dict manifest;
+  manifest.Set("name", "test");
+  manifest.Set("version", "1");
+  manifest.Set("manifest_version", 21);
+  manifest.SetByDottedPath("app.launch.local_path", "foo.html");
 
   std::string error;
   scoped_refptr<const Extension> extension(
@@ -456,11 +456,11 @@
 }
 
 TEST_F(SimpleFeatureTest, SessionType) {
-  base::DictionaryValue manifest;
-  manifest.SetStringKey("name", "test");
-  manifest.SetStringKey("version", "1");
-  manifest.SetIntKey("manifest_version", 2);
-  manifest.SetStringPath("app.launch.local_path", "foo.html");
+  base::Value::Dict manifest;
+  manifest.Set("name", "test");
+  manifest.Set("version", "1");
+  manifest.Set("manifest_version", 2);
+  manifest.SetByDottedPath("app.launch.local_path", "foo.html");
 
   std::string error;
   scoped_refptr<const Extension> extension(
diff --git a/extensions/common/manifest_handler_unittest.cc b/extensions/common/manifest_handler_unittest.cc
index 47661eab..ba85545 100644
--- a/extensions/common/manifest_handler_unittest.cc
+++ b/extensions/common/manifest_handler_unittest.cc
@@ -213,18 +213,17 @@
   ScopedTestingManifestHandlerRegistry scoped_registry;
   // Can't use ExtensionBuilder, because this extension will fail to
   // be parsed.
-  std::unique_ptr<base::DictionaryValue> manifest_a(
-      DictionaryBuilder()
-          .Set("name", "no name")
-          .Set("version", "0")
-          .Set("manifest_version", 2)
-          .Set("a", 1)
-          .Build());
+  base::Value::Dict manifest_a(DictionaryBuilder()
+                                   .Set("name", "no name")
+                                   .Set("version", "0")
+                                   .Set("manifest_version", 2)
+                                   .Set("a", 1)
+                                   .BuildDict());
 
   // Succeeds when "a" is not recognized.
   std::string error;
   scoped_refptr<Extension> extension = Extension::Create(
-      base::FilePath(), mojom::ManifestLocation::kInvalidLocation, *manifest_a,
+      base::FilePath(), mojom::ManifestLocation::kInvalidLocation, manifest_a,
       Extension::NO_FLAGS, &error);
   EXPECT_TRUE(extension.get());
 
@@ -237,7 +236,7 @@
 
   extension = Extension::Create(base::FilePath(),
                                 mojom::ManifestLocation::kInvalidLocation,
-                                *manifest_a, Extension::NO_FLAGS, &error);
+                                manifest_a, Extension::NO_FLAGS, &error);
   EXPECT_FALSE(extension.get());
   EXPECT_EQ("A", error);
 }
diff --git a/extensions/common/manifest_test.cc b/extensions/common/manifest_test.cc
index dd15eb4..74a8527 100644
--- a/extensions/common/manifest_test.cc
+++ b/extensions/common/manifest_test.cc
@@ -126,8 +126,7 @@
   const base::Value& value = manifest.GetManifest(test_data_dir, error);
   if (value.is_none())
     return nullptr;
-  return Extension::Create(test_data_dir.DirName(), location,
-                           base::DictAdapterForMigration(value.GetDict()),
+  return Extension::Create(test_data_dir.DirName(), location, value.GetDict(),
                            flags, GetTestExtensionID(), error);
 }
 
diff --git a/extensions/common/mojom/permission_set_mojom_traits_unittest.cc b/extensions/common/mojom/permission_set_mojom_traits_unittest.cc
index 2ae2422..a75a2e4 100644
--- a/extensions/common/mojom/permission_set_mojom_traits_unittest.cc
+++ b/extensions/common/mojom/permission_set_mojom_traits_unittest.cc
@@ -47,8 +47,7 @@
     value.Append("tcp-connect:*.example.com:80");
     value.Append("udp-bind::8080");
     value.Append("udp-send-to::8888");
-    ASSERT_TRUE(
-        input->FromValue(&base::Value::AsListValue(value), nullptr, nullptr));
+    ASSERT_TRUE(input->FromValue(&value, nullptr, nullptr));
   }
 
   std::unique_ptr<APIPermission> output = nullptr;
@@ -67,8 +66,7 @@
     value.Append("tcp-connect:*.example.com:80");
     value.Append("udp-bind::8080");
     value.Append("udp-send-to::8888");
-    ASSERT_TRUE(permission->FromValue(&base::Value::AsListValue(value), nullptr,
-                                      nullptr));
+    ASSERT_TRUE(permission->FromValue(&value, nullptr, nullptr));
   }
 
   APIPermissionSet input;
@@ -135,8 +133,7 @@
     value.Append("tcp-connect:*.example.com:80");
     value.Append("udp-bind::8080");
     value.Append("udp-send-to::8888");
-    ASSERT_TRUE(permission->FromValue(&base::Value::AsListValue(value), nullptr,
-                                      nullptr));
+    ASSERT_TRUE(permission->FromValue(&value, nullptr, nullptr));
   }
 
   APIPermissionSet apis;
diff --git a/gpu/command_buffer/service/image_reader_gl_owner.cc b/gpu/command_buffer/service/image_reader_gl_owner.cc
index 4b6fb13..2497c096 100644
--- a/gpu/command_buffer/service/image_reader_gl_owner.cc
+++ b/gpu/command_buffer/service/image_reader_gl_owner.cc
@@ -226,14 +226,14 @@
   // If we've already lost the texture, then do nothing.
   if (!image_reader_) {
     DLOG(ERROR) << "Already lost texture / image reader";
-    return gl::ScopedJavaSurface::AcquireExternalSurface(nullptr);
+    return nullptr;
   }
 
   // Get the android native window from the image reader.
   ANativeWindow* window = nullptr;
   if (loader_->AImageReader_getWindow(image_reader_, &window) != AMEDIA_OK) {
     DLOG(ERROR) << "unable to get a window from image reader.";
-    return gl::ScopedJavaSurface::AcquireExternalSurface(nullptr);
+    return nullptr;
   }
 
   // Get the java surface object from the Android native window.
@@ -243,7 +243,7 @@
   DCHECK(j_surface);
 
   // Get the scoped java surface that will call release() on destruction.
-  return gl::ScopedJavaSurface(j_surface);
+  return gl::ScopedJavaSurface(j_surface, /*auto_release=*/true);
 }
 
 void ImageReaderGLOwner::UpdateTexImage() {
diff --git a/gpu/command_buffer/service/shared_image/compound_image_backing.cc b/gpu/command_buffer/service/shared_image/compound_image_backing.cc
index 79550ab9..9e94f8bb 100644
--- a/gpu/command_buffer/service/shared_image/compound_image_backing.cc
+++ b/gpu/command_buffer/service/shared_image/compound_image_backing.cc
@@ -309,39 +309,33 @@
     bool allow_shm_overlays,
     const Mailbox& mailbox,
     gfx::GpuMemoryBufferHandle handle,
-    gfx::BufferFormat buffer_format,
+    gfx::BufferFormat format,
     gfx::BufferPlane plane,
     const gfx::Size& size,
     const gfx::ColorSpace& color_space,
     GrSurfaceOrigin surface_origin,
     SkAlphaType alpha_type,
     uint32_t usage) {
-  DCHECK(IsValidSharedMemoryBufferFormat(size, buffer_format, plane));
-
-  const gfx::Size plane_size = GetPlaneSize(plane, size);
-  const viz::ResourceFormat plane_format =
-      viz::GetResourceFormat(GetPlaneBufferFormat(plane, buffer_format));
-
-  const size_t plane_index = GetPlaneIndex(plane, buffer_format);
-  handle.offset +=
-      gfx::BufferOffsetForBufferFormat(size, buffer_format, plane_index);
+  DCHECK(IsValidSharedMemoryBufferFormat(size, format, plane));
 
   SharedMemoryRegionWrapper shm_wrapper;
-  if (!shm_wrapper.Initialize(handle, plane_size, plane_format)) {
+  if (!shm_wrapper.Initialize(handle, size, format, plane)) {
     DLOG(ERROR) << "Failed to create SharedMemoryRegionWrapper";
     return nullptr;
   }
 
-  auto si_format = viz::SharedImageFormat::SinglePlane(plane_format);
+  const gfx::Size plane_size = GetPlaneSize(plane, size);
+  const auto plane_format = viz::SharedImageFormat::SinglePlane(
+      viz::GetResourceFormat(GetPlaneBufferFormat(plane, format)));
 
   auto shm_backing = std::make_unique<SharedMemoryImageBacking>(
-      mailbox, si_format, plane_size, color_space, surface_origin, alpha_type,
-      SHARED_IMAGE_USAGE_CPU_WRITE, std::move(shm_wrapper));
+      mailbox, plane_format, plane_size, color_space, surface_origin,
+      alpha_type, SHARED_IMAGE_USAGE_CPU_WRITE, std::move(shm_wrapper));
   shm_backing->SetNotRefCounted();
 
   return base::WrapUnique(new CompoundImageBacking(
-      mailbox, si_format, plane_size, color_space, surface_origin, alpha_type,
-      usage, allow_shm_overlays, std::move(shm_backing),
+      mailbox, plane_format, plane_size, color_space, surface_origin,
+      alpha_type, usage, allow_shm_overlays, std::move(shm_backing),
       gpu_backing_factory->GetWeakPtr()));
 }
 
diff --git a/gpu/command_buffer/service/shared_image/egl_image_backing.cc b/gpu/command_buffer/service/shared_image/egl_image_backing.cc
index 2fd05b2f..ca0e2bc 100644
--- a/gpu/command_buffer/service/shared_image/egl_image_backing.cc
+++ b/gpu/command_buffer/service/shared_image/egl_image_backing.cc
@@ -485,12 +485,6 @@
   return base::MakeRefCounted<TextureHolder>(std::move(texture));
 }
 
-void EGLImageBacking::SetEndReadFence(
-    scoped_refptr<gl::SharedGLFenceEGL> shared_egl_fence) {
-  AutoLock auto_lock(this);
-  read_fences_[gl::g_current_gl_context] = std::move(shared_egl_fence);
-}
-
 void EGLImageBacking::MarkForDestruction() {
   AutoLock auto_lock(this);
   DCHECK(!have_context() || created_on_context_ == gl::g_current_gl_context);
diff --git a/gpu/command_buffer/service/shared_image/egl_image_backing.h b/gpu/command_buffer/service/shared_image/egl_image_backing.h
index 3ddf2da9..86e8d60e 100644
--- a/gpu/command_buffer/service/shared_image/egl_image_backing.h
+++ b/gpu/command_buffer/service/shared_image/egl_image_backing.h
@@ -90,8 +90,6 @@
   scoped_refptr<TextureHolder> GenEGLImageSibling(
       base::span<const uint8_t> pixel_data);
 
-  void SetEndReadFence(scoped_refptr<gl::SharedGLFenceEGL> shared_egl_fence);
-
   const GLCommonImageBackingFactory::FormatInfo format_info_;
   scoped_refptr<TextureHolder> source_texture_holder_;
   raw_ptr<gl::GLApi> created_on_context_;
diff --git a/gpu/command_buffer/service/shared_image/gl_texture_image_backing.cc b/gpu/command_buffer/service/shared_image/gl_texture_image_backing.cc
index b8dff58..340359e 100644
--- a/gpu/command_buffer/service/shared_image/gl_texture_image_backing.cc
+++ b/gpu/command_buffer/service/shared_image/gl_texture_image_backing.cc
@@ -469,10 +469,8 @@
   SharedContextState* shared_context_state = factory()->GetSharedContextState();
   ui::ScopedMakeCurrent smc(shared_context_state->context(),
                             shared_context_state->surface());
-  auto image_np = base::MakeRefCounted<gl::GLImageNativePixmap>(
-      size(), ToBufferFormat(format()));
-  image_np->InitializeFromTexture(GetGLServiceId());
-  image_egl_ = image_np;
+  image_egl_ = gl::GLImageNativePixmap::CreateFromTexture(
+      size(), ToBufferFormat(format()), GetGLServiceId());
   if (passthrough_texture_) {
     passthrough_texture_->SetLevelImage(passthrough_texture_->target(), 0,
                                         image_egl_.get());
diff --git a/gpu/command_buffer/service/shared_image/shared_image_factory.cc b/gpu/command_buffer/service/shared_image/shared_image_factory.cc
index aa2b9eb02..0c5e3f8 100644
--- a/gpu/command_buffer/service/shared_image/shared_image_factory.cc
+++ b/gpu/command_buffer/service/shared_image/shared_image_factory.cc
@@ -449,10 +449,12 @@
 
   std::unique_ptr<SharedImageBacking> backing;
   if (use_compound) {
+    // Only allow shmem overlays for NV12 on Windows.
 #if BUILDFLAG(IS_WIN)
-    constexpr bool allow_shm_overlays = true;
+    const bool allow_shm_overlays =
+        format == gfx::BufferFormat::YUV_420_BIPLANAR;
 #else
-    constexpr bool allow_shm_overlays = false;
+    const bool allow_shm_overlays = false;
 #endif
     backing = CompoundImageBacking::CreateSharedMemory(
         factory, allow_shm_overlays, mailbox, std::move(handle), format, plane,
diff --git a/gpu/command_buffer/service/shared_image/shared_memory_image_backing_factory.cc b/gpu/command_buffer/service/shared_image/shared_memory_image_backing_factory.cc
index 1d88dd89..261fdc0 100644
--- a/gpu/command_buffer/service/shared_image/shared_memory_image_backing_factory.cc
+++ b/gpu/command_buffer/service/shared_image/shared_memory_image_backing_factory.cc
@@ -60,12 +60,11 @@
     SkAlphaType alpha_type,
     uint32_t usage) {
   DCHECK(handle.type == gfx::SHARED_MEMORY_BUFFER);
-  viz::ResourceFormat format = viz::GetResourceFormat(buffer_format);
   SharedMemoryRegionWrapper shm_wrapper;
-  if (!shm_wrapper.Initialize(handle, size, format)) {
+  if (!shm_wrapper.Initialize(handle, size, buffer_format, plane)) {
     return nullptr;
   }
-
+  const auto format = viz::GetResourceFormat(buffer_format);
   auto backing = std::make_unique<SharedMemoryImageBacking>(
       mailbox, viz::SharedImageFormat::SinglePlane(format), size, color_space,
       surface_origin, alpha_type, usage, std::move(shm_wrapper));
diff --git a/gpu/command_buffer/service/shared_memory_region_wrapper.cc b/gpu/command_buffer/service/shared_memory_region_wrapper.cc
index 11c93282..91fa8ce 100644
--- a/gpu/command_buffer/service/shared_memory_region_wrapper.cc
+++ b/gpu/command_buffer/service/shared_memory_region_wrapper.cc
@@ -7,8 +7,8 @@
 #include "base/logging.h"
 #include "base/numerics/checked_math.h"
 #include "base/system/sys_info.h"
-#include "components/viz/common/resources/resource_format_utils.h"
-#include "components/viz/common/resources/resource_sizes.h"
+#include "gpu/command_buffer/common/gpu_memory_buffer_support.h"
+#include "ui/gfx/buffer_format_util.h"
 #include "ui/gfx/gpu_memory_buffer.h"
 
 namespace gpu {
@@ -16,39 +16,25 @@
 
 // Validate that |stride| will work for pixels with |size| and |format|.
 bool ValidateStride(const gfx::Size size,
-                    viz::ResourceFormat format,
+                    gfx::BufferFormat format,
                     uint32_t stride) {
   if (!base::IsValueInRangeForNumericType<size_t>(stride))
     return false;
 
-  uint32_t min_width_in_bytes = 0;
-  if (!viz::ResourceSizes::MaybeWidthInBytes(size.width(), format,
-                                             &min_width_in_bytes)) {
+  // Use plane index 0 since we can't handle different plane strides anyway.
+  size_t alignment = gfx::RowByteAlignmentForBufferFormat(format, /*plane=*/0);
+  if (stride % alignment != 0)
+    return false;
+
+  size_t min_width_in_bytes = 0;
+  if (!gfx::RowSizeForBufferFormatChecked(size.width(), format, /*plane=*/0,
+                                          &min_width_in_bytes)) {
     return false;
   }
 
   if (stride < min_width_in_bytes)
     return false;
 
-  // Check that stride is a multiple of pixel byte size.
-  int bits_per_pixel = viz::BitsPerPixel(format);
-  switch (bits_per_pixel) {
-    case 64:
-    case 32:
-    case 16:
-      if (stride % (bits_per_pixel / 8) != 0)
-        return false;
-      break;
-    case 8:
-    case 4:
-      break;
-    default:
-      // YVU420, YUV_420_BIPLANAR, and YUVA_420_TRIPLANAR format aren't
-      // supported.
-      NOTREACHED();
-      return false;
-  }
-
   return true;
 }
 
@@ -64,7 +50,8 @@
 bool SharedMemoryRegionWrapper::Initialize(
     const gfx::GpuMemoryBufferHandle& handle,
     const gfx::Size& size,
-    viz::ResourceFormat format) {
+    gfx::BufferFormat format,
+    gfx::BufferPlane plane) {
   DCHECK(!mapping_.IsValid());
 
   if (!handle.region.IsValid()) {
@@ -77,30 +64,49 @@
     return false;
   }
 
-  // Minimize the amount of address space we use but make sure offset is a
-  // multiple of page size as required by MapAt().
-  size_t allocation_granularity = base::SysInfo::VMAllocationGranularity();
-  size_t memory_offset = handle.offset % allocation_granularity;
-  size_t map_offset =
-      allocation_granularity * (handle.offset / allocation_granularity);
-
-  base::CheckedNumeric<size_t> checked_size = handle.stride;
-  checked_size *= size.height();
-  checked_size += memory_offset;
-  if (!checked_size.IsValid()) {
+  size_t buffer_size;
+  if (!gfx::BufferSizeForBufferFormatChecked(size, format, &buffer_size)) {
     DLOG(ERROR) << "Invalid GMB size.";
     return false;
   }
 
-  mapping_ = handle.region.MapAt(static_cast<off_t>(map_offset),
-                                 checked_size.ValueOrDie());
+  // Minimize the amount of address space we use but make sure offset is a
+  // multiple of page size as required by MapAt().
+  // TODO(sunnyps): This doesn't seem to be a requirement of MapAt() anymore.
+  const size_t allocation_granularity =
+      base::SysInfo::VMAllocationGranularity();
+  const size_t memory_offset = handle.offset % allocation_granularity;
+  const size_t map_offset =
+      allocation_granularity * (handle.offset / allocation_granularity);
 
+  base::CheckedNumeric<size_t> checked_size = buffer_size;
+  checked_size += memory_offset;
+  if (!checked_size.IsValid()) {
+    DLOG(ERROR) << "Invalid shared memory region map size.";
+    return false;
+  }
+
+  const size_t map_size = checked_size.ValueOrDie();
+  mapping_ = handle.region.MapAt(static_cast<off_t>(map_offset), map_size);
   if (!mapping_.IsValid()) {
     DLOG(ERROR) << "Failed to map shared memory.";
     return false;
   }
 
-  offset_ = memory_offset;
+  // Add plane offset separately so that we map the entire buffer even if we're
+  // accessing an individual plane - this helps with shared memory overlays on
+  // Windows by allowing access via the Y plane shared image only.
+  const size_t plane_index = GetPlaneIndex(plane, format);
+  const size_t plane_offset =
+      gfx::BufferOffsetForBufferFormat(size, format, plane_index);
+#if DCHECK_IS_ON()
+  const gfx::Size plane_size = GetPlaneSize(plane, size);
+  const size_t plane_size_bytes =
+      gfx::PlaneSizeForBufferFormat(plane_size, format, plane_index);
+  DCHECK_LE(memory_offset + plane_offset + plane_size_bytes, map_size);
+#endif
+
+  offset_ = memory_offset + plane_offset;
   stride_ = handle.stride;
 
   return true;
diff --git a/gpu/command_buffer/service/shared_memory_region_wrapper.h b/gpu/command_buffer/service/shared_memory_region_wrapper.h
index 19ee638..d8ace07 100644
--- a/gpu/command_buffer/service/shared_memory_region_wrapper.h
+++ b/gpu/command_buffer/service/shared_memory_region_wrapper.h
@@ -8,7 +8,7 @@
 #include "base/containers/span.h"
 #include "base/memory/shared_memory_mapping.h"
 #include "base/unguessable_token.h"
-#include "components/viz/common/resources/resource_format.h"
+#include "ui/gfx/buffer_types.h"
 #include "ui/gfx/geometry/size.h"
 
 namespace gfx {
@@ -31,7 +31,8 @@
   // until destruction.
   bool Initialize(const gfx::GpuMemoryBufferHandle& handle,
                   const gfx::Size& size,
-                  viz::ResourceFormat format);
+                  gfx::BufferFormat format,
+                  gfx::BufferPlane plane);
 
   bool IsValid() const;
   uint8_t* GetMemory() const;
diff --git a/gpu/command_buffer/tests/gl_test_utils.cc b/gpu/command_buffer/tests/gl_test_utils.cc
index a3108c9c..686c482 100644
--- a/gpu/command_buffer/tests/gl_test_utils.cc
+++ b/gpu/command_buffer/tests/gl_test_utils.cc
@@ -484,9 +484,9 @@
   EXPECT_NE(0u, tex_service_id);
 
   // Create an EGLImage from the real texture id.
-  auto image = base::MakeRefCounted<gl::GLImageNativePixmap>(size, format);
-  bool result = image->InitializeFromTexture(tex_service_id);
-  DCHECK(result);
+  auto image =
+      gl::GLImageNativePixmap::CreateFromTexture(size, format, tex_service_id);
+  DCHECK(image);
 
   // The test will own the EGLImage no need to keep a reference on the GL
   // texture after returning from this function. This is covered by the
diff --git a/gpu/ipc/common/gpu_surface_lookup.h b/gpu/ipc/common/gpu_surface_lookup.h
index f7b3a4e..ac1cddc 100644
--- a/gpu/ipc/common/gpu_surface_lookup.h
+++ b/gpu/ipc/common/gpu_surface_lookup.h
@@ -5,14 +5,10 @@
 #ifndef GPU_IPC_COMMON_GPU_SURFACE_LOOKUP_H_
 #define GPU_IPC_COMMON_GPU_SURFACE_LOOKUP_H_
 
-#include "build/build_config.h"
 #include "gpu/gpu_export.h"
 #include "gpu/ipc/common/surface_handle.h"
 #include "ui/gfx/native_widget_types.h"
-
-#if BUILDFLAG(IS_ANDROID)
 #include "ui/gl/android/scoped_java_surface.h"
-#endif
 
 namespace gpu {
 
@@ -30,11 +26,9 @@
   static GpuSurfaceLookup* GetInstance();
   static void InitInstance(GpuSurfaceLookup* lookup);
 
-#if BUILDFLAG(IS_ANDROID)
   virtual gl::ScopedJavaSurface AcquireJavaSurface(
       int surface_id,
       bool* can_be_used_with_surface_control) = 0;
-#endif
 };
 
 }  // namespace gpu
diff --git a/gpu/ipc/common/gpu_surface_tracker.cc b/gpu/ipc/common/gpu_surface_tracker.cc
index 84921d2..0d28aec 100644
--- a/gpu/ipc/common/gpu_surface_tracker.cc
+++ b/gpu/ipc/common/gpu_surface_tracker.cc
@@ -6,31 +6,15 @@
 
 #include "base/check.h"
 #include "build/build_config.h"
-
-#if BUILDFLAG(IS_ANDROID)
-#include <android/native_window_jni.h>
 #include "ui/gl/android/scoped_java_surface.h"
-#endif  // BUILDFLAG(IS_ANDROID)
 
 namespace gpu {
 
-#if BUILDFLAG(IS_ANDROID)
 GpuSurfaceTracker::SurfaceRecord::SurfaceRecord(
-    gfx::AcceleratedWidget widget,
-    const base::android::JavaRef<jobject>& j_surface,
+    gl::ScopedJavaSurface surface,
     bool can_be_used_with_surface_control)
-    : widget(widget),
-      can_be_used_with_surface_control(can_be_used_with_surface_control) {
-  // TODO(liberato): It would be nice to assert |surface|, but we
-  // can't.  in_process_context_factory.cc (for tests) actually calls us without
-  // a Surface from java.  Presumably, nobody uses it.  crbug.com/712717 .
-  if (j_surface)
-    surface = gl::ScopedJavaSurface::AcquireExternalSurface(j_surface);
-}
-#else   // BUILDFLAG(IS_ANDROID)
-GpuSurfaceTracker::SurfaceRecord::SurfaceRecord(gfx::AcceleratedWidget widget)
-    : widget(widget) {}
-#endif  // !BUILDFLAG(IS_ANDROID)
+    : surface(std::move(surface)),
+      can_be_used_with_surface_control(can_be_used_with_surface_control) {}
 
 GpuSurfaceTracker::SurfaceRecord::SurfaceRecord(SurfaceRecord&&) = default;
 
@@ -66,7 +50,6 @@
   surface_map_.erase(surface_handle);
 }
 
-#if BUILDFLAG(IS_ANDROID)
 gl::ScopedJavaSurface GpuSurfaceTracker::AcquireJavaSurface(
     gpu::SurfaceHandle surface_handle,
     bool* can_be_used_with_surface_control) {
@@ -80,9 +63,8 @@
 
   *can_be_used_with_surface_control =
       it->second.can_be_used_with_surface_control;
-  return gl::ScopedJavaSurface::AcquireExternalSurface(j_surface.j_surface());
+  return j_surface.CopyRetainOwnership();
 }
-#endif
 
 std::size_t GpuSurfaceTracker::GetSurfaceCount() {
   base::AutoLock lock(surface_map_lock_);
diff --git a/gpu/ipc/common/gpu_surface_tracker.h b/gpu/ipc/common/gpu_surface_tracker.h
index 3b245fa..9281127 100644
--- a/gpu/ipc/common/gpu_surface_tracker.h
+++ b/gpu/ipc/common/gpu_surface_tracker.h
@@ -9,60 +9,41 @@
 
 #include <map>
 
+#include "base/android/scoped_java_ref.h"
 #include "base/memory/singleton.h"
 #include "base/synchronization/lock.h"
-#include "build/build_config.h"
 #include "gpu/gpu_export.h"
 #include "gpu/ipc/common/gpu_surface_lookup.h"
 #include "gpu/ipc/common/surface_handle.h"
-#include "ui/gfx/native_widget_types.h"
-
-#if BUILDFLAG(IS_ANDROID)
-#include "base/android/scoped_java_ref.h"
-#endif
+#include "ui/gl/android/scoped_java_surface.h"
 
 namespace gpu {
 
-// This class is used on Android and Mac, and is responsible for tracking native
+// This class is used on Android, and is responsible for tracking native
 // window surfaces exposed to the GPU process. Every surface gets registered to
 // this class, and gets a handle.  The handle can be passed to
 // CommandBufferProxyImpl::Create or to
 // GpuMemoryBufferManager::CreateGpuMemoryBuffer.
 // On Android, the handle is used in the GPU process to get a reference to the
-// ANativeWindow, using GpuSurfaceLookup (implemented by
-// ChildProcessSurfaceManager).  We require that an Android Surface is provided
-// with the ANativeWindow, so one must provide an explicit GpuSurfaceTracker::
-// SurfaceRecord when adding it.
-// On Mac, the handle just passes through the GPU process, and is sent back via
-// GpuCommandBufferMsg_SwapBuffersCompleted to reference the surface.
+// ScopedJavaSurface, using GpuSurfaceLookup (implemented by
+// ChildProcessSurfaceManager).
 // This class is thread safe.
 class GPU_EXPORT GpuSurfaceTracker : public gpu::GpuSurfaceLookup {
  public:
   struct SurfaceRecord {
-#if BUILDFLAG(IS_ANDROID)
-    SurfaceRecord(gfx::AcceleratedWidget widget,
-                  const base::android::JavaRef<jobject>& j_surface,
+    SurfaceRecord(gl::ScopedJavaSurface surface,
                   bool can_be_used_with_surface_control);
-#else   // BUILDFLAG(IS_ANDROID)
-    explicit SurfaceRecord(gfx::AcceleratedWidget widget);
-#endif  // !BUILDFLAG(IS_ANDROID)
 
     SurfaceRecord(SurfaceRecord&&);
     SurfaceRecord(const SurfaceRecord&) = delete;
 
-    // TODO(crbug.com/1399516): Make this non-android.
-    gfx::AcceleratedWidget widget;
-#if BUILDFLAG(IS_ANDROID)
     gl::ScopedJavaSurface surface;
     bool can_be_used_with_surface_control;
-#endif
   };
 
-#if BUILDFLAG(IS_ANDROID)
   gl::ScopedJavaSurface AcquireJavaSurface(
       gpu::SurfaceHandle surface_handle,
       bool* can_be_used_with_surface_control) override;
-#endif
 
   // Gets the global instance of the surface tracker.
   static GpuSurfaceTracker* Get() { return GetInstance(); }
diff --git a/gpu/ipc/service/gpu_memory_buffer_factory_native_pixmap.cc b/gpu/ipc/service/gpu_memory_buffer_factory_native_pixmap.cc
index 84dd4215..a581c3de 100644
--- a/gpu/ipc/service/gpu_memory_buffer_factory_native_pixmap.cc
+++ b/gpu/ipc/service/gpu_memory_buffer_factory_native_pixmap.cc
@@ -116,8 +116,8 @@
                << gfx::BufferUsageToString(usage);
     return nullptr;
   }
-  auto image = base::MakeRefCounted<gl::GLImageNativePixmap>(size, format);
-  if (!image->Initialize(std::move(pixmap))) {
+  auto image = gl::GLImageNativePixmap::Create(size, format, std::move(pixmap));
+  if (!image) {
     LOG(ERROR) << "Failed to create GLImage " << size.ToString() << ", "
                << gfx::BufferFormatToString(format) << ", usage "
                << gfx::BufferUsageToString(usage);
diff --git a/headless/BUILD.gn b/headless/BUILD.gn
index c227453..2225f2e 100644
--- a/headless/BUILD.gn
+++ b/headless/BUILD.gn
@@ -311,7 +311,6 @@
     "public/headless_browser.cc",
     "public/headless_browser.h",
     "public/headless_export.h",
-    "public/internal/headless_devtools_client_impl.h",
     "public/internal/message_dispatcher.h",
     "public/internal/value_conversions.h",
     "public/util/error_reporter.cc",
@@ -386,7 +385,6 @@
     "lib/browser/headless_devtools.h",
     "lib/browser/headless_devtools_agent_host_client.cc",
     "lib/browser/headless_devtools_agent_host_client.h",
-    "lib/browser/headless_devtools_client_impl.cc",
     "lib/browser/headless_devtools_manager_delegate.cc",
     "lib/browser/headless_devtools_manager_delegate.h",
     "lib/browser/headless_permission_manager.cc",
@@ -715,7 +713,6 @@
 
   deps = [
     ":headless_browsertests",
-    ":headless_example",
     ":headless_unittests",
   ]
 }
@@ -1062,22 +1059,3 @@
   ]
   output = "$target_gen_dir/public/version.h"
 }
-
-executable("headless_example") {
-  sources = [ "app/headless_example.cc" ]
-  defines = []
-
-  deps = [
-    ":headless_shell_lib",
-    "//content",
-    "//sandbox",
-    "//skia",  # we need this to override font render hinting in headless build
-    "//ui/gfx/geometry",
-  ]
-
-  if (is_win) {
-    deps += [ "//content/public/app" ]
-  }
-
-  configs += [ ":headless_defines_config" ]
-}
diff --git a/headless/app/headless_example.cc b/headless/app/headless_example.cc
deleted file mode 100644
index 3a9ea309..0000000
--- a/headless/app/headless_example.cc
+++ /dev/null
@@ -1,202 +0,0 @@
-// Copyright 2017 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// A small example application showing the use of the C++ Headless Chrome
-// library. It navigates to a web site given on the command line, waits for it
-// to load and prints out the DOM.
-//
-// Tip: start reading from the main() function below.
-
-#include "base/bind.h"
-#include "base/command_line.h"
-#include "base/memory/raw_ptr.h"
-#include "base/memory/weak_ptr.h"
-#include "build/build_config.h"
-#include "headless/public/devtools/domains/page.h"
-#include "headless/public/devtools/domains/runtime.h"
-#include "headless/public/headless_browser.h"
-#include "headless/public/headless_devtools_client.h"
-#include "headless/public/headless_devtools_target.h"
-#include "headless/public/headless_web_contents.h"
-#include "ui/gfx/geometry/size.h"
-
-#if BUILDFLAG(IS_WIN)
-#include "base/strings/utf_string_conversions.h"
-#include "content/public/app/sandbox_helper_win.h"
-#include "sandbox/win/src/sandbox_types.h"
-#endif
-
-// This class contains the main application logic, i.e., waiting for a page to
-// load and printing its DOM. Note that browser initialization happens outside
-// this class.
-class HeadlessExample : public headless::HeadlessWebContents::Observer,
-                        public headless::page::Observer {
- public:
-  HeadlessExample(headless::HeadlessBrowser* browser,
-                  headless::HeadlessWebContents* web_contents);
-  ~HeadlessExample() override;
-
-  // headless::HeadlessWebContents::Observer implementation:
-  void DevToolsTargetReady() override;
-
-  // headless::page::Observer implementation:
-  void OnLoadEventFired(
-      const headless::page::LoadEventFiredParams& params) override;
-
-  // Tip: Observe headless::inspector::ExperimentalObserver::OnTargetCrashed to
-  // be notified of renderer crashes.
-
-  void OnDomFetched(std::unique_ptr<headless::runtime::EvaluateResult> result);
-
- private:
-  // The headless browser instance. Owned by the headless library. See main().
-  raw_ptr<headless::HeadlessBrowser> browser_;
-  // Our tab. Owned by |browser_|.
-  raw_ptr<headless::HeadlessWebContents> web_contents_;
-  // The DevTools client used to control the tab.
-  std::unique_ptr<headless::HeadlessDevToolsClient> devtools_client_;
-  // A helper for creating weak pointers to this class.
-  base::WeakPtrFactory<HeadlessExample> weak_factory_{this};
-};
-
-namespace {
-HeadlessExample* g_example;
-}
-
-HeadlessExample::HeadlessExample(headless::HeadlessBrowser* browser,
-                                 headless::HeadlessWebContents* web_contents)
-    : browser_(browser),
-      web_contents_(web_contents),
-      devtools_client_(headless::HeadlessDevToolsClient::Create()) {
-  web_contents_->AddObserver(this);
-}
-
-HeadlessExample::~HeadlessExample() {
-  // Note that we shut down the browser last, because it owns objects such as
-  // the web contents which can no longer be accessed after the browser is gone.
-  devtools_client_->GetPage()->RemoveObserver(this);
-  web_contents_->GetDevToolsTarget()->DetachClient(devtools_client_.get());
-  web_contents_->RemoveObserver(this);
-  browser_->Shutdown();
-}
-
-// This method is called when the tab is ready for DevTools inspection.
-void HeadlessExample::DevToolsTargetReady() {
-  // Attach our DevTools client to the tab so that we can send commands to it
-  // and observe events.
-  web_contents_->GetDevToolsTarget()->AttachClient(devtools_client_.get());
-
-  // Start observing events from DevTools's page domain. This lets us get
-  // notified when the page has finished loading. Note that it is possible
-  // the page has already finished loading by now. See
-  // HeadlessShell::DevToolTargetReady for how to handle that case correctly.
-  devtools_client_->GetPage()->AddObserver(this);
-  devtools_client_->GetPage()->Enable();
-}
-
-void HeadlessExample::OnLoadEventFired(
-    const headless::page::LoadEventFiredParams& params) {
-  // The page has now finished loading. Let's grab a snapshot of the DOM by
-  // evaluating the innerHTML property on the document element.
-  devtools_client_->GetRuntime()->Evaluate(
-      "(document.doctype ? new "
-      "XMLSerializer().serializeToString(document.doctype) + '\\n' : '') + "
-      "document.documentElement.outerHTML",
-      base::BindOnce(&HeadlessExample::OnDomFetched,
-                     weak_factory_.GetWeakPtr()));
-}
-
-void HeadlessExample::OnDomFetched(
-    std::unique_ptr<headless::runtime::EvaluateResult> result) {
-  // Make sure the evaluation succeeded before reading the result.
-  if (result->HasExceptionDetails()) {
-    LOG(ERROR) << "Failed to serialize document: "
-               << result->GetExceptionDetails()->GetText();
-  } else {
-    printf("%s\n", result->GetResult()->GetValue()->GetString().c_str());
-  }
-
-  // Shut down the browser (see ~HeadlessExample).
-  delete g_example;
-  g_example = nullptr;
-}
-
-// This function is called by the headless library after the browser has been
-// initialized. It runs on the UI thread.
-void OnHeadlessBrowserStarted(headless::HeadlessBrowser* browser) {
-  // In order to open tabs, we first need a browser context. It corresponds to a
-  // user profile and contains things like the user's cookies, local storage,
-  // cache, etc.
-  headless::HeadlessBrowserContext::Builder context_builder =
-      browser->CreateBrowserContextBuilder();
-
-  // Here we can set options for the browser context. As an example we enable
-  // incognito mode, which makes sure profile data is not written to disk.
-  context_builder.SetIncognitoMode(true);
-
-  // Construct the context and set it as the default. The default browser
-  // context is used by the Target.createTarget() DevTools command when no other
-  // context is given.
-  headless::HeadlessBrowserContext* browser_context = context_builder.Build();
-  browser->SetDefaultBrowserContext(browser_context);
-
-  // Get the URL from the command line.
-  base::CommandLine::StringVector args =
-      base::CommandLine::ForCurrentProcess()->GetArgs();
-  if (args.empty()) {
-    LOG(ERROR) << "No URL to load";
-    browser->Shutdown();
-    return;
-  }
-#if BUILDFLAG(IS_WIN)
-  GURL url(base::WideToUTF16(args[0]));
-#else
-  GURL url(args[0]);
-#endif
-
-  // Open a tab (i.e., HeadlessWebContents) in the newly created browser
-  // context.
-  headless::HeadlessWebContents::Builder tab_builder(
-      browser_context->CreateWebContentsBuilder());
-
-  // We can set options for the opened tab here. In this example we are just
-  // setting the initial URL to navigate to.
-  tab_builder.SetInitialURL(url);
-
-  // Create an instance of the example app, which will wait for the page to load
-  // and print its DOM.
-  headless::HeadlessWebContents* web_contents = tab_builder.Build();
-  g_example = new HeadlessExample(browser, web_contents);
-}
-
-int main(int argc, const char** argv) {
-#if !BUILDFLAG(IS_WIN)
-  // This function must be the first thing we call to make sure child processes
-  // such as the renderer are started properly. The headless library starts
-  // child processes by forking and exec'ing the main application.
-  headless::RunChildProcessIfNeeded(argc, argv);
-#endif
-
-  // Create a headless browser instance. There can be one of these per process
-  // and it can only be initialized once.
-  headless::HeadlessBrowser::Options::Builder builder(argc, argv);
-
-#if BUILDFLAG(IS_WIN)
-  // In windows, you must initialize and set the sandbox, or pass it along
-  // if it has already been initialized.
-  sandbox::SandboxInterfaceInfo sandbox_info = {nullptr};
-  content::InitializeSandboxInfo(&sandbox_info);
-  builder.SetSandboxInfo(&sandbox_info);
-#endif
-  // Here you can customize browser options. As an example we set the window
-  // size.
-  builder.SetWindowSize(gfx::Size(800, 600));
-
-  // Pass control to the headless library. It will bring up the browser and
-  // invoke the given callback on the browser UI thread. Note: if you need to
-  // pass more parameters to the callback, you can add them to the Bind() call
-  // below.
-  return headless::HeadlessBrowserMain(
-      builder.Build(), base::BindOnce(&OnHeadlessBrowserStarted));
-}
diff --git a/headless/lib/browser/headless_devtools_client_impl.cc b/headless/lib/browser/headless_devtools_client_impl.cc
deleted file mode 100644
index b1902338a..0000000
--- a/headless/lib/browser/headless_devtools_client_impl.cc
+++ /dev/null
@@ -1,524 +0,0 @@
-// Copyright 2016 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "headless/public/internal/headless_devtools_client_impl.h"
-
-#include <memory>
-
-#include "base/bind.h"
-#include "base/json/json_reader.h"
-#include "base/json/json_writer.h"
-#include "base/logging.h"
-#include "base/memory/ptr_util.h"
-#include "base/values.h"
-#include "content/public/browser/browser_task_traits.h"
-#include "content/public/browser/browser_thread.h"
-#include "headless/public/headless_devtools_target.h"
-
-namespace headless {
-
-namespace {
-int g_next_message_id = 0;
-int g_next_raw_message_id = 1;
-}  // namespace
-
-// static
-std::unique_ptr<HeadlessDevToolsClient> HeadlessDevToolsClient::Create() {
-  auto result = std::make_unique<HeadlessDevToolsClientImpl>();
-  result->InitBrowserMainThread();
-  return result;
-}
-
-// static
-std::unique_ptr<HeadlessDevToolsClient>
-HeadlessDevToolsClient::CreateWithExternalHost(ExternalHost* external_host) {
-  auto result = std::make_unique<HeadlessDevToolsClientImpl>();
-  result->AttachToExternalHost(external_host);
-  return result;
-}
-
-HeadlessDevToolsClientImpl::HeadlessDevToolsClientImpl()
-    : accessibility_domain_(this),
-      animation_domain_(this),
-      browser_domain_(this),
-      cache_storage_domain_(this),
-      console_domain_(this),
-      css_domain_(this),
-      database_domain_(this),
-      debugger_domain_(this),
-      device_orientation_domain_(this),
-      dom_domain_(this),
-      dom_debugger_domain_(this),
-      dom_snapshot_domain_(this),
-      dom_storage_domain_(this),
-      emulation_domain_(this),
-      fetch_domain_(this),
-      headless_experimental_domain_(this),
-      heap_profiler_domain_(this),
-      indexeddb_domain_(this),
-      input_domain_(this),
-      inspector_domain_(this),
-      io_domain_(this),
-      layer_tree_domain_(this),
-      log_domain_(this),
-      memory_domain_(this),
-      network_domain_(this),
-      page_domain_(this),
-      performance_domain_(this),
-      profiler_domain_(this),
-      runtime_domain_(this),
-      security_domain_(this),
-      service_worker_domain_(this),
-      target_domain_(this),
-      tracing_domain_(this) {}
-
-HeadlessDevToolsClientImpl::~HeadlessDevToolsClientImpl() {
-  if (parent_client_)
-    parent_client_->sessions_.erase(session_id_);
-}
-
-void HeadlessDevToolsClientImpl::AttachToExternalHost(
-    ExternalHost* external_host) {
-  DCHECK(!channel_ && !external_host_);
-  external_host_ = external_host;
-}
-
-void HeadlessDevToolsClientImpl::InitBrowserMainThread() {
-  browser_main_thread_ = content::GetUIThreadTaskRunner({});
-}
-
-void HeadlessDevToolsClientImpl::ChannelClosed() {
-  pending_messages_.clear();
-  channel_ = nullptr;
-}
-
-void HeadlessDevToolsClientImpl::AttachToChannel(
-    std::unique_ptr<HeadlessDevToolsChannel> channel) {
-  DCHECK(!channel_ && !external_host_);
-  channel_ = std::move(channel);
-  channel_->SetClient(this);
-}
-
-void HeadlessDevToolsClientImpl::DetachFromChannel() {
-  pending_messages_.clear();
-  channel_ = nullptr;
-}
-
-void HeadlessDevToolsClientImpl::SetRawProtocolListener(
-    RawProtocolListener* raw_protocol_listener) {
-  raw_protocol_listener_ = raw_protocol_listener;
-}
-
-std::unique_ptr<HeadlessDevToolsClient>
-HeadlessDevToolsClientImpl::CreateSession(const std::string& session_id) {
-  std::unique_ptr<HeadlessDevToolsClientImpl> client =
-      std::make_unique<HeadlessDevToolsClientImpl>();
-  client->parent_client_ = this;
-  client->session_id_ = session_id;
-  client->browser_main_thread_ = browser_main_thread_;
-  sessions_[session_id] = client.get();
-  return client;
-}
-
-int HeadlessDevToolsClientImpl::GetNextRawDevToolsMessageId() {
-  int id = g_next_raw_message_id;
-  g_next_raw_message_id += 2;
-  return id;
-}
-
-void HeadlessDevToolsClientImpl::SendRawDevToolsMessage(
-    const std::string& json_message) {
-  std::unique_ptr<base::Value> message =
-      base::JSONReader::ReadDeprecated(json_message);
-  if (!message || !message->is_dict()) {
-    LOG(ERROR) << "Malformed raw message";
-    return;
-  }
-  if (!session_id_.empty())
-    message->GetDict().Set("sessionId", session_id_);
-  SendProtocolMessage(message->GetDict());
-}
-
-void HeadlessDevToolsClientImpl::DispatchMessageFromExternalHost(
-    base::span<const uint8_t> json_message) {
-  DCHECK(external_host_);
-  ReceiveProtocolMessage(json_message);
-}
-
-void HeadlessDevToolsClientImpl::ReceiveProtocolMessage(
-    base::span<const uint8_t> json_message) {
-  base::StringPiece message_str(
-      reinterpret_cast<const char*>(json_message.data()), json_message.size());
-  // LOG(ERROR) << "[RECV] " << message_str;
-  std::unique_ptr<base::Value> message =
-      base::JSONReader::ReadDeprecated(message_str, base::JSON_PARSE_RFC);
-  if (!message || !message->is_dict()) {
-    NOTREACHED() << "Badly formed reply " << message_str;
-    return;
-  }
-  std::unique_ptr<base::DictionaryValue> message_dict =
-      base::DictionaryValue::From(std::move(message));
-
-  const std::string* session_id = message_dict->FindStringKey("sessionId");
-  if (session_id) {
-    auto it = sessions_.find(*session_id);
-    if (it != sessions_.end()) {
-      it->second->ReceiveProtocolMessage(json_message, std::move(message_dict));
-      return;
-    }
-  }
-  ReceiveProtocolMessage(json_message, std::move(message_dict));
-}
-
-void HeadlessDevToolsClientImpl::ReceiveProtocolMessage(
-    base::span<const uint8_t> json_message,
-    std::unique_ptr<base::DictionaryValue> message) {
-  base::StringPiece message_str(
-      reinterpret_cast<const char*>(json_message.data()), json_message.size());
-  const base::DictionaryValue* message_dict;
-  if (!message || !message->GetAsDictionary(&message_dict)) {
-    NOTREACHED() << "Badly formed reply " << message_str;
-    return;
-  }
-
-  if (raw_protocol_listener_ &&
-      raw_protocol_listener_->OnProtocolMessage(json_message, *message_dict)) {
-    return;
-  }
-
-  bool success = false;
-  if (message_dict->FindKey("id"))
-    success = DispatchMessageReply(std::move(message), *message_dict);
-  else
-    success = DispatchEvent(std::move(message), *message_dict);
-  if (!success)
-    DLOG(ERROR) << "Unhandled protocol message: " << message_str;
-}
-
-bool HeadlessDevToolsClientImpl::DispatchMessageReply(
-    std::unique_ptr<base::Value> owning_message,
-    const base::DictionaryValue& message_dict) {
-  const base::Value* id_value = message_dict.FindKey("id");
-  if (!id_value) {
-    NOTREACHED() << "ID must be specified.";
-    return false;
-  }
-  auto it = pending_messages_.find(id_value->GetInt());
-  if (it == pending_messages_.end()) {
-    NOTREACHED() << "Unexpected reply";
-    return false;
-  }
-  Callback callback = std::move(it->second);
-  pending_messages_.erase(it);
-  if (!callback.callback_with_result.is_null()) {
-    const base::DictionaryValue* result_dict;
-    if (message_dict.GetDictionary("result", &result_dict)) {
-      if (browser_main_thread_) {
-        browser_main_thread_->PostTask(
-            FROM_HERE,
-            base::BindOnce(
-                &HeadlessDevToolsClientImpl::DispatchMessageReplyWithResultTask,
-                weak_ptr_factory_.GetWeakPtr(), std::move(owning_message),
-                std::move(callback.callback_with_result), result_dict));
-      } else {
-        std::move(callback.callback_with_result).Run(*result_dict);
-      }
-    } else if (message_dict.GetDictionary("error", &result_dict)) {
-      auto null_value = std::make_unique<base::Value>();
-      base::Value* null_value_ptr = null_value.get();
-      DLOG(ERROR) << "Error in method call result: " << *result_dict;
-      if (browser_main_thread_) {
-        browser_main_thread_->PostTask(
-            FROM_HERE,
-            base::BindOnce(
-                &HeadlessDevToolsClientImpl::DispatchMessageReplyWithResultTask,
-                weak_ptr_factory_.GetWeakPtr(), std::move(null_value),
-                std::move(callback.callback_with_result), null_value_ptr));
-      } else {
-        std::move(callback.callback_with_result).Run(*null_value);
-      }
-    } else {
-      NOTREACHED() << "Reply has neither result nor error";
-      return false;
-    }
-  } else if (!callback.callback.is_null()) {
-    if (browser_main_thread_) {
-      browser_main_thread_->PostTask(
-          FROM_HERE,
-          base::BindOnce(
-              [](base::WeakPtr<HeadlessDevToolsClientImpl> self,
-                 base::OnceClosure callback) {
-                if (self)
-                  std::move(callback).Run();
-              },
-              weak_ptr_factory_.GetWeakPtr(), std::move(callback.callback)));
-    } else {
-      std::move(callback.callback).Run();
-    }
-  }
-  return true;
-}
-
-void HeadlessDevToolsClientImpl::DispatchMessageReplyWithResultTask(
-    std::unique_ptr<base::Value> owning_message,
-    base::OnceCallback<void(const base::Value&)> callback,
-    const base::Value* result_dict) {
-  std::move(callback).Run(*result_dict);
-}
-
-bool HeadlessDevToolsClientImpl::DispatchEvent(
-    std::unique_ptr<base::Value> owning_message,
-    const base::DictionaryValue& message_dict) {
-  const base::Value* method_value = message_dict.FindKey("method");
-  if (!method_value)
-    return false;
-  const std::string& method = method_value->GetString();
-  if (method == "Inspector.targetCrashed")
-    renderer_crashed_ = true;
-  EventHandlerMap::const_iterator it = event_handlers_.find(method);
-  if (it == event_handlers_.end()) {
-    if (method != "Inspector.targetCrashed")
-      NOTREACHED() << "Unknown event: " << method;
-    return false;
-  }
-  if (!it->second.is_null()) {
-    const base::DictionaryValue* result_dict;
-    if (!message_dict.GetDictionary("params", &result_dict)) {
-      NOTREACHED() << "Badly formed event parameters";
-      return false;
-    }
-    if (browser_main_thread_) {
-      // DevTools assumes event handling is async so we must post a task here or
-      // we risk breaking things.
-      browser_main_thread_->PostTask(
-          FROM_HERE,
-          base::BindOnce(&HeadlessDevToolsClientImpl::DispatchEventTask,
-                         weak_ptr_factory_.GetWeakPtr(),
-                         std::move(owning_message), &it->second, result_dict));
-    } else {
-      DispatchEventTask(std::move(owning_message), &it->second, result_dict);
-    }
-  }
-  return true;
-}
-
-void HeadlessDevToolsClientImpl::DispatchEventTask(
-    std::unique_ptr<base::Value> owning_message,
-    const EventHandler* event_handler,
-    const base::DictionaryValue* result_dict) {
-  event_handler->Run(*result_dict);
-}
-
-accessibility::Domain* HeadlessDevToolsClientImpl::GetAccessibility() {
-  return &accessibility_domain_;
-}
-
-animation::Domain* HeadlessDevToolsClientImpl::GetAnimation() {
-  return &animation_domain_;
-}
-
-browser::Domain* HeadlessDevToolsClientImpl::GetBrowser() {
-  return &browser_domain_;
-}
-
-cache_storage::Domain* HeadlessDevToolsClientImpl::GetCacheStorage() {
-  return &cache_storage_domain_;
-}
-
-console::Domain* HeadlessDevToolsClientImpl::GetConsole() {
-  return &console_domain_;
-}
-
-css::Domain* HeadlessDevToolsClientImpl::GetCSS() {
-  return &css_domain_;
-}
-
-database::Domain* HeadlessDevToolsClientImpl::GetDatabase() {
-  return &database_domain_;
-}
-
-debugger::Domain* HeadlessDevToolsClientImpl::GetDebugger() {
-  return &debugger_domain_;
-}
-
-device_orientation::Domain* HeadlessDevToolsClientImpl::GetDeviceOrientation() {
-  return &device_orientation_domain_;
-}
-
-dom::Domain* HeadlessDevToolsClientImpl::GetDOM() {
-  return &dom_domain_;
-}
-
-dom_debugger::Domain* HeadlessDevToolsClientImpl::GetDOMDebugger() {
-  return &dom_debugger_domain_;
-}
-
-dom_snapshot::Domain* HeadlessDevToolsClientImpl::GetDOMSnapshot() {
-  return &dom_snapshot_domain_;
-}
-
-dom_storage::Domain* HeadlessDevToolsClientImpl::GetDOMStorage() {
-  return &dom_storage_domain_;
-}
-
-emulation::Domain* HeadlessDevToolsClientImpl::GetEmulation() {
-  return &emulation_domain_;
-}
-
-fetch::Domain* HeadlessDevToolsClientImpl::GetFetch() {
-  return &fetch_domain_;
-}
-
-headless_experimental::Domain*
-HeadlessDevToolsClientImpl::GetHeadlessExperimental() {
-  return &headless_experimental_domain_;
-}
-
-heap_profiler::Domain* HeadlessDevToolsClientImpl::GetHeapProfiler() {
-  return &heap_profiler_domain_;
-}
-
-indexeddb::Domain* HeadlessDevToolsClientImpl::GetIndexedDB() {
-  return &indexeddb_domain_;
-}
-
-input::Domain* HeadlessDevToolsClientImpl::GetInput() {
-  return &input_domain_;
-}
-
-inspector::Domain* HeadlessDevToolsClientImpl::GetInspector() {
-  return &inspector_domain_;
-}
-
-io::Domain* HeadlessDevToolsClientImpl::GetIO() {
-  return &io_domain_;
-}
-
-layer_tree::Domain* HeadlessDevToolsClientImpl::GetLayerTree() {
-  return &layer_tree_domain_;
-}
-
-log::Domain* HeadlessDevToolsClientImpl::GetLog() {
-  return &log_domain_;
-}
-
-memory::Domain* HeadlessDevToolsClientImpl::GetMemory() {
-  return &memory_domain_;
-}
-
-network::Domain* HeadlessDevToolsClientImpl::GetNetwork() {
-  return &network_domain_;
-}
-
-page::Domain* HeadlessDevToolsClientImpl::GetPage() {
-  return &page_domain_;
-}
-
-performance::Domain* HeadlessDevToolsClientImpl::GetPerformance() {
-  return &performance_domain_;
-}
-
-profiler::Domain* HeadlessDevToolsClientImpl::GetProfiler() {
-  return &profiler_domain_;
-}
-
-runtime::Domain* HeadlessDevToolsClientImpl::GetRuntime() {
-  return &runtime_domain_;
-}
-
-security::Domain* HeadlessDevToolsClientImpl::GetSecurity() {
-  return &security_domain_;
-}
-
-service_worker::Domain* HeadlessDevToolsClientImpl::GetServiceWorker() {
-  return &service_worker_domain_;
-}
-
-target::Domain* HeadlessDevToolsClientImpl::GetTarget() {
-  return &target_domain_;
-}
-
-tracing::Domain* HeadlessDevToolsClientImpl::GetTracing() {
-  return &tracing_domain_;
-}
-
-template <typename CallbackType>
-void HeadlessDevToolsClientImpl::FinalizeAndSendMessage(
-    base::Value::Dict message,
-    CallbackType callback) {
-  if (renderer_crashed_)
-    return;
-  int id = g_next_message_id;
-  g_next_message_id += 2;  // We only send even numbered messages.
-  message.Set("id", id);
-  if (!session_id_.empty())
-    message.Set("sessionId", session_id_);
-  pending_messages_[id] = Callback(std::move(callback));
-  SendProtocolMessage(message);
-}
-
-void HeadlessDevToolsClientImpl::SendProtocolMessage(
-    const base::Value::Dict& message) {
-  if (parent_client_) {
-    parent_client_->SendProtocolMessage(message);
-    return;
-  }
-
-  std::string json_message;
-  base::JSONWriter::Write(message, &json_message);
-  // LOG(ERROR) << "[SEND] " << json_message;
-  auto bytes_message = base::as_bytes(base::make_span(json_message));
-  if (channel_)
-    channel_->SendProtocolMessage(bytes_message);
-  else
-    external_host_->SendProtocolMessage(bytes_message);
-}
-
-template <typename CallbackType>
-void HeadlessDevToolsClientImpl::SendMessageWithParams(const char* method,
-                                                       base::Value params,
-                                                       CallbackType callback) {
-  base::Value::Dict message;
-  message.Set("method", method);
-  message.Set("params", std::move(params));
-  FinalizeAndSendMessage(std::move(message), std::move(callback));
-}
-
-void HeadlessDevToolsClientImpl::SendMessage(
-    const char* method,
-    base::Value params,
-    base::OnceCallback<void(const base::Value&)> callback) {
-  SendMessageWithParams(method, std::move(params), std::move(callback));
-}
-
-void HeadlessDevToolsClientImpl::SendMessage(const char* method,
-                                             base::Value params,
-                                             base::OnceClosure callback) {
-  SendMessageWithParams(method, std::move(params), std::move(callback));
-}
-
-void HeadlessDevToolsClientImpl::RegisterEventHandler(
-    const char* method,
-    base::RepeatingCallback<void(const base::Value&)> callback) {
-  DCHECK(event_handlers_.find(method) == event_handlers_.end());
-  event_handlers_[method] = std::move(callback);
-}
-
-HeadlessDevToolsClientImpl::Callback::Callback() = default;
-
-HeadlessDevToolsClientImpl::Callback::Callback(Callback&& other) = default;
-
-HeadlessDevToolsClientImpl::Callback::Callback(base::OnceClosure callback)
-    : callback(std::move(callback)) {}
-
-HeadlessDevToolsClientImpl::Callback::Callback(
-    base::OnceCallback<void(const base::Value&)> callback)
-    : callback_with_result(std::move(callback)) {}
-
-HeadlessDevToolsClientImpl::Callback::~Callback() = default;
-
-HeadlessDevToolsClientImpl::Callback&
-HeadlessDevToolsClientImpl::Callback::operator=(Callback&& other) = default;
-
-}  // namespace headless
diff --git a/headless/public/internal/headless_devtools_client_impl.h b/headless/public/internal/headless_devtools_client_impl.h
deleted file mode 100644
index 56bfd36..0000000
--- a/headless/public/internal/headless_devtools_client_impl.h
+++ /dev/null
@@ -1,235 +0,0 @@
-// Copyright 2016 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef HEADLESS_PUBLIC_INTERNAL_HEADLESS_DEVTOOLS_CLIENT_IMPL_H_
-#define HEADLESS_PUBLIC_INTERNAL_HEADLESS_DEVTOOLS_CLIENT_IMPL_H_
-
-#include <unordered_map>
-
-#include "base/containers/flat_map.h"
-#include "base/containers/span.h"
-#include "base/memory/raw_ptr.h"
-#include "base/memory/weak_ptr.h"
-#include "base/task/sequenced_task_runner.h"
-#include "headless/public/devtools/domains/accessibility.h"
-#include "headless/public/devtools/domains/animation.h"
-#include "headless/public/devtools/domains/browser.h"
-#include "headless/public/devtools/domains/cache_storage.h"
-#include "headless/public/devtools/domains/console.h"
-#include "headless/public/devtools/domains/css.h"
-#include "headless/public/devtools/domains/database.h"
-#include "headless/public/devtools/domains/debugger.h"
-#include "headless/public/devtools/domains/device_orientation.h"
-#include "headless/public/devtools/domains/dom.h"
-#include "headless/public/devtools/domains/dom_debugger.h"
-#include "headless/public/devtools/domains/dom_snapshot.h"
-#include "headless/public/devtools/domains/dom_storage.h"
-#include "headless/public/devtools/domains/emulation.h"
-#include "headless/public/devtools/domains/fetch.h"
-#include "headless/public/devtools/domains/headless_experimental.h"
-#include "headless/public/devtools/domains/heap_profiler.h"
-#include "headless/public/devtools/domains/indexeddb.h"
-#include "headless/public/devtools/domains/input.h"
-#include "headless/public/devtools/domains/inspector.h"
-#include "headless/public/devtools/domains/io.h"
-#include "headless/public/devtools/domains/layer_tree.h"
-#include "headless/public/devtools/domains/log.h"
-#include "headless/public/devtools/domains/memory.h"
-#include "headless/public/devtools/domains/network.h"
-#include "headless/public/devtools/domains/page.h"
-#include "headless/public/devtools/domains/performance.h"
-#include "headless/public/devtools/domains/profiler.h"
-#include "headless/public/devtools/domains/runtime.h"
-#include "headless/public/devtools/domains/security.h"
-#include "headless/public/devtools/domains/service_worker.h"
-#include "headless/public/devtools/domains/target.h"
-#include "headless/public/devtools/domains/tracing.h"
-#include "headless/public/headless_devtools_client.h"
-#include "headless/public/headless_export.h"
-#include "headless/public/internal/message_dispatcher.h"
-
-namespace base {
-class DictionaryValue;
-}
-
-namespace headless {
-
-class HEADLESS_EXPORT HeadlessDevToolsClientImpl
-    : public HeadlessDevToolsClient,
-      public HeadlessDevToolsChannel::Client,
-      public internal::MessageDispatcher {
- public:
-  HeadlessDevToolsClientImpl();
-
-  HeadlessDevToolsClientImpl(const HeadlessDevToolsClientImpl&) = delete;
-  HeadlessDevToolsClientImpl& operator=(const HeadlessDevToolsClientImpl&) =
-      delete;
-
-  ~HeadlessDevToolsClientImpl() override;
-
-  // HeadlessDevToolsClient implementation:
-  accessibility::Domain* GetAccessibility() override;
-  animation::Domain* GetAnimation() override;
-  browser::Domain* GetBrowser() override;
-  cache_storage::Domain* GetCacheStorage() override;
-  console::Domain* GetConsole() override;
-  css::Domain* GetCSS() override;
-  database::Domain* GetDatabase() override;
-  debugger::Domain* GetDebugger() override;
-  device_orientation::Domain* GetDeviceOrientation() override;
-  dom::Domain* GetDOM() override;
-  dom_debugger::Domain* GetDOMDebugger() override;
-  dom_snapshot::Domain* GetDOMSnapshot() override;
-  dom_storage::Domain* GetDOMStorage() override;
-  emulation::Domain* GetEmulation() override;
-  fetch::Domain* GetFetch() override;
-  headless_experimental::Domain* GetHeadlessExperimental() override;
-  heap_profiler::Domain* GetHeapProfiler() override;
-  indexeddb::Domain* GetIndexedDB() override;
-  input::Domain* GetInput() override;
-  inspector::Domain* GetInspector() override;
-  io::Domain* GetIO() override;
-  layer_tree::Domain* GetLayerTree() override;
-  log::Domain* GetLog() override;
-  memory::Domain* GetMemory() override;
-  network::Domain* GetNetwork() override;
-  page::Domain* GetPage() override;
-  performance::Domain* GetPerformance() override;
-  profiler::Domain* GetProfiler() override;
-  runtime::Domain* GetRuntime() override;
-  security::Domain* GetSecurity() override;
-  service_worker::Domain* GetServiceWorker() override;
-  target::Domain* GetTarget() override;
-  tracing::Domain* GetTracing() override;
-  void SetRawProtocolListener(
-      RawProtocolListener* raw_protocol_listener) override;
-  std::unique_ptr<HeadlessDevToolsClient> CreateSession(
-      const std::string& session_id) override;
-  int GetNextRawDevToolsMessageId() override;
-  void SendRawDevToolsMessage(const std::string& json_message) override;
-  void DispatchMessageFromExternalHost(
-      base::span<const uint8_t> json_message) override;
-  void AttachToChannel(
-      std::unique_ptr<HeadlessDevToolsChannel> channel) override;
-  void DetachFromChannel() override;
-
-  // HeadlessDevToolsChannel::Client implementation.
-  void ReceiveProtocolMessage(base::span<const uint8_t> message) override;
-  void ChannelClosed() override;
-
-  // internal::MessageDispatcher implementation:
-  void SendMessage(
-      const char* method,
-      base::Value params,
-      base::OnceCallback<void(const base::Value&)> callback) override;
-  void SendMessage(const char* method,
-                   base::Value params,
-                   base::OnceClosure callback) override;
-  void RegisterEventHandler(
-      const char* method,
-      base::RepeatingCallback<void(const base::Value&)> callback) override;
-
-  // TODO(dgozman): remove with ExternalHost.
-  void AttachToExternalHost(ExternalHost* external_host);
-  void InitBrowserMainThread();
-
-  void SetTaskRunnerForTests(
-      scoped_refptr<base::SequencedTaskRunner> task_runner) {
-    browser_main_thread_ = task_runner;
-  }
-
- private:
-  // Contains a callback with or without a result parameter depending on the
-  // message type.
-  struct Callback {
-    Callback();
-    Callback(Callback&& other);
-    explicit Callback(base::OnceClosure callback);
-    explicit Callback(base::OnceCallback<void(const base::Value&)> callback);
-    ~Callback();
-
-    Callback& operator=(Callback&& other);
-
-    base::OnceClosure callback;
-    base::OnceCallback<void(const base::Value&)> callback_with_result;
-  };
-
-  template <typename CallbackType>
-  void FinalizeAndSendMessage(base::Value::Dict message, CallbackType callback);
-
-  template <typename CallbackType>
-  void SendMessageWithParams(const char* method,
-                             base::Value params,
-                             CallbackType callback);
-
-  bool DispatchMessageReply(std::unique_ptr<base::Value> owning_message,
-                            const base::DictionaryValue& message_dict);
-  void DispatchMessageReplyWithResultTask(
-      std::unique_ptr<base::Value> owning_message,
-      base::OnceCallback<void(const base::Value&)> callback,
-      const base::Value* result_dict);
-  using EventHandler = base::RepeatingCallback<void(const base::Value&)>;
-  using EventHandlerMap = std::unordered_map<std::string, EventHandler>;
-
-  bool DispatchEvent(std::unique_ptr<base::Value> owning_message,
-                     const base::DictionaryValue& message_dict);
-  void DispatchEventTask(std::unique_ptr<base::Value> owning_message,
-                         const EventHandler* event_handler,
-                         const base::DictionaryValue* result_dict);
-
-  void ReceiveProtocolMessage(base::span<const uint8_t> json_message,
-                              std::unique_ptr<base::DictionaryValue> message);
-  void SendProtocolMessage(const base::Value::Dict& message);
-
-  std::unique_ptr<HeadlessDevToolsChannel> channel_;
-  raw_ptr<ExternalHost> external_host_ = nullptr;
-  raw_ptr<RawProtocolListener> raw_protocol_listener_ = nullptr;
-
-  std::unordered_map<int, Callback> pending_messages_;
-  EventHandlerMap event_handlers_;
-  std::string session_id_;
-  raw_ptr<HeadlessDevToolsClientImpl> parent_client_ = nullptr;
-  base::flat_map<std::string, HeadlessDevToolsClientImpl*> sessions_;
-  bool renderer_crashed_ = false;
-
-  accessibility::ExperimentalDomain accessibility_domain_;
-  animation::ExperimentalDomain animation_domain_;
-  browser::ExperimentalDomain browser_domain_;
-  cache_storage::ExperimentalDomain cache_storage_domain_;
-  console::ExperimentalDomain console_domain_;
-  css::ExperimentalDomain css_domain_;
-  database::ExperimentalDomain database_domain_;
-  debugger::ExperimentalDomain debugger_domain_;
-  device_orientation::ExperimentalDomain device_orientation_domain_;
-  dom::ExperimentalDomain dom_domain_;
-  dom_debugger::ExperimentalDomain dom_debugger_domain_;
-  dom_snapshot::ExperimentalDomain dom_snapshot_domain_;
-  dom_storage::ExperimentalDomain dom_storage_domain_;
-  emulation::ExperimentalDomain emulation_domain_;
-  fetch::ExperimentalDomain fetch_domain_;
-  headless_experimental::ExperimentalDomain headless_experimental_domain_;
-  heap_profiler::ExperimentalDomain heap_profiler_domain_;
-  indexeddb::ExperimentalDomain indexeddb_domain_;
-  input::ExperimentalDomain input_domain_;
-  inspector::ExperimentalDomain inspector_domain_;
-  io::ExperimentalDomain io_domain_;
-  layer_tree::ExperimentalDomain layer_tree_domain_;
-  log::ExperimentalDomain log_domain_;
-  memory::ExperimentalDomain memory_domain_;
-  network::ExperimentalDomain network_domain_;
-  page::ExperimentalDomain page_domain_;
-  performance::ExperimentalDomain performance_domain_;
-  profiler::ExperimentalDomain profiler_domain_;
-  runtime::ExperimentalDomain runtime_domain_;
-  security::ExperimentalDomain security_domain_;
-  service_worker::ExperimentalDomain service_worker_domain_;
-  target::ExperimentalDomain target_domain_;
-  tracing::ExperimentalDomain tracing_domain_;
-  scoped_refptr<base::SequencedTaskRunner> browser_main_thread_;
-  base::WeakPtrFactory<HeadlessDevToolsClientImpl> weak_ptr_factory_{this};
-};
-
-}  // namespace headless
-
-#endif  // HEADLESS_PUBLIC_INTERNAL_HEADLESS_DEVTOOLS_CLIENT_IMPL_H_
diff --git a/infra/config/generated/builders/reclient/Mac Builder reclient test untrusted/properties.json b/infra/config/generated/builders/reclient/Mac Builder reclient test untrusted/properties.json
index 99652c55..67b06f7 100644
--- a/infra/config/generated/builders/reclient/Mac Builder reclient test untrusted/properties.json
+++ b/infra/config/generated/builders/reclient/Mac Builder reclient test untrusted/properties.json
@@ -46,14 +46,15 @@
   "$build/reclient": {
     "bootstrap_env": {
       "GLOG_vmodule": "bridge*=2",
-      "GOMA_COMPILER_PROXY_ENABLE_CRASH_DUMP": "false",
+      "GOMA_COMPILER_PROXY_ENABLE_CRASH_DUMP": "true",
       "RBE_deps_cache_mode": "reproxy",
       "RBE_experimental_goma_deps_cache": "True",
       "RBE_ip_reset_min_delay": "-1s"
     },
     "instance": "rbe-chromium-untrusted-test",
     "metrics_project": "chromium-reclient-metrics",
-    "profiler_service": "reclient-mac"
+    "profiler_service": "reclient-mac",
+    "scandeps_server": true
   },
   "$recipe_engine/resultdb/test_presentation": {
     "column_keys": [],
diff --git a/infra/config/generated/builders/reclient/Mac Builder reclient test/properties.json b/infra/config/generated/builders/reclient/Mac Builder reclient test/properties.json
index 3bb4e9d..40dc381 100644
--- a/infra/config/generated/builders/reclient/Mac Builder reclient test/properties.json
+++ b/infra/config/generated/builders/reclient/Mac Builder reclient test/properties.json
@@ -46,14 +46,15 @@
   "$build/reclient": {
     "bootstrap_env": {
       "GLOG_vmodule": "bridge*=2",
-      "GOMA_COMPILER_PROXY_ENABLE_CRASH_DUMP": "false",
+      "GOMA_COMPILER_PROXY_ENABLE_CRASH_DUMP": "true",
       "RBE_deps_cache_mode": "reproxy",
       "RBE_experimental_goma_deps_cache": "True",
       "RBE_ip_reset_min_delay": "-1s"
     },
     "instance": "rbe-chromium-trusted-test",
     "metrics_project": "chromium-reclient-metrics",
-    "profiler_service": "reclient-mac"
+    "profiler_service": "reclient-mac",
+    "scandeps_server": true
   },
   "$recipe_engine/resultdb/test_presentation": {
     "column_keys": [],
diff --git a/infra/config/generated/builders/reclient/ios-simulator reclient test untrusted/properties.json b/infra/config/generated/builders/reclient/ios-simulator reclient test untrusted/properties.json
index 905b8a1f..f79200e5 100644
--- a/infra/config/generated/builders/reclient/ios-simulator reclient test untrusted/properties.json
+++ b/infra/config/generated/builders/reclient/ios-simulator reclient test untrusted/properties.json
@@ -46,13 +46,14 @@
   "$build/reclient": {
     "bootstrap_env": {
       "GLOG_vmodule": "bridge*=2",
-      "GOMA_COMPILER_PROXY_ENABLE_CRASH_DUMP": "false",
+      "GOMA_COMPILER_PROXY_ENABLE_CRASH_DUMP": "true",
       "RBE_deps_cache_mode": "reproxy",
       "RBE_experimental_goma_deps_cache": "True",
       "RBE_ip_reset_min_delay": "-1s"
     },
     "instance": "rbe-chromium-untrusted-test",
-    "metrics_project": "chromium-reclient-metrics"
+    "metrics_project": "chromium-reclient-metrics",
+    "scandeps_server": true
   },
   "$recipe_engine/resultdb/test_presentation": {
     "column_keys": [],
diff --git a/infra/config/generated/builders/reclient/ios-simulator reclient test/properties.json b/infra/config/generated/builders/reclient/ios-simulator reclient test/properties.json
index 1ec5bf8..6fccaa3b 100644
--- a/infra/config/generated/builders/reclient/ios-simulator reclient test/properties.json
+++ b/infra/config/generated/builders/reclient/ios-simulator reclient test/properties.json
@@ -46,13 +46,14 @@
   "$build/reclient": {
     "bootstrap_env": {
       "GLOG_vmodule": "bridge*=2",
-      "GOMA_COMPILER_PROXY_ENABLE_CRASH_DUMP": "false",
+      "GOMA_COMPILER_PROXY_ENABLE_CRASH_DUMP": "true",
       "RBE_deps_cache_mode": "reproxy",
       "RBE_experimental_goma_deps_cache": "True",
       "RBE_ip_reset_min_delay": "-1s"
     },
     "instance": "rbe-chromium-trusted-test",
-    "metrics_project": "chromium-reclient-metrics"
+    "metrics_project": "chromium-reclient-metrics",
+    "scandeps_server": true
   },
   "$recipe_engine/resultdb/test_presentation": {
     "column_keys": [],
diff --git a/infra/config/generated/builders/reclient/mac-arm64-rel reclient test untrusted/properties.json b/infra/config/generated/builders/reclient/mac-arm64-rel reclient test untrusted/properties.json
index 6cc4d63..6a3f71d 100644
--- a/infra/config/generated/builders/reclient/mac-arm64-rel reclient test untrusted/properties.json
+++ b/infra/config/generated/builders/reclient/mac-arm64-rel reclient test untrusted/properties.json
@@ -45,13 +45,14 @@
   "$build/reclient": {
     "bootstrap_env": {
       "GLOG_vmodule": "bridge*=2",
-      "GOMA_COMPILER_PROXY_ENABLE_CRASH_DUMP": "false",
+      "GOMA_COMPILER_PROXY_ENABLE_CRASH_DUMP": "true",
       "RBE_deps_cache_mode": "reproxy",
       "RBE_experimental_goma_deps_cache": "True",
       "RBE_ip_reset_min_delay": "-1s"
     },
     "instance": "rbe-chromium-untrusted-test",
-    "metrics_project": "chromium-reclient-metrics"
+    "metrics_project": "chromium-reclient-metrics",
+    "scandeps_server": true
   },
   "$recipe_engine/resultdb/test_presentation": {
     "column_keys": [],
diff --git a/infra/config/generated/builders/reclient/mac-arm64-rel reclient test/properties.json b/infra/config/generated/builders/reclient/mac-arm64-rel reclient test/properties.json
index 111024b..4ca7f05c 100644
--- a/infra/config/generated/builders/reclient/mac-arm64-rel reclient test/properties.json
+++ b/infra/config/generated/builders/reclient/mac-arm64-rel reclient test/properties.json
@@ -45,13 +45,14 @@
   "$build/reclient": {
     "bootstrap_env": {
       "GLOG_vmodule": "bridge*=2",
-      "GOMA_COMPILER_PROXY_ENABLE_CRASH_DUMP": "false",
+      "GOMA_COMPILER_PROXY_ENABLE_CRASH_DUMP": "true",
       "RBE_deps_cache_mode": "reproxy",
       "RBE_experimental_goma_deps_cache": "True",
       "RBE_ip_reset_min_delay": "-1s"
     },
     "instance": "rbe-chromium-trusted-test",
-    "metrics_project": "chromium-reclient-metrics"
+    "metrics_project": "chromium-reclient-metrics",
+    "scandeps_server": true
   },
   "$recipe_engine/resultdb/test_presentation": {
     "column_keys": [],
diff --git a/infra/config/lib/builders.star b/infra/config/lib/builders.star
index cfdc647..52b61ed 100644
--- a/infra/config/lib/builders.star
+++ b/infra/config/lib/builders.star
@@ -289,7 +289,7 @@
 
 _VALID_REPROXY_ENV_PREFIX_LIST = ["RBE_", "GLOG_", "GOMA_"]
 
-def _reclient_property(*, instance, service, jobs, rewrapper_env, profiler_service, publish_trace, cache_silo, ensure_verified, bootstrap_env):
+def _reclient_property(*, instance, service, jobs, rewrapper_env, profiler_service, publish_trace, cache_silo, ensure_verified, bootstrap_env, scandeps_server):
     reclient = {}
     instance = defaults.get_value("reclient_instance", instance)
     if not instance:
@@ -317,6 +317,9 @@
                      ", ".join(_VALID_REPROXY_ENV_PREFIX_LIST) +
                      "), got '%s'" % k)
         reclient["bootstrap_env"] = bootstrap_env
+    scandeps_server = defaults.get_value("reclient_scandeps_server", scandeps_server)
+    if scandeps_server:
+        reclient["scandeps_server"] = scandeps_server
     profiler_service = defaults.get_value("reclient_profiler_service", profiler_service)
     if profiler_service:
         reclient["profiler_service"] = profiler_service
@@ -400,6 +403,7 @@
     reclient_bootstrap_env = None,
     reclient_profiler_service = None,
     reclient_publish_trace = None,
+    reclient_scandeps_server = False,
     reclient_cache_silo = None,
     reclient_ensure_verified = None,
 
@@ -465,6 +469,7 @@
         reclient_bootstrap_env = args.DEFAULT,
         reclient_profiler_service = args.DEFAULT,
         reclient_publish_trace = args.DEFAULT,
+        reclient_scandeps_server = args.DEFAULT,
         reclient_cache_silo = None,
         reclient_ensure_verified = None,
         omit_python2 = args.DEFAULT,
@@ -642,6 +647,7 @@
             not set.
         reclient_publish_trace: If True, it publish trace by rpl2cloudtrace. Has
             no effect if reclient_instance is not set.
+        reclient_scandeps_server: If true, reproxy should start its own scandeps_server
         reclient_cache_silo: A string indicating a cache siling key to use for
             remote caching. Has no effect if reclient_instance is not set.
         reclient_ensure_verified: If True, it verifies build artifacts. Has no
@@ -797,6 +803,7 @@
         bootstrap_env = reclient_bootstrap_env,
         profiler_service = reclient_profiler_service,
         publish_trace = reclient_publish_trace,
+        scandeps_server = reclient_scandeps_server,
         cache_silo = reclient_cache_silo,
         ensure_verified = reclient_ensure_verified,
     )
diff --git a/infra/config/subprojects/reclient/reclient.star b/infra/config/subprojects/reclient/reclient.star
index 5a95839..546abd8 100644
--- a/infra/config/subprojects/reclient/reclient.star
+++ b/infra/config/subprojects/reclient/reclient.star
@@ -60,6 +60,7 @@
             "chromium-cq-staging-builder@chops-service-accounts.iam.gserviceaccount.com"
         ),
         reclient_version = "staging",
+        enable_crash_dump = "false",
         **kwargs):
     trusted_instance = reclient_instance % "trusted"
     unstrusted_instance = reclient_instance % "untrusted"
@@ -71,7 +72,7 @@
         "RBE_ip_reset_min_delay": "-1s",
         "RBE_deps_cache_mode": "reproxy",
         # TODO(b/258210757) remove once long term breakpad plans are dertermined
-        "GOMA_COMPILER_PROXY_ENABLE_CRASH_DUMP": "false",
+        "GOMA_COMPILER_PROXY_ENABLE_CRASH_DUMP": enable_crash_dump,
     })
     return [
         ci.builder(
@@ -193,6 +194,7 @@
             build_gs_bucket = "chromium-fyi-archive",
         ),
     ),
+    enable_crash_dump = "true",
     builderless = True,
     cores = None,
     os = os.MAC_DEFAULT,
@@ -202,6 +204,7 @@
         "GLOG_vmodule": "bridge*=2",
     },
     reclient_profiler_service = "reclient-mac",
+    reclient_scandeps_server = True,
 )
 
 fyi_reclient_staging_builder(
@@ -308,12 +311,14 @@
     builderless = True,
     cores = None,
     os = os.MAC_DEFAULT,
+    enable_crash_dump = "true",
     xcode = xcode.x14main,
     console_view_category = "ios",
     priority = 35,
     reclient_bootstrap_env = {
         "GLOG_vmodule": "bridge*=2",
     },
+    reclient_scandeps_server = True,
 )
 
 fyi_reclient_staging_builder(
@@ -386,10 +391,12 @@
     cores = None,
     os = os.MAC_DEFAULT,
     console_view_category = "mac",
+    enable_crash_dump = "true",
     priority = 35,
     reclient_bootstrap_env = {
         "GLOG_vmodule": "bridge*=2",
     },
+    reclient_scandeps_server = True,
 )
 
 ci.builder(
diff --git a/ios/chrome/app/BUILD.gn b/ios/chrome/app/BUILD.gn
index f430897..25f9cfe 100644
--- a/ios/chrome/app/BUILD.gn
+++ b/ios/chrome/app/BUILD.gn
@@ -498,6 +498,7 @@
     "//ios/chrome/browser/screenshot",
     "//ios/chrome/browser/search_engines",
     "//ios/chrome/browser/search_engines:extension_search_engine_data_updater",
+    "//ios/chrome/browser/search_engines:search_engines_util",
     "//ios/chrome/browser/sessions:scene_util",
     "//ios/chrome/browser/sessions:session_service",
     "//ios/chrome/browser/share_extension",
diff --git a/ios/chrome/browser/providers/push_notification/chromium_push_notification.mm b/ios/chrome/browser/providers/push_notification/chromium_push_notification.mm
index 6428d39..86299fa3 100644
--- a/ios/chrome/browser/providers/push_notification/chromium_push_notification.mm
+++ b/ios/chrome/browser/providers/push_notification/chromium_push_notification.mm
@@ -24,6 +24,12 @@
   void RegisterDevice(PushNotificationConfiguration* config,
                       void (^completion_handler)(NSError* error)) final;
   void UnregisterDevice(void (^completion_handler)(NSError* error)) final;
+  bool DeviceTokenIsSet() const final;
+
+ protected:
+  // PushNotificationService implementation.
+  void SetAccountsToDevice(NSArray<NSString*>* account_ids,
+                           CompletionHandler completion_handler) final;
 };
 
 void ChromiumPushNotificationService::RegisterDevice(
@@ -58,6 +64,27 @@
       }));
 }
 
+bool ChromiumPushNotificationService::DeviceTokenIsSet() const {
+  return false;
+}
+
+void ChromiumPushNotificationService::SetAccountsToDevice(
+    NSArray<NSString*>* account_ids,
+    void (^completion_handler)(NSError* error)) {
+  // Chromium does not initialize the device's connection to the push
+  // notification server. As a result, the `completion_handler` is called with
+  // a NSFeatureUnsupportedError.
+
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(^() {
+        NSError* error =
+            [NSError errorWithDomain:kChromiumPushNotificationErrorDomain
+                                code:NSFeatureUnsupportedError
+                            userInfo:nil];
+        completion_handler(error);
+      }));
+}
+
 }  // namespace
 
 std::unique_ptr<PushNotificationService> CreatePushNotificationService() {
diff --git a/ios/chrome/browser/push_notification/BUILD.gn b/ios/chrome/browser/push_notification/BUILD.gn
index bc7b2aa8..20637ac 100644
--- a/ios/chrome/browser/push_notification/BUILD.gn
+++ b/ios/chrome/browser/push_notification/BUILD.gn
@@ -5,6 +5,8 @@
 source_set("push_notification_service") {
   configs += [ "//build/config/compiler:enable_arc" ]
   sources = [
+    "push_notification_account_context_manager.h",
+    "push_notification_account_context_manager.mm",
     "push_notification_client_manager.h",
     "push_notification_client_manager.mm",
     "push_notification_configuration.h",
@@ -37,3 +39,29 @@
   ]
   deps = [ "//base" ]
 }
+
+source_set("unit_tests") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  testonly = true
+  sources = [ "push_notification_client_manager_unittest.mm" ]
+  deps = [
+    ":push_notification_client",
+    ":push_notification_service",
+    ":test_support",
+    "//testing/gtest",
+  ]
+}
+
+source_set("test_support") {
+  configs += [ "//build/config/compiler:enable_arc" ]
+  sources = [
+    "test_push_notification_client.h",
+    "test_push_notification_client.mm",
+  ]
+
+  deps = [
+    ":push_notification_client",
+    ":push_notification_service",
+    "//base",
+  ]
+}
diff --git a/ios/chrome/browser/push_notification/push_notification_account_context_manager.h b/ios/chrome/browser/push_notification/push_notification_account_context_manager.h
new file mode 100644
index 0000000..397386e
--- /dev/null
+++ b/ios/chrome/browser/push_notification/push_notification_account_context_manager.h
@@ -0,0 +1,60 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_PUSH_NOTIFICATION_PUSH_NOTIFICATION_ACCOUNT_CONTEXT_MANAGER_H_
+#define IOS_CHROME_BROWSER_PUSH_NOTIFICATION_PUSH_NOTIFICATION_ACCOUNT_CONTEXT_MANAGER_H_
+
+#import <Foundation/Foundation.h>
+
+#import "ios/chrome/browser/browser_state/chrome_browser_state_manager.h"
+
+class BrowserStateInfoCache;
+
+namespace ios {
+class ChromeBrowserStateManager;
+
+}
+
+// This class is intended to store the permissions for each push notification
+// enabled feature for a given account and the number of times the account is
+// signed in across BrowserStates.
+@interface PushNotificationAccountContext : NSObject
+// A dictionary that maps the string value of a push notification client id to
+// the perf service value for that push notification enable feature.
+@property(nonatomic, copy) NSDictionary<NSString*, NSNumber*>* preferenceMap;
+// A counter that stores the number of times a given account is used across
+// BrowserStates.
+@property(nonatomic, readonly) NSUInteger occurrencesAcrossBrowserStates;
+@end
+
+// The purpose of this class is to manage the mapping between GaiaIDs and its
+// context data related to push notifications.
+@interface PushNotificationAccountContextManager : NSObject
+
+// The designated initializer. `BrowserStateInfoCache` must not be nil.
+- (instancetype)initWithChromeBrowserStateManager:
+    (ios::ChromeBrowserStateManager*)manager NS_DESIGNATED_INITIALIZER;
+
+- (instancetype)init NS_UNAVAILABLE;
+
+// Adds the account to the manager if the account is not signed into the device
+// in any BrowserState. This function returns a BOOL value indicating whether
+// the account was added to the manager.
+- (BOOL)addAccount:(NSString*)gaiaID;
+
+// Removes the account from the manager if the account is not signed into the
+// device in any BrowserState. This function returns a BOOL value indicating
+// whether the account was removed from the maanger.
+- (BOOL)removeAccount:(NSString*)gaiaID;
+
+// A dictionary that maps a user's GAIA ID to an object containing the account's
+// preferences for all push notification enabled features and an number
+// representing the number of times the account is signed in across
+// BrowserStates.
+@property(nonatomic, readonly)
+    NSDictionary<NSString*, PushNotificationAccountContext*>* contextMap;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_PUSH_NOTIFICATION_PUSH_NOTIFICATION_ACCOUNT_CONTEXT_MANAGER_H_
diff --git a/ios/chrome/browser/push_notification/push_notification_account_context_manager.mm b/ios/chrome/browser/push_notification/push_notification_account_context_manager.mm
new file mode 100644
index 0000000..4c28430d
--- /dev/null
+++ b/ios/chrome/browser/push_notification/push_notification_account_context_manager.mm
@@ -0,0 +1,145 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/push_notification/push_notification_account_context_manager.h"
+
+#import "base/strings/sys_string_conversions.h"
+#import "components/prefs/pref_service.h"
+#import "ios/chrome/browser/browser_state/browser_state_info_cache.h"
+#import "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#import "ios/chrome/browser/browser_state/chrome_browser_state_manager.h"
+#import "ios/chrome/browser/prefs/pref_names.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@interface PushNotificationAccountContext ()
+
+@property(nonatomic, assign) NSUInteger occurrencesAcrossBrowserStates;
+
+@end
+
+@implementation PushNotificationAccountContext
+@end
+
+@implementation PushNotificationAccountContextManager {
+  // Used to retrieve BrowserStates located at a given path.
+  ios::ChromeBrowserStateManager* _chromeBrowserStateManager;
+  // Maps a GaiaID to the given account's AccountContext object.
+  NSMutableDictionary<NSString*, PushNotificationAccountContext*>* _contextMap;
+}
+
+- (instancetype)initWithChromeBrowserStateManager:
+    (ios::ChromeBrowserStateManager*)manager {
+  self = [super init];
+
+  if (self) {
+    _contextMap = [[NSMutableDictionary alloc] init];
+    _chromeBrowserStateManager = manager;
+    BrowserStateInfoCache* infoCache = manager->GetBrowserStateInfoCache();
+
+    const size_t numberOfBrowserStates = infoCache->GetNumberOfBrowserStates();
+    for (size_t i = 0; i < numberOfBrowserStates; i++) {
+      NSString* gaiaID =
+          base::SysUTF8ToNSString(infoCache->GetGAIAIdOfBrowserStateAtIndex(i));
+      base::FilePath path = infoCache->GetPathOfBrowserStateAtIndex(i);
+      ChromeBrowserState* chromeBrowserState =
+          _chromeBrowserStateManager->GetBrowserState(path);
+      [self addAccount:gaiaID withBrowserState:chromeBrowserState];
+    }
+  }
+
+  return self;
+}
+
+- (BOOL)addAccount:(NSString*)gaiaID {
+  ChromeBrowserState* chromeBrowserState = [self chromeBrowserStateFrom:gaiaID];
+  if (!chromeBrowserState)
+    return NO;
+
+  return [self addAccount:gaiaID
+         withBrowserState:[self chromeBrowserStateFrom:gaiaID]];
+}
+
+- (BOOL)removeAccount:(NSString*)gaiaID {
+  PushNotificationAccountContext* context = _contextMap[gaiaID];
+  DCHECK(context);
+
+  context.occurrencesAcrossBrowserStates--;
+  if (context.occurrencesAcrossBrowserStates > 0)
+    return NO;
+
+  [_contextMap removeObjectForKey:gaiaID];
+  return YES;
+}
+
+#pragma mark - Properties
+
+- (NSDictionary<NSString*, PushNotificationAccountContext*>*)contextMap {
+  return _contextMap;
+}
+
+#pragma mark - Private
+
+// Create a new AccountContext object for the given gaiaID, if the gaia id does
+// not already exist in the dictionary, and maps the gaiaID to the new context
+// object.
+- (BOOL)addAccount:(NSString*)gaiaID
+    withBrowserState:(ChromeBrowserState*)browserState {
+  DCHECK(browserState);
+
+  if (!gaiaID.length)
+    return NO;
+
+  PushNotificationAccountContext* context = _contextMap[gaiaID];
+  if (context) {
+    context.occurrencesAcrossBrowserStates++;
+    return NO;
+  }
+
+  PrefService* prefService = browserState->GetPrefs();
+  NSMutableDictionary<NSString*, NSNumber*>* preferenceMap =
+      [[NSMutableDictionary alloc] init];
+  const base::Value::Dict& permissions =
+      prefService->GetDict(prefs::kFeaturePushNotificationPermissions);
+
+  for (const auto pair : permissions) {
+    preferenceMap[base::SysUTF8ToNSString(pair.first)] =
+        [NSNumber numberWithBool:pair.second.GetBool()];
+  }
+
+  context = [[PushNotificationAccountContext alloc] init];
+  context.preferenceMap = preferenceMap;
+  context.occurrencesAcrossBrowserStates = 1;
+  _contextMap[gaiaID] = context;
+
+  return YES;
+}
+
+// Returns the ChromeBrowserState that has the given gaiaID set as the primary
+// account. TODO(crbug.com/1400732) Implement policy that computes correct
+// permission set. This function naively chooses the first ChromeBrowserState
+// that is associated with the given gaiaID. In a multi-profile environment
+// where the given gaiaID is signed into multiple profiles, it is possible that
+// the push notification enabled features' permissions may be incorrectly
+// applied.
+- (ChromeBrowserState*)chromeBrowserStateFrom:(NSString*)gaiaID {
+  BrowserStateInfoCache* infoCache =
+      _chromeBrowserStateManager->GetBrowserStateInfoCache();
+  const size_t numberOfBrowserStates = infoCache->GetNumberOfBrowserStates();
+  for (size_t i = 0; i < numberOfBrowserStates; i++) {
+    NSString* browserStateGaiaID =
+        base::SysUTF8ToNSString(infoCache->GetGAIAIdOfBrowserStateAtIndex(i));
+
+    if (gaiaID == browserStateGaiaID) {
+      base::FilePath path = infoCache->GetPathOfBrowserStateAtIndex(i);
+      return _chromeBrowserStateManager->GetBrowserState(path);
+    }
+  }
+
+  return nil;
+}
+
+@end
diff --git a/ios/chrome/browser/push_notification/push_notification_client_id.h b/ios/chrome/browser/push_notification/push_notification_client_id.h
index 249dfd6..e85417b 100644
--- a/ios/chrome/browser/push_notification/push_notification_client_id.h
+++ b/ios/chrome/browser/push_notification/push_notification_client_id.h
@@ -16,7 +16,7 @@
 enum class PushNotificationClientId {
   // TODO(crbug.com/1353801): Once Chrome has a push notification enabled
   // feature, add that feature's identifier to this class.
-  kCommerce = 1,
+  kCommerce = 1
 };
 
 #endif  // IOS_CHROME_BROWSER_PUSH_NOTIFICATION_PUSH_NOTIFICATION_CLIENT_ID_H_
diff --git a/ios/chrome/browser/push_notification/push_notification_client_manager.h b/ios/chrome/browser/push_notification/push_notification_client_manager.h
index aebd6e8..8547bf2 100644
--- a/ios/chrome/browser/push_notification/push_notification_client_manager.h
+++ b/ios/chrome/browser/push_notification/push_notification_client_manager.h
@@ -31,6 +31,14 @@
   void AddPushNotificationClient(
       std::unique_ptr<PushNotificationClient> client);
 
+  // This function removes the mapping between a PushNotificationClientId and a
+  // PushNotificationClient from the manager.
+  void RemovePushNotificationClient(PushNotificationClientId client_id);
+
+  // This function returns a list of the PushNotificationClients stored by the
+  // manager.
+  std::vector<const PushNotificationClient*> GetPushNotificationClients();
+
   // This function is called when the user interacts with the delivered
   // notification. This function identifies and delegates the interacted with
   // notification to the appropriate PushNotificationClient.
@@ -58,11 +66,10 @@
   static std::vector<PushNotificationClientId> GetClients();
 
  private:
-  // A list of features that support push notifications.
-  std::unordered_map<PushNotificationClientId,
-                     std::unique_ptr<PushNotificationClient>>
-      clients_ = std::unordered_map<PushNotificationClientId,
-                                    std::unique_ptr<PushNotificationClient>>();
+  using ClientMap = std::unordered_map<PushNotificationClientId,
+                                       std::unique_ptr<PushNotificationClient>>;
+  // A map of client ids to the features that support push notifications.
+  ClientMap clients_;
 };
 
-#endif  // IOS_CHROME_BROWSER_PUSH_NOTIFICATION_PUSH_NOTIFICATION_CLIENT_MANAGER_H_
\ No newline at end of file
+#endif  // IOS_CHROME_BROWSER_PUSH_NOTIFICATION_PUSH_NOTIFICATION_CLIENT_MANAGER_H_
diff --git a/ios/chrome/browser/push_notification/push_notification_client_manager.mm b/ios/chrome/browser/push_notification/push_notification_client_manager.mm
index 46c15df..38cad88 100644
--- a/ios/chrome/browser/push_notification/push_notification_client_manager.mm
+++ b/ios/chrome/browser/push_notification/push_notification_client_manager.mm
@@ -15,11 +15,6 @@
 #error "This file requires ARC support."
 #endif
 
-namespace {
-NSString* kClientIdPushNotificationDictionaryKey =
-    @"push_notification_client_id";
-}  // namespace
-
 PushNotificationClientManager::PushNotificationClientManager() {
   if (IsPriceNotificationsEnabled()) {
     AddPushNotificationClient(
@@ -33,34 +28,44 @@
   clients_.insert(std::make_pair(client->GetClientId(), std::move(client)));
 }
 
+void PushNotificationClientManager::RemovePushNotificationClient(
+    PushNotificationClientId client_id) {
+  clients_.erase(client_id);
+}
+
+std::vector<const PushNotificationClient*>
+PushNotificationClientManager::GetPushNotificationClients() {
+  std::vector<const PushNotificationClient*> manager_clients;
+
+  for (auto& client : clients_) {
+    manager_clients.push_back(std::move(client.second.get()));
+  }
+
+  return manager_clients;
+}
+
 void PushNotificationClientManager::HandleNotificationInteraction(
     UNNotificationResponse* notification_response) {
-  NSDictionary* user_info =
-      notification_response.notification.request.content.userInfo;
-
-  PushNotificationClientId client_id =
-      static_cast<PushNotificationClientId>([[user_info
-          objectForKey:kClientIdPushNotificationDictionaryKey] integerValue]);
-
-  auto it = clients_.find(client_id);
-  if (it != clients_.end()) {
-    it->second->HandleNotificationInteraction(notification_response);
+  for (auto& client : clients_) {
+    client.second->HandleNotificationInteraction(notification_response);
   }
 }
 
 UIBackgroundFetchResult
 PushNotificationClientManager::HandleNotificationReception(
     NSDictionary<NSString*, id>* user_info) {
-  PushNotificationClientId client_id =
-      static_cast<PushNotificationClientId>([[user_info
-          objectForKey:kClientIdPushNotificationDictionaryKey] integerValue]);
-
-  auto it = clients_.find(client_id);
-  if (it != clients_.end()) {
-    return it->second->HandleNotificationReception(user_info);
+  UIBackgroundFetchResult result = UIBackgroundFetchResultNoData;
+  for (auto& client : clients_) {
+    UIBackgroundFetchResult client_result =
+        client.second->HandleNotificationReception(user_info);
+    if (client_result == UIBackgroundFetchResultNewData) {
+      return UIBackgroundFetchResultNewData;
+    } else if (client_result == UIBackgroundFetchResultFailed) {
+      result = client_result;
+    }
   }
 
-  return UIBackgroundFetchResultNoData;
+  return result;
 }
 
 void PushNotificationClientManager::RegisterActionableNotifications() {
diff --git a/ios/chrome/browser/push_notification/push_notification_client_manager_unittest.mm b/ios/chrome/browser/push_notification/push_notification_client_manager_unittest.mm
new file mode 100644
index 0000000..183629a
--- /dev/null
+++ b/ios/chrome/browser/push_notification/push_notification_client_manager_unittest.mm
@@ -0,0 +1,123 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <UserNotifications/UserNotifications.h>
+
+#import "ios/chrome/browser/push_notification/push_notification_client.h"
+#import "ios/chrome/browser/push_notification/push_notification_client_manager.h"
+#import "ios/chrome/browser/push_notification/test_push_notification_client.h"
+#import "testing/platform_test.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace {
+
+void GenerateClients(std::unique_ptr<PushNotificationClientManager>& manager,
+                     size_t number_of_clients) {
+  for (size_t i = 0; i < number_of_clients; i++) {
+    std::unique_ptr<PushNotificationClient> client =
+        std::make_unique<TestPushNotificationClient>(i + 1);
+    manager->AddPushNotificationClient(std::move(client));
+  }
+}
+
+TestPushNotificationClient* GetClient(
+    std::unique_ptr<PushNotificationClientManager>& manager,
+    size_t index) {
+  return const_cast<TestPushNotificationClient*>(
+      static_cast<const TestPushNotificationClient*>(
+          manager->GetPushNotificationClients()[index]));
+}
+
+}  // namespace
+
+class PushNotificationClientManagerTest : public PlatformTest {
+ protected:
+  PushNotificationClientManagerTest() {
+    size_t number_of_clients = manager->GetPushNotificationClients().size();
+
+    for (size_t i = 0; i < number_of_clients; i++) {
+      manager->RemovePushNotificationClient(
+          GetClient(manager, i)->GetClientId());
+    }
+  }
+  std::unique_ptr<PushNotificationClientManager> manager =
+      std::make_unique<PushNotificationClientManager>();
+};
+
+TEST_F(PushNotificationClientManagerTest, AddClient) {
+  const unsigned long number_of_clients = 1;
+  GenerateClients(manager, number_of_clients);
+
+  ASSERT_EQ(number_of_clients, manager->GetPushNotificationClients().size());
+}
+
+TEST_F(PushNotificationClientManagerTest, AddMultipleClients) {
+  const unsigned long number_of_clients = 5;
+  GenerateClients(manager, number_of_clients);
+
+  ASSERT_EQ(number_of_clients, manager->GetPushNotificationClients().size());
+}
+
+TEST_F(PushNotificationClientManagerTest, HandleNotificationReception) {
+  GenerateClients(manager, 1);
+  ASSERT_EQ(UIBackgroundFetchResultNoData,
+            manager->HandleNotificationReception(nil));
+}
+
+TEST_F(PushNotificationClientManagerTest,
+       HandleNotificationReceptionWithNewData) {
+  const unsigned long number_of_clients = 5;
+  GenerateClients(manager, number_of_clients);
+
+  TestPushNotificationClient* client = GetClient(manager, 0);
+  client->SetBackgroundFetchResult(UIBackgroundFetchResultNewData);
+  ASSERT_EQ(UIBackgroundFetchResultNewData,
+            manager->HandleNotificationReception(nil));
+}
+
+TEST_F(PushNotificationClientManagerTest,
+       HandleNotificationReceptionWithFailure) {
+  const unsigned long number_of_clients = 5;
+  GenerateClients(manager, number_of_clients);
+
+  TestPushNotificationClient* client = GetClient(manager, 0);
+  client->SetBackgroundFetchResult(UIBackgroundFetchResultFailed);
+  ASSERT_EQ(UIBackgroundFetchResultFailed,
+            manager->HandleNotificationReception(nil));
+}
+
+TEST_F(PushNotificationClientManagerTest,
+       HandleNotificationReceptionWithNewDataAndFailure) {
+  const unsigned long number_of_clients = 5;
+  GenerateClients(manager, number_of_clients);
+
+  GetClient(manager, 0)
+      ->SetBackgroundFetchResult(UIBackgroundFetchResultNewData);
+  GetClient(manager, 1)
+      ->SetBackgroundFetchResult(UIBackgroundFetchResultFailed);
+  ASSERT_EQ(UIBackgroundFetchResultNewData,
+            manager->HandleNotificationReception(nil));
+}
+
+TEST_F(PushNotificationClientManagerTest, HandleNotificationInteraction) {
+  const unsigned long number_of_clients = 1;
+  GenerateClients(manager, number_of_clients);
+
+  manager->HandleNotificationInteraction(nil);
+  ASSERT_TRUE(GetClient(manager, 0)->HasNotificationReceivedInteraction());
+}
+
+TEST_F(PushNotificationClientManagerTest,
+       HandleNotificationInteractionWithMultipleClients) {
+  const unsigned long number_of_clients = 5;
+  GenerateClients(manager, number_of_clients);
+
+  manager->HandleNotificationInteraction(nil);
+  for (size_t i = 0; i < manager->GetPushNotificationClients().size(); i++) {
+    ASSERT_TRUE(GetClient(manager, i)->HasNotificationReceivedInteraction());
+  }
+}
diff --git a/ios/chrome/browser/push_notification/push_notification_configuration.h b/ios/chrome/browser/push_notification/push_notification_configuration.h
index aa38d51..ed3f3503 100644
--- a/ios/chrome/browser/push_notification/push_notification_configuration.h
+++ b/ios/chrome/browser/push_notification/push_notification_configuration.h
@@ -7,6 +7,7 @@
 
 #import <Foundation/Foundation.h>
 
+@class PushNotificationAccountContextManager;
 @protocol SingleSignOnService;
 
 using GaiaIdToPushNotificationPreferenceMap =
@@ -28,10 +29,18 @@
 // SingleSignOnService used by PushNotificationService.
 @property(nonatomic, strong) id<SingleSignOnService> ssoService;
 
-// A dictionary that maps a user's GAIA ID to its preferences for all push
-// notification enabled features.
+// DEPRECATED. Please use the `contextManager.contextMap` instead. A dictionary
+// that maps a user's GAIA ID to its preferences for all push notification
+// enabled features.
 @property(nonatomic, copy) GaiaIdToPushNotificationPreferenceMap* preferenceMap;
 
+// A dictionary that maps a user's GAIA ID to an object containing the account's
+// preferences for all push notification enabled features and an number
+// representing the number of times the account is signed in across
+// BrowserStates.
+@property(nonatomic, copy)
+    PushNotificationAccountContextManager* contextManager;
+
 @end
 
-#endif  // IOS_CHROME_BROWSER_PUSH_NOTIFICATION_PUSH_NOTIFICATION_CONFIGURATION_H_
\ No newline at end of file
+#endif  // IOS_CHROME_BROWSER_PUSH_NOTIFICATION_PUSH_NOTIFICATION_CONFIGURATION_H_
diff --git a/ios/chrome/browser/push_notification/push_notification_service.h b/ios/chrome/browser/push_notification/push_notification_service.h
index c5aaa0e8..09e64dc 100644
--- a/ios/chrome/browser/push_notification/push_notification_service.h
+++ b/ios/chrome/browser/push_notification/push_notification_service.h
@@ -12,6 +12,10 @@
 namespace user_prefs {
 class PrefRegistrySyncable;
 }  // namespace user_prefs
+namespace ios {
+class ChromeBrowserStateManager;
+}
+@class PushNotificationAccountContextManager;
 class PushNotificationClientManager;
 
 // Service responsible for establishing connection and interacting
@@ -35,6 +39,21 @@
   // the operation successfully or unsuccessfully completes.
   virtual void UnregisterDevice(CompletionHandler completion_handler) = 0;
 
+  // Returns whether the device has retrieved and stored its APNS device token.
+  virtual bool DeviceTokenIsSet() const;
+
+  // Registers the new account to the push notification server. In a multi
+  // BrowserState environment, the PushNotificationService tracks the signed in
+  // account across BrowserStates.
+  void RegisterAccount(NSString* account_id,
+                       CompletionHandler completion_handler);
+
+  // Unregisters the account from the push notification server. In a multi
+  // BrowserState environment, the account will not be signed out until it's
+  // signed out across BrowserStates.
+  void UnregisterAccount(NSString* account_id,
+                         CompletionHandler completion_handler);
+
   // Updates the current user's push notification preferences with the push
   // notification server.
   void UpdateFeaturePushNotificationPreferences(
@@ -51,10 +70,30 @@
   // Returns PushNotificationService's PushNotificationClientManager.
   PushNotificationClientManager* GetPushNotificationClientManager();
 
+ protected:
+  PushNotificationService(ios::ChromeBrowserStateManager* manager);
+  // Registers the device with the push notification server. By supplying a list
+  // of the GAIA IDs currently logged into Chrome on the device and the device's
+  // APNS token, the server associates the GAIA IDs to the device, which allows
+  // the server to begin sending push notifications to that device. When this
+  // method is called, the server overwrites the accountIDs that are associated
+  // with the device token with the given array of accountIDs. Thus, to
+  // unregister an account from receiving push notifications on the device, this
+  // method should be called with an array of accountIDs that omits the account
+  // that is intended to be unregistered.
+  virtual void SetAccountsToDevice(NSArray<NSString*>* account_ids,
+                                   CompletionHandler completion_handler) {}
+
  private:
   // The PushNotificationClientManager manages all interactions between the
   // system and push notification enabled features.
   std::unique_ptr<PushNotificationClientManager> client_manager_;
+
+  // Stores a mapping of each account's GAIA ID signed into the device to its
+  // context object. This object contains the account's pref service values
+  // pertaining to push notification supported features and the number of times
+  // the given account is signed in across multiple browser states.
+  __strong PushNotificationAccountContextManager* context_manager_;
 };
 
 #endif  // IOS_CHROME_BROWSER_PUSH_NOTIFICATION_PUSH_NOTIFICATION_SERVICE_H_
diff --git a/ios/chrome/browser/push_notification/push_notification_service.mm b/ios/chrome/browser/push_notification/push_notification_service.mm
index 272baf8..1cc868d 100644
--- a/ios/chrome/browser/push_notification/push_notification_service.mm
+++ b/ios/chrome/browser/push_notification/push_notification_service.mm
@@ -7,7 +7,9 @@
 #import "base/strings/string_number_conversions.h"
 #import "base/values.h"
 #import "components/pref_registry/pref_registry_syncable.h"
+#import "ios/chrome/browser/application_context/application_context.h"
 #import "ios/chrome/browser/prefs/pref_names.h"
+#import "ios/chrome/browser/push_notification/push_notification_account_context_manager.h"
 #import "ios/chrome/browser/push_notification/push_notification_client_id.h"
 #import "ios/chrome/browser/push_notification/push_notification_client_manager.h"
 
@@ -16,7 +18,15 @@
 #endif
 
 PushNotificationService::PushNotificationService()
-    : client_manager_(std::make_unique<PushNotificationClientManager>()) {}
+    : PushNotificationService(
+          GetApplicationContext()->GetChromeBrowserStateManager()) {}
+
+PushNotificationService::PushNotificationService(
+    ios::ChromeBrowserStateManager* manager)
+    : client_manager_(std::make_unique<PushNotificationClientManager>()) {
+  context_manager_ = [[PushNotificationAccountContextManager alloc]
+      initWithChromeBrowserStateManager:manager];
+}
 
 PushNotificationService::~PushNotificationService() = default;
 
@@ -25,6 +35,28 @@
   return client_manager_.get();
 }
 
+bool PushNotificationService::DeviceTokenIsSet() const {
+  return false;
+}
+
+void PushNotificationService::RegisterAccount(
+    NSString* account_id,
+    CompletionHandler completion_handler) {
+  if ([context_manager_ addAccount:account_id]) {
+    SetAccountsToDevice(context_manager_.contextMap.allKeys,
+                        completion_handler);
+  }
+}
+
+void PushNotificationService::UnregisterAccount(
+    NSString* account_id,
+    CompletionHandler completion_handler) {
+  if ([context_manager_ removeAccount:account_id]) {
+    SetAccountsToDevice(context_manager_.contextMap.allKeys,
+                        completion_handler);
+  }
+}
+
 void PushNotificationService::RegisterBrowserStatePrefs(
     user_prefs::PrefRegistrySyncable* registry) {
   base::Value::Dict feature_push_notification_permission = base::Value::Dict();
diff --git a/ios/chrome/browser/push_notification/test_push_notification_client.h b/ios/chrome/browser/push_notification/test_push_notification_client.h
new file mode 100644
index 0000000..2903b4c
--- /dev/null
+++ b/ios/chrome/browser/push_notification/test_push_notification_client.h
@@ -0,0 +1,31 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_PUSH_NOTIFICATION_TEST_PUSH_NOTIFICATION_CLIENT_H_
+#define IOS_CHROME_BROWSER_PUSH_NOTIFICATION_TEST_PUSH_NOTIFICATION_CLIENT_H_
+
+#import "ios/chrome/browser/push_notification/push_notification_client.h"
+
+class TestPushNotificationClient : public PushNotificationClient {
+ public:
+  TestPushNotificationClient(size_t client_id);
+  ~TestPushNotificationClient() override;
+
+  // Override PushNotificationClient::
+  void HandleNotificationInteraction(
+      UNNotificationResponse* notification_response) override;
+  UIBackgroundFetchResult HandleNotificationReception(
+      NSDictionary<NSString*, id>* notification) override;
+  NSArray<UNNotificationCategory*>* RegisterActionableNotifications() override;
+
+  // Indicates whether the client has been
+  bool HasNotificationReceivedInteraction();
+  // Sets the client's UIBackgroundFetchResult to given FetchResult.
+  void SetBackgroundFetchResult(UIBackgroundFetchResult result);
+
+ private:
+  UIBackgroundFetchResult fetch_result_ = UIBackgroundFetchResultNoData;
+  bool has_notification_received_interaction_ = false;
+};
+#endif  // IOS_CHROME_BROWSER_PUSH_NOTIFICATION_TEST_PUSH_NOTIFICATION_CLIENT_H_
diff --git a/ios/chrome/browser/push_notification/test_push_notification_client.mm b/ios/chrome/browser/push_notification/test_push_notification_client.mm
new file mode 100644
index 0000000..b5992725
--- /dev/null
+++ b/ios/chrome/browser/push_notification/test_push_notification_client.mm
@@ -0,0 +1,41 @@
+// Copyright 2022 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/push_notification/test_push_notification_client.h"
+
+#import "ios/chrome/browser/push_notification/push_notification_client_id.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+TestPushNotificationClient::TestPushNotificationClient(size_t client_id)
+    : PushNotificationClient(static_cast<PushNotificationClientId>(client_id)) {
+}
+TestPushNotificationClient::~TestPushNotificationClient() = default;
+
+void TestPushNotificationClient::HandleNotificationInteraction(
+    UNNotificationResponse* notification) {
+  has_notification_received_interaction_ = true;
+}
+
+UIBackgroundFetchResult TestPushNotificationClient::HandleNotificationReception(
+    NSDictionary<NSString*, id>* notification) {
+  return fetch_result_;
+}
+
+NSArray<UNNotificationCategory*>*
+TestPushNotificationClient::RegisterActionableNotifications() {
+  // Add actional notifications as new notification types are added.
+  return @[];
+}
+
+bool TestPushNotificationClient::HasNotificationReceivedInteraction() {
+  return has_notification_received_interaction_;
+}
+
+void TestPushNotificationClient::SetBackgroundFetchResult(
+    UIBackgroundFetchResult result) {
+  fetch_result_ = result;
+}
diff --git a/ios/chrome/browser/search_engines/BUILD.gn b/ios/chrome/browser/search_engines/BUILD.gn
index 940e83c..58a78c6 100644
--- a/ios/chrome/browser/search_engines/BUILD.gn
+++ b/ios/chrome/browser/search_engines/BUILD.gn
@@ -16,8 +16,6 @@
     "search_engine_tab_helper.mm",
     "search_engine_tab_helper_factory.h",
     "search_engine_tab_helper_factory.mm",
-    "search_engines_util.cc",
-    "search_engines_util.h",
     "template_url_fetcher_factory.cc",
     "template_url_fetcher_factory.h",
     "template_url_service_client_impl.cc",
@@ -69,7 +67,7 @@
     "extension_search_engine_data_updater.mm",
   ]
   deps = [
-    ":search_engines",
+    ":search_engines_util",
     "//base",
     "//components/search_engines",
     "//ios/chrome/browser/widget_kit:features",
@@ -123,6 +121,20 @@
   configs += [ "//build/config/compiler:enable_arc" ]
 }
 
+source_set("search_engines_util") {
+  sources = [
+    "search_engines_util.cc",
+    "search_engines_util.h",
+  ]
+  deps = [
+    "//base",
+    "//components/country_codes",
+    "//components/prefs",
+    "//components/search_engines",
+  ]
+  configs += [ "//build/config/compiler:enable_arc" ]
+}
+
 optimize_js("search_engine_js") {
   primary_script = "resources/search_engine.js"
   sources = [ "resources/search_engine.js" ]
diff --git a/ios/chrome/browser/ui/context_menu/BUILD.gn b/ios/chrome/browser/ui/context_menu/BUILD.gn
index ec710165..a73b5459 100644
--- a/ios/chrome/browser/ui/context_menu/BUILD.gn
+++ b/ios/chrome/browser/ui/context_menu/BUILD.gn
@@ -18,6 +18,7 @@
     "//ios/chrome/browser/policy:policy_util",
     "//ios/chrome/browser/prefs:pref_names",
     "//ios/chrome/browser/search_engines",
+    "//ios/chrome/browser/search_engines:search_engines_util",
     "//ios/chrome/browser/ui:feature_flags",
     "//ios/chrome/browser/ui/alert_coordinator",
     "//ios/chrome/browser/ui/commands",
diff --git a/ios/chrome/browser/ui/infobars/banners/infobar_banner_consumer.h b/ios/chrome/browser/ui/infobars/banners/infobar_banner_consumer.h
index f79f7a1bc..5ba4abea 100644
--- a/ios/chrome/browser/ui/infobars/banners/infobar_banner_consumer.h
+++ b/ios/chrome/browser/ui/infobars/banners/infobar_banner_consumer.h
@@ -40,6 +40,11 @@
 // The subtitle displayed by this InfobarBanner.
 - (void)setSubtitleText:(NSString*)subtitleText;
 
+// Applies a custom style (e.g. bold, italic) to a range of the subtitle
+// displayed by the InfobarBanner.
+- (void)addCustomStyle:(UIFontDescriptorSymbolicTraits)symbolicTraits
+       toSubtitleRange:(NSRange)range;
+
 // If YES, restricts the number of lines in subtitle to 1.
 - (void)setRestrictSubtitleTextToSingleLine:
     (BOOL)restrictSubtitleTextToSingleLine;
diff --git a/ios/chrome/browser/ui/infobars/banners/infobar_banner_view_controller.mm b/ios/chrome/browser/ui/infobars/banners/infobar_banner_view_controller.mm
index 71e2c24..0ba77823 100644
--- a/ios/chrome/browser/ui/infobars/banners/infobar_banner_view_controller.mm
+++ b/ios/chrome/browser/ui/infobars/banners/infobar_banner_view_controller.mm
@@ -58,6 +58,21 @@
 constexpr base::TimeDelta kLongPressTimeDuration = base::Milliseconds(400);
 }  // namespace
 
+#pragma mark - StyledRange
+
+// Used to track ranges of a string that should receive custom styling.
+@interface StyledRange : NSObject
+
+@property(nonatomic) UIFontDescriptorSymbolicTraits symbolicTraits;
+@property(nonatomic) NSRange range;
+
+@end
+
+@implementation StyledRange
+@end
+
+#pragma mark - InfobarBannerViewController
+
 @interface InfobarBannerViewController ()
 
 // Properties backing the InfobarBannerConsumer protocol.
@@ -67,6 +82,7 @@
 @property(nonatomic, assign) BOOL presentsModal;
 @property(nonatomic, copy) NSString* titleText;
 @property(nonatomic, copy) NSString* subtitleText;
+@property(nonatomic, copy) NSMutableArray<StyledRange*>* subtitleStyledRanges;
 @property(nonatomic, assign) BOOL useIconBackgroundTint;
 @property(nonatomic, strong) UIColor* iconImageTintColor;
 @property(nonatomic, strong) UIColor* iconBackgroundColor;
@@ -117,6 +133,7 @@
     _presentsModal = presentsModal;
     _useIconBackgroundTint = YES;
     _restrictSubtitleTextToSingleLine = NO;
+    _subtitleStyledRanges = [[NSMutableArray alloc] init];
   }
   return self;
 }
@@ -203,11 +220,8 @@
                                       forAxis:UILayoutConstraintAxisVertical];
 
   self.subTitleLabel = [[UILabel alloc] init];
-  self.subTitleLabel.text = self.subtitleText;
-  self.subTitleLabel.font =
-      [UIFont preferredFontForTextStyle:UIFontTextStyleFootnote];
+  self.subTitleLabel.attributedText = [self subtitleAttributedText];
   self.subTitleLabel.adjustsFontForContentSizeCategory = YES;
-  self.subTitleLabel.textColor = [UIColor colorNamed:kTextSecondaryColor];
   if (_restrictSubtitleTextToSingleLine) {
     self.subTitleLabel.numberOfLines = 1;
   } else {
@@ -405,7 +419,7 @@
 
 - (void)setSubtitleText:(NSString*)subtitleText {
   _subtitleText = subtitleText;
-  self.subTitleLabel.text = _subtitleText;
+  self.subTitleLabel.attributedText = [self subtitleAttributedText];
   self.subTitleLabel.hidden = !self.subtitleText.length;
 }
 
@@ -435,6 +449,14 @@
   _iconBackgroundColor = iconBackgroundColor;
 }
 
+- (void)addCustomStyle:(UIFontDescriptorSymbolicTraits)symbolicTraits
+       toSubtitleRange:(NSRange)range {
+  StyledRange* styledRange = [[StyledRange alloc] init];
+  styledRange.symbolicTraits = symbolicTraits;
+  styledRange.range = range;
+  [_subtitleStyledRanges addObject:styledRange];
+}
+
 - (void)setRestrictSubtitleTextToSingleLine:
     (BOOL)restrictSubtitleTextToSingleLine {
   _restrictSubtitleTextToSingleLine = restrictSubtitleTextToSingleLine;
@@ -643,4 +665,33 @@
   return self.titleText;
 }
 
+- (NSMutableAttributedString*)subtitleAttributedText {
+  if (!self.self.subtitleText) {
+    return nil;
+  }
+
+  UIFontDescriptor* defaultDescriptor = [UIFontDescriptor
+      preferredFontDescriptorWithTextStyle:UIFontTextStyleFootnote];
+  // Passing 0 defers the size responsibility to the descriptor.
+  UIFont* defaultFont = [UIFont fontWithDescriptor:defaultDescriptor size:0.0];
+  NSMutableAttributedString* attributedText = [[NSMutableAttributedString alloc]
+      initWithString:self.subtitleText
+          attributes:@{
+            NSFontAttributeName : defaultFont,
+            NSForegroundColorAttributeName :
+                [UIColor colorNamed:kTextSecondaryColor]
+          }];
+
+  for (StyledRange* styledRange in self.subtitleStyledRanges) {
+    UIFontDescriptor* customDescriptor = [defaultDescriptor
+        fontDescriptorWithSymbolicTraits:styledRange.symbolicTraits];
+    // Passing 0 defers the size responsibility to the descriptor.
+    UIFont* customFont = [UIFont fontWithDescriptor:customDescriptor size:0.0];
+    [attributedText addAttribute:NSFontAttributeName
+                           value:customFont
+                           range:styledRange.range];
+  }
+  return attributedText;
+}
+
 @end
diff --git a/ios/chrome/browser/ui/infobars/banners/infobar_banner_view_controller_unittest.mm b/ios/chrome/browser/ui/infobars/banners/infobar_banner_view_controller_unittest.mm
index d00b2d3e..d0b33aa 100644
--- a/ios/chrome/browser/ui/infobars/banners/infobar_banner_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/infobars/banners/infobar_banner_view_controller_unittest.mm
@@ -56,9 +56,9 @@
   // Add view_controller_ to the UI Hierarchy to make sure views are created and
   // retained correctly.
   [scoped_key_window_.Get() setRootViewController:view_controller_];
-  ASSERT_EQ(view_controller_.titleLabel.text, kTitle);
-  ASSERT_EQ(view_controller_.subTitleLabel.text, kSubtitleText);
-  ASSERT_EQ(view_controller_.infobarButton.titleLabel.text, kButtonText);
+  ASSERT_NSEQ(view_controller_.titleLabel.text, kTitle);
+  ASSERT_NSEQ(view_controller_.subTitleLabel.text, kSubtitleText);
+  ASSERT_NSEQ(view_controller_.infobarButton.titleLabel.text, kButtonText);
 }
 
 TEST_F(InfobarBannerViewControllerTest, TestSubtitleLabelHidden) {
@@ -66,6 +66,38 @@
   // Add view_controller_ to the UI Hierarchy to make sure views are created and
   // retained correctly.
   [scoped_key_window_.Get() setRootViewController:view_controller_];
-  ASSERT_EQ(view_controller_.titleLabel.text, @"title");
+  ASSERT_NSEQ(view_controller_.titleLabel.text, @"title");
   ASSERT_TRUE(view_controller_.subTitleLabel.hidden);
 }
+
+TEST_F(InfobarBannerViewControllerTest, TestAddCustomStyleToSubtitleRange) {
+  NSString* const kSubtitleText = @"BoldItalic";
+  NSRange expectedBoldRange = [kSubtitleText rangeOfString:@"Bold"];
+  NSRange expectedItalicRange = [kSubtitleText rangeOfString:@"Italic"];
+  [view_controller_ setSubtitleText:kSubtitleText];
+
+  [view_controller_ addCustomStyle:UIFontDescriptorTraitBold
+                   toSubtitleRange:expectedBoldRange];
+  [view_controller_ addCustomStyle:UIFontDescriptorTraitItalic
+                   toSubtitleRange:expectedItalicRange];
+  [scoped_key_window_.Get() setRootViewController:view_controller_];
+
+  NSRange maxSearchRange = NSMakeRange(0, [kSubtitleText length]);
+  NSRange range1;
+  UIFont* font1 = [view_controller_.subTitleLabel.attributedText
+                  attribute:NSFontAttributeName
+                    atIndex:0
+      longestEffectiveRange:&range1
+                    inRange:maxSearchRange];
+  EXPECT_TRUE(NSEqualRanges(range1, expectedBoldRange));
+  EXPECT_EQ([[font1 fontDescriptor] symbolicTraits], UIFontDescriptorTraitBold);
+  NSRange range2;
+  UIFont* font2 = [view_controller_.subTitleLabel.attributedText
+                  attribute:NSFontAttributeName
+                    atIndex:expectedItalicRange.location
+      longestEffectiveRange:&range2
+                    inRange:maxSearchRange];
+  EXPECT_TRUE(NSEqualRanges(range2, expectedItalicRange));
+  EXPECT_EQ([[font2 fontDescriptor] symbolicTraits],
+            UIFontDescriptorTraitItalic);
+}
diff --git a/ios/chrome/browser/ui/infobars/banners/test/fake_infobar_banner_consumer.mm b/ios/chrome/browser/ui/infobars/banners/test/fake_infobar_banner_consumer.mm
index 5bb7469..f9480c6 100644
--- a/ios/chrome/browser/ui/infobars/banners/test/fake_infobar_banner_consumer.mm
+++ b/ios/chrome/browser/ui/infobars/banners/test/fake_infobar_banner_consumer.mm
@@ -9,4 +9,9 @@
 #endif
 
 @implementation FakeInfobarBannerConsumer
+
+- (void)addCustomStyle:(UIFontDescriptorSymbolicTraits)symbolicTraits
+       toSubtitleRange:(NSRange)range {
+}
+
 @end
diff --git a/ios/chrome/browser/ui/location_bar/BUILD.gn b/ios/chrome/browser/ui/location_bar/BUILD.gn
index 8195192f..7bbb4011 100644
--- a/ios/chrome/browser/ui/location_bar/BUILD.gn
+++ b/ios/chrome/browser/ui/location_bar/BUILD.gn
@@ -51,6 +51,7 @@
     "//ios/chrome/browser/overlays",
     "//ios/chrome/browser/overlays/public/web_content_area",
     "//ios/chrome/browser/search_engines",
+    "//ios/chrome/browser/search_engines:search_engines_util",
     "//ios/chrome/browser/ssl",
     "//ios/chrome/browser/ui:feature_flags",
     "//ios/chrome/browser/ui/badges",
diff --git a/ios/chrome/browser/ui/omnibox/BUILD.gn b/ios/chrome/browser/ui/omnibox/BUILD.gn
index b901aa74..601f88a 100644
--- a/ios/chrome/browser/ui/omnibox/BUILD.gn
+++ b/ios/chrome/browser/ui/omnibox/BUILD.gn
@@ -167,6 +167,7 @@
     "//ios/chrome/browser/net",
     "//ios/chrome/browser/prerender",
     "//ios/chrome/browser/search_engines",
+    "//ios/chrome/browser/search_engines:search_engines_util",
     "//ios/chrome/browser/sessions",
     "//ios/chrome/browser/ui:feature_flags",
     "//ios/chrome/browser/ui/commands",
diff --git a/ios/chrome/browser/ui/omnibox/keyboard_assist/BUILD.gn b/ios/chrome/browser/ui/omnibox/keyboard_assist/BUILD.gn
index 3f616231..614f3714 100644
--- a/ios/chrome/browser/ui/omnibox/keyboard_assist/BUILD.gn
+++ b/ios/chrome/browser/ui/omnibox/keyboard_assist/BUILD.gn
@@ -27,7 +27,8 @@
     "//components/search_engines",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/flags:system_flags",
-    "//ios/chrome/browser/search_engines:search_engines",
+    "//ios/chrome/browser/search_engines",
+    "//ios/chrome/browser/search_engines:search_engines_util",
     "//ios/chrome/browser/ui:feature_flags",
     "//ios/chrome/browser/ui/commands",
     "//ios/chrome/browser/ui/lens:lens_entrypoint",
diff --git a/ios/chrome/browser/ui/popup_menu/BUILD.gn b/ios/chrome/browser/ui/popup_menu/BUILD.gn
index 3f848db..e98ebf368 100644
--- a/ios/chrome/browser/ui/popup_menu/BUILD.gn
+++ b/ios/chrome/browser/ui/popup_menu/BUILD.gn
@@ -78,6 +78,7 @@
     "//ios/chrome/browser/policy:policy_util",
     "//ios/chrome/browser/reading_list",
     "//ios/chrome/browser/search_engines",
+    "//ios/chrome/browser/search_engines:search_engines_util",
     "//ios/chrome/browser/translate",
     "//ios/chrome/browser/ui:feature_flags",
     "//ios/chrome/browser/ui/activity_services",
diff --git a/ios/chrome/browser/ui/toolbar/BUILD.gn b/ios/chrome/browser/ui/toolbar/BUILD.gn
index 60f434c..403aa3b 100644
--- a/ios/chrome/browser/ui/toolbar/BUILD.gn
+++ b/ios/chrome/browser/ui/toolbar/BUILD.gn
@@ -45,6 +45,7 @@
     "//ios/chrome/browser/prerender",
     "//ios/chrome/browser/reading_list",
     "//ios/chrome/browser/search_engines",
+    "//ios/chrome/browser/search_engines:search_engines_util",
     "//ios/chrome/browser/ui:feature_flags",
     "//ios/chrome/browser/ui/bookmarks:core",
     "//ios/chrome/browser/ui/broadcaster",
diff --git a/ios/chrome/browser/web_state_list/web_state_list_metrics_browser_agent.mm b/ios/chrome/browser/web_state_list/web_state_list_metrics_browser_agent.mm
index 24709ae..bd21205 100644
--- a/ios/chrome/browser/web_state_list/web_state_list_metrics_browser_agent.mm
+++ b/ios/chrome/browser/web_state_list/web_state_list_metrics_browser_agent.mm
@@ -4,6 +4,7 @@
 
 #import "ios/chrome/browser/web_state_list/web_state_list_metrics_browser_agent.h"
 
+#import "base/metrics/histogram_functions.h"
 #import "base/metrics/histogram_macros.h"
 #import "base/metrics/user_metrics.h"
 #import "base/metrics/user_metrics_action.h"
@@ -85,6 +86,12 @@
     bool user_action) {
   if (metric_collection_paused_)
     return;
+
+  base::TimeDelta age_at_deletion =
+      base::Time::Now() - web_state->GetCreationTime();
+  base::UmaHistogramCustomTimes("Tab.AgeAtDeletion", age_at_deletion,
+                                base::Minutes(1), base::Days(24), 50);
+
   if (user_action)
     base::RecordAction(base::UserMetricsAction("MobileTabClosed"));
 }
diff --git a/ios/chrome/test/BUILD.gn b/ios/chrome/test/BUILD.gn
index 169f407..b62c4445 100644
--- a/ios/chrome/test/BUILD.gn
+++ b/ios/chrome/test/BUILD.gn
@@ -239,6 +239,7 @@
     "//ios/chrome/browser/policy:unit_tests",
     "//ios/chrome/browser/prerender:unit_tests",
     "//ios/chrome/browser/promos_manager:unit_tests",
+    "//ios/chrome/browser/push_notification:unit_tests",
     "//ios/chrome/browser/reading_list:unit_tests",
     "//ios/chrome/browser/safe_browsing:unit_tests",
     "//ios/chrome/browser/safe_browsing/tailored_security:unit_tests",
diff --git a/ios/chrome/test/earl_grey/BUILD.gn b/ios/chrome/test/earl_grey/BUILD.gn
index 747291c..150177c 100644
--- a/ios/chrome/test/earl_grey/BUILD.gn
+++ b/ios/chrome/test/earl_grey/BUILD.gn
@@ -75,6 +75,7 @@
     "//components/password_manager/core/common",
     "//components/prefs",
     "//components/safe_browsing/core/common",
+    "//components/search_engines",
     "//components/strings",
     "//components/sync/base",
     "//components/translate/core/browser",
@@ -97,6 +98,8 @@
     "//ios/chrome/browser/passwords",
     "//ios/chrome/browser/passwords:eg_app_support+eg2",
     "//ios/chrome/browser/policy:eg_app_support+eg2",
+    "//ios/chrome/browser/search_engines:search_engines_util",
+    "//ios/chrome/browser/search_engines:template_url_service_factory",
     "//ios/chrome/browser/signin:fake_system_identity",
     "//ios/chrome/browser/sync",
     "//ios/chrome/browser/translate:eg_app_support+eg2",
diff --git a/ios/chrome/test/earl_grey/DEPS b/ios/chrome/test/earl_grey/DEPS
index 1f3f327..a564f78 100644
--- a/ios/chrome/test/earl_grey/DEPS
+++ b/ios/chrome/test/earl_grey/DEPS
@@ -2,6 +2,7 @@
   # To compile base::Feature under EG2
   "chrome_earl_grey_app_interface\.mm":[
    "+components/autofill/core/common/autofill_features.h",
+   "+components/search_engines/template_url_service.h",
    "+components/password_manager/core/common/password_manager_features.h",
    "+components/payments/core/features.h",
    "+components/ukm/ios/features.h",
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
index 5105203c..9cc11e7 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
@@ -21,6 +21,7 @@
 #import "components/metrics/demographics/demographic_metrics_provider.h"
 #import "components/password_manager/core/common/password_manager_features.h"
 #import "components/prefs/pref_service.h"
+#import "components/search_engines/template_url_service.h"
 #import "components/sync/base/pref_names.h"
 #import "components/unified_consent/unified_consent_service.h"
 #import "components/variations/variations_associated_data.h"
@@ -31,6 +32,8 @@
 #import "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #import "ios/chrome/browser/content_settings/host_content_settings_map_factory.h"
 #import "ios/chrome/browser/ntp/features.h"
+#import "ios/chrome/browser/search_engines/search_engines_util.h"
+#import "ios/chrome/browser/search_engines/template_url_service_factory.h"
 #import "ios/chrome/browser/signin/fake_system_identity.h"
 #import "ios/chrome/browser/sync/sync_service_factory.h"
 #import "ios/chrome/browser/ui/commands/application_commands.h"
@@ -1104,9 +1107,13 @@
 }
 
 + (BOOL)isUseLensToSearchForImageEnabled {
+  TemplateURLService* service =
+      ios::TemplateURLServiceFactory::GetForBrowserState(
+          chrome_test_util::GetOriginalBrowserState());
   return base::FeatureList::IsEnabled(kUseLensToSearchForImage) &&
          ios::provider::IsLensSupported() &&
-         ui::GetDeviceFormFactor() != ui::DEVICE_FORM_FACTOR_TABLET;
+         ui::GetDeviceFormFactor() != ui::DEVICE_FORM_FACTOR_TABLET &&
+         search_engines::SupportsSearchImageWithLens(service);
 }
 
 + (BOOL)isThumbstripEnabledForWindowWithNumber:(int)windowNumber {
diff --git a/ios/chrome/test/providers/push_notification/test_push_notification.mm b/ios/chrome/test/providers/push_notification/test_push_notification.mm
index 628ccc7..41661499 100644
--- a/ios/chrome/test/providers/push_notification/test_push_notification.mm
+++ b/ios/chrome/test/providers/push_notification/test_push_notification.mm
@@ -27,6 +27,12 @@
   void RegisterDevice(PushNotificationConfiguration* config,
                       void (^completion_handler)(NSError* error)) final;
   void UnregisterDevice(void (^completion_handler)(NSError* error)) final;
+  bool DeviceTokenIsSet() const final;
+
+ protected:
+  // PushNotificationService implementation.
+  void SetAccountsToDevice(NSArray<NSString*>* account_ids,
+                           CompletionHandler completion_handler) final;
 };
 
 void TestPushNotificationService::RegisterDevice(
@@ -59,8 +65,28 @@
       }));
 }
 
+bool TestPushNotificationService::DeviceTokenIsSet() const {
+  return false;
+}
+
+void TestPushNotificationService::SetAccountsToDevice(
+    NSArray<NSString*>* account_ids,
+    void (^completion_handler)(NSError* error)) {
+  // Test implementation does nothing. As a result, the `completion_handler` is
+  // called with a NSFeatureUnsupportedError.
+
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(^() {
+        NSError* error =
+            [NSError errorWithDomain:kTestPushNotificationErrorDomain
+                                code:NSFeatureUnsupportedError
+                            userInfo:nil];
+        completion_handler(error);
+      }));
+}
+
 std::unique_ptr<PushNotificationService> CreatePushNotificationService() {
   return std::make_unique<TestPushNotificationService>();
 }
 }  // namespace provider
-}  // namespace ios
\ No newline at end of file
+}  // namespace ios
diff --git a/ios/chrome/test/wpt/cwt_webdriver_app_interface.mm b/ios/chrome/test/wpt/cwt_webdriver_app_interface.mm
index fa433e2..3a7b6c4 100644
--- a/ios/chrome/test/wpt/cwt_webdriver_app_interface.mm
+++ b/ios/chrome/test/wpt/cwt_webdriver_app_interface.mm
@@ -192,56 +192,38 @@
 + (NSString*)executeAsyncJavaScriptFunction:(NSString*)function
                                       inTab:(NSString*)tabID
                                     timeout:(base::TimeDelta)timeout {
-  const std::string kMessageResultKey("result");
-
-  // Use a distinct messageID value for each invocation of this method to
-  // distinguish stale messages (from previous script invocations that timed
-  // out) from a message for the current script.
-  static NSUInteger messageID = 0;
-  std::string command = base::StringPrintf("CWTWebDriver%lu", messageID++);
-
-  // Construct a completion handler that takes a single argument and sends a
-  // message with this argument.
-  std::string scriptCompletionHandler =
-      base::StringPrintf("function(value) {"
-                         "__gCrWeb.message.invokeOnHost({command: "
-                         "'%s.result', %s: value}); }",
-                         command.c_str(), kMessageResultKey.c_str());
-
-  // Construct a script that calls the given `function` with
-  // `scriptCompletionHandler` as an argument.
-  std::string scriptFunctionWithCompletionHandler = base::StringPrintf(
-      "(%s).call(null, %s)", base::SysNSStringToUTF8(function).c_str(),
-      scriptCompletionHandler.c_str());
-
-  __block absl::optional<base::Value> messageValue;
-  const web::WebState::ScriptCommandCallback callback =
-      base::BindRepeating(^(const base::Value& value, const GURL&,
-                            /*interacted*/ bool,
-                            /*sender_frame*/ web::WebFrame*) {
-        const base::Value* result = value.FindKey(kMessageResultKey);
-
-        // `result` will be null when the computed result in JavaScript is
-        // `undefined`. This happens, for example, when injecting a script that
-        // performs some action (like setting the document's title) but doesn't
-        // return any value.
-        if (result)
-          messageValue = result->Clone();
-        else
-          messageValue = base::Value();
-      });
-
   __block BOOL webStateFound = NO;
-  __block base::CallbackListSubscription subscription;
+  __block absl::optional<base::Value> messageValue;
   DispatchSyncOnMainThread(^{
     web::WebState* webState = GetWebStateWithId(tabID);
     if (!webState)
       return;
+    web::WebFrame* mainFrame = web::GetMainFrame(webState);
+    if (!mainFrame) {
+      return;
+    }
     webStateFound = YES;
-    subscription = webState->AddScriptCommandCallback(callback, command);
-    web::WebFrame* main_frame = web::GetMainFrame(webState);
-    main_frame->ExecuteJavaScript(
-        base::UTF8ToUTF16(scriptFunctionWithCompletionHandler));
+
+    NSString* script =
+        [NSString stringWithFormat:@"var result;"
+                                   @"(%@).call(null, (r) => { result = r; } );"
+                                   @"result;",
+                                   function];
+
+    mainFrame->ExecuteJavaScript(base::SysNSStringToUTF16(script),
+                                 base::BindOnce(^(const base::Value* result) {
+                                   // `result` will be null when the computed
+                                   // result in JavaScript is `undefined`. This
+                                   // happens, for example, when injecting a
+                                   // script that performs some action (like
+                                   // setting the document's title) but doesn't
+                                   // return any value.
+                                   if (result) {
+                                     messageValue = result->Clone();
+                                   } else {
+                                     messageValue = base::Value();
+                                   }
+                                 }));
   });
 
   if (!webStateFound)
diff --git a/media/gpu/chromeos/gl_image_processor_backend.cc b/media/gpu/chromeos/gl_image_processor_backend.cc
index 05f3d42..347c6ff 100644
--- a/media/gpu/chromeos/gl_image_processor_backend.cc
+++ b/media/gpu/chromeos/gl_image_processor_backend.cc
@@ -83,9 +83,10 @@
   DCHECK(native_pixmap->AreDmaBufFdsValid());
 
   // Import the NativePixmap into GL.
-  auto image = base::MakeRefCounted<gl::GLImageNativePixmap>(
-      video_frame->coded_size(), gfx::BufferFormat::YUV_420_BIPLANAR);
-  if (!image->Initialize(std::move(native_pixmap))) {
+  auto image = gl::GLImageNativePixmap::Create(
+      video_frame->coded_size(), gfx::BufferFormat::YUV_420_BIPLANAR,
+      std::move(native_pixmap));
+  if (!image) {
     LOG(ERROR) << "Could not initialize the GL image";
     return nullptr;
   }
diff --git a/media/gpu/test/video_frame_validator.cc b/media/gpu/test/video_frame_validator.cc
index c1ccb518..521ea3a 100644
--- a/media/gpu/test/video_frame_validator.cc
+++ b/media/gpu/test/video_frame_validator.cc
@@ -20,6 +20,7 @@
 #include "media/gpu/buildflags.h"
 #include "media/gpu/macros.h"
 #include "media/gpu/test/image_quality_metrics.h"
+#include "media/gpu/test/video_frame_helpers.h"
 #include "media/gpu/test/video_test_helpers.h"
 #include "media/gpu/video_frame_mapper.h"
 #include "media/gpu/video_frame_mapper_factory.h"
@@ -31,8 +32,10 @@
 namespace test {
 
 VideoFrameValidator::VideoFrameValidator(
-    std::unique_ptr<VideoFrameProcessor> corrupt_frame_processor)
+    std::unique_ptr<VideoFrameProcessor> corrupt_frame_processor,
+    CropHelper crop_helper)
     : corrupt_frame_processor_(std::move(corrupt_frame_processor)),
+      crop_helper_(crop_helper),
       num_frames_validating_(0),
       frame_validator_thread_("FrameValidatorThread"),
       frame_validator_cv_(&frame_validator_lock_) {
@@ -179,6 +182,9 @@
 
   ASSERT_TRUE(frame->IsMappable());
 
+  if (ShouldCrop())
+    frame = CloneAndCropFrame(std::move(frame));
+
   auto mismatched_info = Validate(frame, frame_index);
 
   base::AutoLock auto_lock(frame_validator_lock_);
@@ -195,6 +201,36 @@
   frame_validator_cv_.Signal();
 }
 
+scoped_refptr<VideoFrame> VideoFrameValidator::CloneAndCropFrame(
+    scoped_refptr<const VideoFrame> frame) const {
+  const auto crop = crop_helper_.Run(*frame);
+  const auto& visible = frame->visible_rect();
+  auto cloned_frame = CloneVideoFrame(frame.get(), frame->layout());
+  // Ensures that the crop is within the previous visible rectangle.
+  if (!visible.Contains(crop)) {
+    LOG(ERROR) << "Crop " << crop.ToString()
+               << "must be contained by the visible area of the input frame: "
+               << visible.ToString() << ".";
+    return cloned_frame;
+  }
+  return VideoFrame::WrapVideoFrame(cloned_frame, frame->format(), crop,
+                                    crop.size());
+}
+
+gfx::Rect BottomRowCrop(int row_height, const VideoFrame& frame) {
+  const auto& visible = frame.visible_rect();
+  // Performs bounds checking on the row_height to ensure that the returned
+  // crop is contained by the current visible area.
+  if (visible.size().height() < row_height) {
+    LOG(ERROR) << "Could not get a row of height " << row_height
+               << " from a visible area of height " << visible.size().height();
+    return visible;
+  }
+
+  return gfx::Rect{visible.x(), visible.bottom() - row_height, visible.width(),
+                   row_height};
+}
+
 struct MD5VideoFrameValidator::MD5MismatchedFrameInfo
     : public VideoFrameValidator::MismatchedFrameInfo {
   MD5MismatchedFrameInfo(size_t frame_index,
@@ -218,10 +254,11 @@
 std::unique_ptr<MD5VideoFrameValidator> MD5VideoFrameValidator::Create(
     const std::vector<std::string>& expected_frame_checksums,
     VideoPixelFormat validation_format,
-    std::unique_ptr<VideoFrameProcessor> corrupt_frame_processor) {
-  auto video_frame_validator = base::WrapUnique(
-      new MD5VideoFrameValidator(expected_frame_checksums, validation_format,
-                                 std::move(corrupt_frame_processor)));
+    std::unique_ptr<VideoFrameProcessor> corrupt_frame_processor,
+    CropHelper crop_helper) {
+  auto video_frame_validator = base::WrapUnique(new MD5VideoFrameValidator(
+      expected_frame_checksums, validation_format,
+      std::move(corrupt_frame_processor), std::move(crop_helper)));
   if (!video_frame_validator->Initialize()) {
     LOG(ERROR) << "Failed to initialize MD5VideoFrameValidator.";
     return nullptr;
@@ -233,8 +270,10 @@
 MD5VideoFrameValidator::MD5VideoFrameValidator(
     const std::vector<std::string>& expected_frame_checksums,
     VideoPixelFormat validation_format,
-    std::unique_ptr<VideoFrameProcessor> corrupt_frame_processor)
-    : VideoFrameValidator(std::move(corrupt_frame_processor)),
+    std::unique_ptr<VideoFrameProcessor> corrupt_frame_processor,
+    CropHelper crop_helper)
+    : VideoFrameValidator(std::move(corrupt_frame_processor),
+                          std::move(crop_helper)),
       expected_frame_checksums_(expected_frame_checksums),
       validation_format_(validation_format) {}
 
@@ -325,9 +364,11 @@
 std::unique_ptr<RawVideoFrameValidator> RawVideoFrameValidator::Create(
     const GetModelFrameCB& get_model_frame_cb,
     std::unique_ptr<VideoFrameProcessor> corrupt_frame_processor,
-    uint8_t tolerance) {
+    uint8_t tolerance,
+    CropHelper crop_helper) {
   auto video_frame_validator = base::WrapUnique(new RawVideoFrameValidator(
-      get_model_frame_cb, std::move(corrupt_frame_processor), tolerance));
+      get_model_frame_cb, std::move(corrupt_frame_processor), tolerance,
+      std::move(crop_helper)));
   if (!video_frame_validator->Initialize()) {
     LOG(ERROR) << "Failed to initialize RawVideoFrameValidator.";
     return nullptr;
@@ -339,8 +380,10 @@
 RawVideoFrameValidator::RawVideoFrameValidator(
     const GetModelFrameCB& get_model_frame_cb,
     std::unique_ptr<VideoFrameProcessor> corrupt_frame_processor,
-    uint8_t tolerance)
-    : VideoFrameValidator(std::move(corrupt_frame_processor)),
+    uint8_t tolerance,
+    CropHelper crop_helper)
+    : VideoFrameValidator(std::move(corrupt_frame_processor),
+                          std::move(crop_helper)),
       get_model_frame_cb_(get_model_frame_cb),
       tolerance_(tolerance) {}
 
@@ -352,6 +395,10 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(validator_thread_sequence_checker_);
   auto model_frame = get_model_frame_cb_.Run(frame_index);
   CHECK(model_frame);
+
+  if (ShouldCrop())
+    model_frame = CloneAndCropFrame(std::move(model_frame));
+
   size_t diff_cnt =
       CompareFramesWithErrorDiff(*frame, *model_frame, tolerance_);
   if (diff_cnt > 0)
@@ -376,15 +423,15 @@
     const GetModelFrameCB& get_model_frame_cb,
     std::unique_ptr<VideoFrameProcessor> corrupt_frame_processor,
     ValidationMode validation_mode,
-    double tolerance) {
+    double tolerance,
+    CropHelper crop_helper) {
   auto video_frame_validator = base::WrapUnique(new PSNRVideoFrameValidator(
       get_model_frame_cb, std::move(corrupt_frame_processor), validation_mode,
-      tolerance));
+      tolerance, std::move(crop_helper)));
   if (!video_frame_validator->Initialize()) {
     LOG(ERROR) << "Failed to initialize PSNRVideoFrameValidator.";
     return nullptr;
   }
-
   return video_frame_validator;
 }
 
@@ -392,8 +439,10 @@
     const GetModelFrameCB& get_model_frame_cb,
     std::unique_ptr<VideoFrameProcessor> corrupt_frame_processor,
     ValidationMode validation_mode,
-    double tolerance)
-    : VideoFrameValidator(std::move(corrupt_frame_processor)),
+    double tolerance,
+    CropHelper crop_helper)
+    : VideoFrameValidator(std::move(corrupt_frame_processor),
+                          std::move(crop_helper)),
       get_model_frame_cb_(get_model_frame_cb),
       tolerance_(tolerance),
       validation_mode_(validation_mode) {}
@@ -406,6 +455,10 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(validator_thread_sequence_checker_);
   auto model_frame = get_model_frame_cb_.Run(frame_index);
   CHECK(model_frame);
+
+  if (ShouldCrop())
+    model_frame = CloneAndCropFrame(std::move(model_frame));
+
   double psnr = ComputePSNR(*frame, *model_frame);
   DVLOGF(4) << "frame_index: " << frame_index << ", psnr: " << psnr;
   psnr_[frame_index] = psnr;
@@ -425,6 +478,7 @@
     average += psnr.second;
   }
   average /= psnr_.size();
+  DVLOGF(4) << "Average PSNR: " << average;
   if (average < tolerance_) {
     LOG(ERROR) << "Average PSNR is too low: " << average;
     return false;
@@ -449,15 +503,15 @@
     const GetModelFrameCB& get_model_frame_cb,
     std::unique_ptr<VideoFrameProcessor> corrupt_frame_processor,
     ValidationMode validation_mode,
-    double tolerance) {
+    double tolerance,
+    CropHelper crop_helper) {
   auto video_frame_validator = base::WrapUnique(new SSIMVideoFrameValidator(
       get_model_frame_cb, std::move(corrupt_frame_processor), validation_mode,
-      tolerance));
+      tolerance, std::move(crop_helper)));
   if (!video_frame_validator->Initialize()) {
     LOG(ERROR) << "Failed to initialize SSIMVideoFrameValidator.";
     return nullptr;
   }
-
   return video_frame_validator;
 }
 
@@ -465,8 +519,10 @@
     const GetModelFrameCB& get_model_frame_cb,
     std::unique_ptr<VideoFrameProcessor> corrupt_frame_processor,
     ValidationMode validation_mode,
-    double tolerance)
-    : VideoFrameValidator(std::move(corrupt_frame_processor)),
+    double tolerance,
+    CropHelper crop_helper)
+    : VideoFrameValidator(std::move(corrupt_frame_processor),
+                          std::move(crop_helper)),
       get_model_frame_cb_(get_model_frame_cb),
       tolerance_(tolerance),
       validation_mode_(validation_mode) {}
@@ -478,8 +534,11 @@
                                   size_t frame_index) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(validator_thread_sequence_checker_);
   auto model_frame = get_model_frame_cb_.Run(frame_index);
-
   CHECK(model_frame);
+
+  if (ShouldCrop())
+    model_frame = CloneAndCropFrame(std::move(model_frame));
+
   double ssim = ComputeSSIM(*frame, *model_frame);
   DVLOGF(4) << "frame_index: " << frame_index << ", ssim: " << ssim;
   ssim_[frame_index] = ssim;
@@ -499,6 +558,7 @@
     average += ssim.second;
   }
   average /= ssim_.size();
+  DVLOGF(4) << "Average SSIM: " << average;
   if (average < tolerance_) {
     LOG(ERROR) << "Average SSIM is too low: " << average;
     return false;
diff --git a/media/gpu/test/video_frame_validator.h b/media/gpu/test/video_frame_validator.h
index 9bd658a..2c504b5a 100644
--- a/media/gpu/test/video_frame_validator.h
+++ b/media/gpu/test/video_frame_validator.h
@@ -48,6 +48,9 @@
   using GetModelFrameCB =
       base::RepeatingCallback<scoped_refptr<const VideoFrame>(size_t)>;
 
+  using CropHelper =
+      base::RepeatingCallback<gfx::Rect(const VideoFrame& frame)>;
+
   VideoFrameValidator(const VideoFrameValidator&) = delete;
   VideoFrameValidator& operator=(const VideoFrameValidator&) = delete;
 
@@ -78,13 +81,22 @@
   };
 
   VideoFrameValidator(
-      std::unique_ptr<VideoFrameProcessor> corrupt_frame_processor);
+      std::unique_ptr<VideoFrameProcessor> corrupt_frame_processor,
+      CropHelper crop_helper);
 
   // Start the frame validation thread.
   bool Initialize();
 
   SEQUENCE_CHECKER(validator_thread_sequence_checker_);
 
+  // Returns true if the processed frame or model frame should be cropped with
+  // CloneAndCropFrame() or VideoFrame::WrapVideoFrame().
+  bool ShouldCrop() const { return static_cast<bool>(crop_helper_); }
+
+  // This can be used by VideoFrameValidators to support cropping.
+  scoped_refptr<VideoFrame> CloneAndCropFrame(
+      scoped_refptr<const VideoFrame> frame) const;
+
  private:
   void CleanUpOnValidatorThread();
 
@@ -108,6 +120,10 @@
   // forwarded to. This can be used to e.g. write corrupted frames to disk.
   std::unique_ptr<VideoFrameProcessor> corrupt_frame_processor_;
 
+  // If |crop_helper_| is runnable, then ShouldCrop() will return true and
+  // CloneAndCropFrame() can be used.
+  const CropHelper crop_helper_;
+
   // The number of frames currently queued for validation.
   size_t num_frames_validating_ GUARDED_BY(frame_validator_lock_);
   // The results of invalid frame data.
@@ -122,6 +138,11 @@
   SEQUENCE_CHECKER(validator_sequence_checker_);
 };
 
+// Returns a cropping rectangle containing the bottom |row_height| rows of the
+// input frame. This can be used by VideoFrameValidator::CropHelper.
+gfx::Rect BottomRowCrop(int row_height, const VideoFrame& frame);
+constexpr int kDefaultBottomRowCropHeight = 2;
+
 // Validate by converting the frame to be validated to |validation_format| to
 // resolve pixel format differences on different platforms. Thereafter, it
 // compares md5 values of the mapped and converted buffer with golden md5
@@ -132,7 +153,8 @@
   static std::unique_ptr<MD5VideoFrameValidator> Create(
       const std::vector<std::string>& expected_frame_checksums,
       VideoPixelFormat validation_format = PIXEL_FORMAT_I420,
-      std::unique_ptr<VideoFrameProcessor> corrupt_frame_processor = nullptr);
+      std::unique_ptr<VideoFrameProcessor> corrupt_frame_processor = nullptr,
+      CropHelper crop_helper = CropHelper());
   ~MD5VideoFrameValidator() override;
 
  private:
@@ -141,7 +163,8 @@
   MD5VideoFrameValidator(
       const std::vector<std::string>& expected_frame_checksums,
       VideoPixelFormat validation_format,
-      std::unique_ptr<VideoFrameProcessor> corrupt_frame_processor);
+      std::unique_ptr<VideoFrameProcessor> corrupt_frame_processor,
+      CropHelper crop_helper);
   MD5VideoFrameValidator(const MD5VideoFrameValidator&) = delete;
   MD5VideoFrameValidator& operator=(const MD5VideoFrameValidator&) = delete;
 
@@ -167,7 +190,8 @@
   static std::unique_ptr<RawVideoFrameValidator> Create(
       const GetModelFrameCB& get_model_frame_cb,
       std::unique_ptr<VideoFrameProcessor> corrupt_frame_processor = nullptr,
-      uint8_t tolerance = kDefaultTolerance);
+      uint8_t tolerance = kDefaultTolerance,
+      CropHelper crop_helper = CropHelper());
   ~RawVideoFrameValidator() override;
 
  private:
@@ -176,7 +200,8 @@
   RawVideoFrameValidator(
       const GetModelFrameCB& get_model_frame_cb,
       std::unique_ptr<VideoFrameProcessor> corrupt_frame_processor,
-      uint8_t tolerance);
+      uint8_t tolerance,
+      CropHelper crop_helper);
 
   std::unique_ptr<MismatchedFrameInfo> Validate(
       scoped_refptr<const VideoFrame> frame,
@@ -186,8 +211,6 @@
   const uint8_t tolerance_;
 };
 
-// Validate by computing PSNR from the frame to be validated and the model frame
-// acquired by |get_model_frame_cb_|. If the PSNR value is equal to or more than
 // |tolerance_|, the validation on the frame passes.
 class PSNRVideoFrameValidator : public VideoFrameValidator {
  public:
@@ -197,7 +220,8 @@
       const GetModelFrameCB& get_model_frame_cb,
       std::unique_ptr<VideoFrameProcessor> corrupt_frame_processor = nullptr,
       ValidationMode validation_mode = ValidationMode::kThreshold,
-      double tolerance = kDefaultTolerance);
+      double tolerance = kDefaultTolerance,
+      CropHelper crop_helper = CropHelper());
   const std::map<size_t, double>& GetPSNRValues() const { return psnr_; }
   ~PSNRVideoFrameValidator() override;
 
@@ -208,7 +232,8 @@
       const GetModelFrameCB& get_model_frame_cb,
       std::unique_ptr<VideoFrameProcessor> corrupt_frame_processor,
       ValidationMode validation_mode,
-      double tolerance);
+      double tolerance,
+      CropHelper crop_helper);
 
   std::unique_ptr<MismatchedFrameInfo> Validate(
       scoped_refptr<const VideoFrame> frame,
@@ -217,6 +242,7 @@
   bool Passed() const override;
 
   const GetModelFrameCB get_model_frame_cb_;
+  const CropHelper crop_helper_;
   const double tolerance_;
   const ValidationMode validation_mode_;
   std::map<size_t, double> psnr_;
@@ -233,7 +259,8 @@
       const GetModelFrameCB& get_model_frame_cb,
       std::unique_ptr<VideoFrameProcessor> corrupt_frame_processor = nullptr,
       ValidationMode validation_mode = ValidationMode::kThreshold,
-      double tolerance = kDefaultTolerance);
+      double tolerance = kDefaultTolerance,
+      CropHelper crop_helper = CropHelper());
   const std::map<size_t, double>& GetSSIMValues() const { return ssim_; }
   ~SSIMVideoFrameValidator() override;
 
@@ -244,7 +271,8 @@
       const GetModelFrameCB& get_model_frame_cb,
       std::unique_ptr<VideoFrameProcessor> corrupt_frame_processor,
       ValidationMode validation_mode,
-      double tolerance);
+      double tolerance,
+      CropHelper crop_helper);
 
   std::unique_ptr<MismatchedFrameInfo> Validate(
       scoped_refptr<const VideoFrame> frame,
diff --git a/media/gpu/v4l2/generic_v4l2_device.cc b/media/gpu/v4l2/generic_v4l2_device.cc
index fa9a95b..c3c57a0 100644
--- a/media/gpu/v4l2/generic_v4l2_device.cc
+++ b/media/gpu/v4l2/generic_v4l2_device.cc
@@ -316,9 +316,8 @@
 
   // TODO(b/220336463): plumb the right color space.
   auto image =
-      base::MakeRefCounted<gl::GLImageNativePixmap>(size, buffer_format);
-  bool ret = image->Initialize(std::move(pixmap));
-  DCHECK(ret);
+      gl::GLImageNativePixmap::Create(size, buffer_format, std::move(pixmap));
+  DCHECK(image);
   return image;
 }
 
diff --git a/media/gpu/vaapi/vaapi_picture_native_pixmap_egl.cc b/media/gpu/vaapi/vaapi_picture_native_pixmap_egl.cc
index f739173..4098b3a 100644
--- a/media/gpu/vaapi/vaapi_picture_native_pixmap_egl.cc
+++ b/media/gpu/vaapi/vaapi_picture_native_pixmap_egl.cc
@@ -86,10 +86,10 @@
     return VaapiStatus::Codes::kBadContext;
 
   // TODO(b/220336463): plumb the right color space.
-  auto image =
-      base::MakeRefCounted<gl::GLImageNativePixmap>(visible_size_, format);
+  auto image = gl::GLImageNativePixmap::CreateFromTexture(visible_size_, format,
+                                                          texture_id_);
   // Create an EGLImage from a gl texture
-  if (!image->InitializeFromTexture(texture_id_)) {
+  if (!image) {
     DLOG(ERROR) << "Failed to initialize eglimage from texture id: "
                 << texture_id_;
     return VaapiStatus::Codes::kFailedToInitializeImage;
diff --git a/media/gpu/vaapi/vaapi_picture_native_pixmap_ozone.cc b/media/gpu/vaapi/vaapi_picture_native_pixmap_ozone.cc
index 2abb6886..0b646a87f5 100644
--- a/media/gpu/vaapi/vaapi_picture_native_pixmap_ozone.cc
+++ b/media/gpu/vaapi/vaapi_picture_native_pixmap_ozone.cc
@@ -89,13 +89,14 @@
 
   // TODO(b/220336463): plumb the right color space.
   auto image =
-      base::MakeRefCounted<gl::GLImageNativePixmap>(visible_size_, format);
-  if (!image->Initialize(std::move(pixmap))) {
+      gl::GLImageNativePixmap::Create(visible_size_, format, std::move(pixmap));
+  if (!image) {
     LOG(ERROR) << "Failed to create GLImage";
     return VaapiStatus::Codes::kFailedToInitializeImage;
   }
 
-  gl_image_ = image;
+  gl_image_ = std::move(image);
+
   if (!gl_image_->BindTexImage(texture_target_)) {
     LOG(ERROR) << "Failed to bind texture to GLImage";
     return VaapiStatus::Codes::kFailedToBindTexture;
diff --git a/media/mojo/clients/mojo_android_overlay_unittest.cc b/media/mojo/clients/mojo_android_overlay_unittest.cc
index 7e1447a8..aa426a8 100644
--- a/media/mojo/clients/mojo_android_overlay_unittest.cc
+++ b/media/mojo/clients/mojo_android_overlay_unittest.cc
@@ -138,7 +138,7 @@
     surface_ = gl::ScopedJavaSurface(surface_texture_.get());
     surface_key_ = gpu::GpuSurfaceTracker::Get()->AddSurfaceForNativeWidget(
         gpu::GpuSurfaceTracker::SurfaceRecord(
-            gfx::kNullAcceleratedWidget, surface_.j_surface(),
+            surface_.CopyRetainOwnership(),
             false /* can_be_used_with_surface_control */));
 
     mock_provider_.client_->OnSurfaceReady(surface_key_);
diff --git a/mojo/public/tools/bindings/generators/mojom_ts_generator.py b/mojo/public/tools/bindings/generators/mojom_ts_generator.py
index 8ee12d63..94296195 100644
--- a/mojo/public/tools/bindings/generators/mojom_ts_generator.py
+++ b/mojo/public/tools/bindings/generators/mojom_ts_generator.py
@@ -285,8 +285,7 @@
       # names.
       return False
     return (mojom.IsIntegralKind(kind) or mojom.IsFloatKind(kind)
-            or mojom.IsDoubleKind(kind) or mojom.IsStringKind(kind)
-            or mojom.IsEnumKind(kind))
+            or mojom.IsDoubleKind(kind) or mojom.IsStringKind(kind))
 
   def _TypescriptType(self, kind, maybe_nullable=False):
     def recurse_nullable(kind):
@@ -302,10 +301,13 @@
         else:
           return "%s[]" % get_type_name(kind.kind)
 
-      if (mojom.IsMapKind(kind) and self._IsStringableKind(kind.key_kind)
-          and not mojom.IsNullableKind(kind.key_kind)):
-        return "{[key: %s]: %s}" % (get_type_name(
-            kind.key_kind), recurse_nullable(kind.value_kind))
+      if (mojom.IsMapKind(kind) and not mojom.IsNullableKind(kind.key_kind)):
+        if mojom.IsEnumKind(kind.key_kind):
+          return "{[key in %s]?: %s}" % (get_type_name(
+              kind.key_kind), recurse_nullable(kind.value_kind))
+        if self._IsStringableKind(kind.key_kind):
+          return "{[key: %s]: %s}" % (get_type_name(
+              kind.key_kind), recurse_nullable(kind.value_kind))
 
       if mojom.IsMapKind(kind):
         return "Map<%s, %s>" % (recurse_nullable(
diff --git a/sandbox/linux/bpf_dsl/bpf_dsl.cc b/sandbox/linux/bpf_dsl/bpf_dsl.cc
index 2173963..87f8323f 100644
--- a/sandbox/linux/bpf_dsl/bpf_dsl.cc
+++ b/sandbox/linux/bpf_dsl/bpf_dsl.cc
@@ -52,9 +52,7 @@
 class TrapResultExprImpl : public internal::ResultExprImpl {
  public:
   TrapResultExprImpl(TrapRegistry::TrapFnc func, const void* arg, bool safe)
-      : func_(func), arg_(reinterpret_cast<uintptr_t>(arg)), safe_(safe) {
-    DCHECK(func_);
-  }
+      : handler_(func, arg, safe) {}
 
   TrapResultExprImpl(const TrapResultExprImpl&) = delete;
   TrapResultExprImpl& operator=(const TrapResultExprImpl&) = delete;
@@ -62,17 +60,14 @@
   ~TrapResultExprImpl() override {}
 
   CodeGen::Node Compile(PolicyCompiler* pc) const override {
-    return pc->Trap(func_, reinterpret_cast<const void*>(arg_), safe_);
+    return pc->Trap(handler_);
   }
 
-  bool HasUnsafeTraps() const override { return safe_ == false; }
-
+  bool HasUnsafeTraps() const override { return !handler_.safe; }
   bool IsDeny() const override { return true; }
 
  private:
-  TrapRegistry::TrapFnc func_;
-  uintptr_t arg_;  // Usually a pointer, but may be a smuggled int.
-  bool safe_;
+  TrapRegistry::Handler handler_;
 };
 
 class IfThenResultExprImpl : public internal::ResultExprImpl {
diff --git a/sandbox/linux/bpf_dsl/policy_compiler.cc b/sandbox/linux/bpf_dsl/policy_compiler.cc
index d3c42644..b94ec6e 100644
--- a/sandbox/linux/bpf_dsl/policy_compiler.cc
+++ b/sandbox/linux/bpf_dsl/policy_compiler.cc
@@ -435,17 +435,15 @@
     // The performance penalty for this extra round-trip to user-space is not
     // actually that bad, as we only ever pay it for denied system calls; and a
     // typical program has very few of these.
-    return Trap(ReturnErrno, reinterpret_cast<void*>(ret & SECCOMP_RET_DATA),
-                true);
+    return Trap(
+        {ReturnErrno, reinterpret_cast<void*>(ret & SECCOMP_RET_DATA), true});
   }
 
   return gen_.MakeInstruction(BPF_RET + BPF_K, ret);
 }
 
-CodeGen::Node PolicyCompiler::Trap(TrapRegistry::TrapFnc fnc,
-                                   const void* aux,
-                                   bool safe) {
-  uint16_t trap_id = registry_->Add(fnc, aux, safe);
+CodeGen::Node PolicyCompiler::Trap(const TrapRegistry::Handler& handler) {
+  uint16_t trap_id = registry_->Add(handler);
   return gen_.MakeInstruction(BPF_RET + BPF_K, SECCOMP_RET_TRAP + trap_id);
 }
 
diff --git a/sandbox/linux/bpf_dsl/policy_compiler.h b/sandbox/linux/bpf_dsl/policy_compiler.h
index 5439c0a..d35862af 100644
--- a/sandbox/linux/bpf_dsl/policy_compiler.h
+++ b/sandbox/linux/bpf_dsl/policy_compiler.h
@@ -60,7 +60,7 @@
 
   // Trap returns a CodeGen::Node to indicate the system call should
   // instead invoke a trap handler.
-  CodeGen::Node Trap(TrapRegistry::TrapFnc fnc, const void* aux, bool safe);
+  CodeGen::Node Trap(const TrapRegistry::Handler& handler);
 
   // MaskedEqual returns a CodeGen::Node that represents a conditional branch.
   // Argument "argno" (1..6) will be bitwise-AND'd with "mask" and compared
diff --git a/sandbox/linux/bpf_dsl/test_trap_registry.cc b/sandbox/linux/bpf_dsl/test_trap_registry.cc
index bf546d3f..2b71fce 100644
--- a/sandbox/linux/bpf_dsl/test_trap_registry.cc
+++ b/sandbox/linux/bpf_dsl/test_trap_registry.cc
@@ -11,14 +11,16 @@
 namespace sandbox {
 namespace bpf_dsl {
 
-TestTrapRegistry::TestTrapRegistry() : map_() {}
-TestTrapRegistry::~TestTrapRegistry() {}
+TestTrapRegistry::TestTrapRegistry() = default;
 
-uint16_t TestTrapRegistry::Add(TrapFnc fnc, const void* aux, bool safe) {
-  EXPECT_TRUE(safe);
+TestTrapRegistry::~TestTrapRegistry() = default;
+
+uint16_t TestTrapRegistry::Add(const Handler& handler) {
+  EXPECT_TRUE(handler.safe);
 
   const uint16_t next_id = map_.size() + 1;
-  return map_.insert(std::make_pair(Key(fnc, aux), next_id)).first->second;
+  auto result = map_.insert({handler, next_id});
+  return result.first->second;  // Old value if pre-existing handler.
 }
 
 bool TestTrapRegistry::EnableUnsafeTraps() {
diff --git a/sandbox/linux/bpf_dsl/test_trap_registry.h b/sandbox/linux/bpf_dsl/test_trap_registry.h
index 5c080bd..e51b440fc 100644
--- a/sandbox/linux/bpf_dsl/test_trap_registry.h
+++ b/sandbox/linux/bpf_dsl/test_trap_registry.h
@@ -24,13 +24,11 @@
 
   virtual ~TestTrapRegistry();
 
-  uint16_t Add(TrapFnc fnc, const void* aux, bool safe) override;
+  uint16_t Add(const Handler& handler) override;
   bool EnableUnsafeTraps() override;
 
  private:
-  using Key = std::pair<TrapFnc, const void*>;
-
-  std::map<Key, uint16_t> map_;
+  std::map<Handler, uint16_t> map_;
 };
 
 }  // namespace bpf_dsl
diff --git a/sandbox/linux/bpf_dsl/test_trap_registry_unittest.cc b/sandbox/linux/bpf_dsl/test_trap_registry_unittest.cc
index eaf2657..76ec007 100644
--- a/sandbox/linux/bpf_dsl/test_trap_registry_unittest.cc
+++ b/sandbox/linux/bpf_dsl/test_trap_registry_unittest.cc
@@ -39,7 +39,7 @@
   for (int i = 0; i < 2; ++i) {
     for (size_t j = 0; j < std::size(funcs); ++j) {
       // Trap IDs start at 1.
-      EXPECT_EQ(j + 1, traps.Add(funcs[j].fnc, funcs[j].aux, true));
+      EXPECT_EQ(j + 1, traps.Add({funcs[j].fnc, funcs[j].aux, true}));
     }
   }
 }
diff --git a/sandbox/linux/bpf_dsl/trap_registry.h b/sandbox/linux/bpf_dsl/trap_registry.h
index 00bf79c9..99bd439 100644
--- a/sandbox/linux/bpf_dsl/trap_registry.h
+++ b/sandbox/linux/bpf_dsl/trap_registry.h
@@ -7,6 +7,7 @@
 
 #include <stdint.h>
 
+#include "base/check.h"
 #include "sandbox/linux/system_headers/linux_seccomp.h"
 #include "sandbox/sandbox_export.h"
 
@@ -33,6 +34,22 @@
   // http://pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html
   typedef intptr_t (*TrapFnc)(const struct arch_seccomp_data& args, void* aux);
 
+  struct Handler {
+    Handler() = default;
+    Handler(TrapFnc f, const void* a, bool s)
+        : fnc(f), aux(reinterpret_cast<uintptr_t>(a)), safe(s) {
+      DCHECK(fnc);
+    }
+
+    bool operator<(const Handler& that) const {
+      return std::tie(fnc, aux, safe) < std::tie(that.fnc, that.aux, that.safe);
+    }
+
+    TrapFnc fnc = nullptr;
+    uintptr_t aux = 0;  // Usually a pointer, but may be a smuggled int.
+    bool safe = false;
+  };
+
   TrapRegistry(const TrapRegistry&) = delete;
   TrapRegistry& operator=(const TrapRegistry&) = delete;
 
@@ -40,7 +57,7 @@
   // non-zero trap ID that uniquely identifies the tuple for the life
   // time of the trap registry. If the same tuple is registered
   // multiple times, the same value will be returned each time.
-  virtual uint16_t Add(TrapFnc fnc, const void* aux, bool safe) = 0;
+  virtual uint16_t Add(const Handler& handler) = 0;
 
   // EnableUnsafeTraps tries to enable unsafe traps and returns
   // whether it was successful. This is a one-way operation.
diff --git a/sandbox/linux/seccomp-bpf/trap.cc b/sandbox/linux/seccomp-bpf/trap.cc
index f381216..7f262aa 100644
--- a/sandbox/linux/seccomp-bpf/trap.cc
+++ b/sandbox/linux/seccomp-bpf/trap.cc
@@ -227,7 +227,7 @@
                        SECCOMP_PARM6(ctx));
 #endif  // defined(__mips__)
   } else {
-    const TrapKey& trap = trap_array_[info->si_errno - 1];
+    const auto& trap = trap_array_[info->si_errno - 1];
     if (!trap.safe) {
       SetIsInSigHandler();
     }
@@ -260,12 +260,8 @@
   return;
 }
 
-bool Trap::TrapKey::operator<(const TrapKey& o) const {
-  return std::tie(fnc, aux, safe) < std::tie(o.fnc, o.aux, o.safe);
-}
-
-uint16_t Trap::Add(TrapFnc fnc, const void* aux, bool safe) {
-  if (!safe && !SandboxDebuggingAllowedByUser()) {
+uint16_t Trap::Add(const Handler& handler) {
+  if (!handler.safe && !SandboxDebuggingAllowedByUser()) {
     // Unless the user set the CHROME_SANDBOX_DEBUGGING environment variable,
     // we never return an ErrorCode that is marked as "unsafe". This also
     // means, the BPF compiler will never emit code that allow unsafe system
@@ -282,10 +278,6 @@
         "is enabled");
   }
 
-  // Each unique pair of TrapFnc and auxiliary data make up a distinct instance
-  // of a SECCOMP_RET_TRAP.
-  TrapKey key(fnc, aux, safe);
-
   // We return unique identifiers together with SECCOMP_RET_TRAP. This allows
   // us to associate trap with the appropriate handler. The kernel allows us
   // identifiers in the range from 0 to SECCOMP_RET_DATA (0xFFFF). We want to
@@ -295,7 +287,7 @@
   // calls that might be async-signal-unsafe.
   // In order to do so, we store all of our traps in a C-style trap_array_.
 
-  TrapIds::const_iterator iter = trap_ids_.find(key);
+  auto iter = trap_ids_.find(handler);
   if (iter != trap_ids_.end()) {
     // We have seen this pair before. Return the same id that we assigned
     // earlier.
@@ -328,8 +320,8 @@
   // events.
   if (trap_array_size_ >= trap_array_capacity_) {
     trap_array_capacity_ += kCapacityIncrement;
-    TrapKey* old_trap_array = trap_array_;
-    TrapKey* new_trap_array = new TrapKey[trap_array_capacity_];
+    auto* old_trap_array = trap_array_;
+    auto* new_trap_array = new TrapRegistry::Handler[trap_array_capacity_];
     std::copy_n(old_trap_array, trap_array_size_, new_trap_array);
 
     trap_array_ = new_trap_array;
@@ -341,8 +333,8 @@
   }
 
   uint16_t id = trap_array_size_ + 1;
-  trap_ids_[key] = id;
-  trap_array_[trap_array_size_] = key;
+  trap_ids_[handler] = id;
+  trap_array_[trap_array_size_] = handler;
   trap_array_size_++;
   return id;
 }
diff --git a/sandbox/linux/seccomp-bpf/trap.h b/sandbox/linux/seccomp-bpf/trap.h
index 67df58c..60a1ead 100644
--- a/sandbox/linux/seccomp-bpf/trap.h
+++ b/sandbox/linux/seccomp-bpf/trap.h
@@ -33,8 +33,7 @@
   Trap(const Trap&) = delete;
   Trap& operator=(const Trap&) = delete;
 
-  uint16_t Add(TrapFnc fnc, const void* aux, bool safe) override;
-
+  uint16_t Add(const Handler& handler) override;
   bool EnableUnsafeTraps() override;
 
   // Registry returns the trap registry used by Trap's SIGSYS handler,
@@ -46,18 +45,7 @@
   static bool SandboxDebuggingAllowedByUser();
 
  private:
-  struct TrapKey {
-    TrapKey() = default;
-    TrapKey(TrapFnc f, const void* a, bool s)
-        : fnc(f), aux(reinterpret_cast<uintptr_t>(a)), safe(s) {}
-
-    bool operator<(const TrapKey&) const;
-
-    TrapFnc fnc = nullptr;
-    uintptr_t aux = 0;  // Usually a pointer, but may be a smuggled int.
-    bool safe = false;
-  };
-  using TrapIds = std::map<TrapKey, uint16_t>;
+  using HandlerToIdMap = std::map<TrapRegistry::Handler, uint16_t>;
 
   // Our constructor is private. A shared global instance is created
   // automatically as needed.
@@ -73,18 +61,20 @@
   // dumps.
   void SigSys(int nr, LinuxSigInfo* info, ucontext_t* ctx)
       __attribute__((noinline));
+
   // We have a global singleton that handles all of our SIGSYS traps. This
   // variable must never be deallocated after it has been set up initially, as
   // there is no way to reset in-kernel BPF filters that generate SIGSYS
   // events.
   static Trap* global_trap_;
 
-  TrapIds trap_ids_;  // Maps from TrapKeys to numeric ids
-  // Array of TrapKeys indexed by ids.
+  HandlerToIdMap trap_ids_;  // Maps from Handlers to numeric ids
+
+  // Array of handlers indexed by ids.
   //
-  // This is not a raw_ptr as it is an owning pointer anyway, and is meant to be
-  // used between normal code and signal handlers.
-  RAW_PTR_EXCLUSION TrapKey* trap_array_ = nullptr;
+  // This is not a raw_ptr as it is an owning pointer anyway, and needs
+  // to be safe for signal handlers.
+  RAW_PTR_EXCLUSION TrapRegistry::Handler* trap_array_ = nullptr;
   size_t trap_array_size_ = 0;      // Currently used size of array
   size_t trap_array_capacity_ = 0;  // Currently allocated capacity of array
   bool has_unsafe_traps_ = false;   // Whether unsafe traps have been enabled
diff --git a/services/network/public/cpp/features.cc b/services/network/public/cpp/features.cc
index de04861b..5ad8138 100644
--- a/services/network/public/cpp/features.cc
+++ b/services/network/public/cpp/features.cc
@@ -325,7 +325,7 @@
 
 BASE_FEATURE(kPrefetchNoVarySearch,
              "PrefetchNoVarySearch",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 BASE_FEATURE(kPrerender2ContentSecurityPolicyExtensions,
              "Prerender2ContentSecurityPolicyExtensions",
diff --git a/services/network/public/cpp/parsed_headers.cc b/services/network/public/cpp/parsed_headers.cc
index f1f24c7..a8fa17e 100644
--- a/services/network/public/cpp/parsed_headers.cc
+++ b/services/network/public/cpp/parsed_headers.cc
@@ -101,6 +101,10 @@
     }
   }
 
+  // We're not checking that PrefetchNoVarySearch is enabled on the
+  // renderer side through the Origin Trial, as the network service
+  // doesn't know anything about blink.
+  // The code here only parses the No-Vary-Search header if it is present.
   if (base::FeatureList::IsEnabled(network::features::kPrefetchNoVarySearch))
     parsed_headers->no_vary_search = ParseNoVarySearch(*headers);
 
diff --git a/services/network/public/cpp/parsed_headers_unittest.cc b/services/network/public/cpp/parsed_headers_unittest.cc
index 9367be2..63d4e1d 100644
--- a/services/network/public/cpp/parsed_headers_unittest.cc
+++ b/services/network/public/cpp/parsed_headers_unittest.cc
@@ -32,7 +32,16 @@
 
 class NoVarySearchPrefetchDisabledTest
     : public ::testing::Test,
-      public ::testing::WithParamInterface<base::StringPiece> {};
+      public ::testing::WithParamInterface<base::StringPiece> {
+ public:
+  NoVarySearchPrefetchDisabledTest() {
+    scoped_feature_list_.InitAndDisableFeature(
+        network::features::kPrefetchNoVarySearch);
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
 
 TEST_P(NoVarySearchPrefetchDisabledTest, ParsingNVSReturnsDefaultURLVariance) {
   TestNVSDefaultURLVariance(GetParam());
diff --git a/services/network/web_transport.cc b/services/network/web_transport.cc
index a0e1ac53..2ccb558 100644
--- a/services/network/web_transport.cc
+++ b/services/network/web_transport.cc
@@ -217,7 +217,7 @@
 
   void Send() {
     MaySendFin();
-    while (outgoing_ && outgoing_->CanWrite()) {
+    while (readable_ && outgoing_ && outgoing_->CanWrite()) {
       const void* data = nullptr;
       uint32_t available = 0;
       MojoResult result = readable_->BeginReadData(
@@ -250,7 +250,7 @@
   }
 
   void MaySendFin() {
-    if (!outgoing_) {
+    if (!readable_ || !outgoing_) {
       return;
     }
     if (!has_seen_end_of_pipe_for_readable_ || !has_received_fin_from_client_) {
diff --git a/skia/config/SkUserConfig.h b/skia/config/SkUserConfig.h
index abc3086..50670b75 100644
--- a/skia/config/SkUserConfig.h
+++ b/skia/config/SkUserConfig.h
@@ -220,6 +220,9 @@
 // skia changes to implement new api is completed.
 #define SK_USE_LEGACY_VMA_MEMORY_QUERY
 
+// Temporary until web tests can be rebaselined (skbug.com/13752)
+#define SK_DISABLE_RASTER_PIPELINE_SAMPLING_FIXES
+
 ///////////////////////// Imported from BUILD.gn and skia_common.gypi
 
 /* In some places Skia can use static initializers for global initialization,
diff --git a/testing/buildbot/chromium.perf.json b/testing/buildbot/chromium.perf.json
index ad0ef524..33b2164 100644
--- a/testing/buildbot/chromium.perf.json
+++ b/testing/buildbot/chromium.perf.json
@@ -361,9 +361,8 @@
           "can_use_on_swarming_builders": true,
           "dimension_sets": [
             {
-              "device_os": "MASTER",
               "device_os_flavor": "google",
-              "device_type": "wembley",
+              "device_type": "wembley_2GB",
               "os": "Android",
               "pool": "chrome.tests.perf"
             }
diff --git a/testing/buildbot/client.v8.fyi.json b/testing/buildbot/client.v8.fyi.json
index 66122af05..38ffd154 100644
--- a/testing/buildbot/client.v8.fyi.json
+++ b/testing/buildbot/client.v8.fyi.json
@@ -514,6 +514,41 @@
       },
       {
         "args": [
+          "mediapipe",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --force_higher_performance_gpu --use-cmd-decoder=passthrough --use-gl=angle"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "mediapipe_passthrough_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "gpu": "10de:2184-440.100",
+              "os": "Ubuntu-18.04.5|Ubuntu-18.04.6",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
           "pixel",
           "--show-stdout",
           "--browser=release",
@@ -629,13 +664,96 @@
       },
       {
         "args": [
+          "pixel",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-vulkan=native --disable-vulkan-fallback-to-gl-for-testing --enable-features=Vulkan --use-gl=angle --use-angle=gl --use-cmd-decoder=passthrough",
+          "--dont-restore-color-profile-after-test",
+          "--test-machine-name",
+          "${buildername}",
+          "--git-revision=${got_cr_revision}"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "vulkan_pixel_skia_gold_test",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "gpu": "10de:2184-440.100",
+              "os": "Ubuntu-18.04.5|Ubuntu-18.04.6",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
+          "webgl2_conformance",
+          "--show-stdout",
+          "--browser=release",
+          "--passthrough",
+          "-v",
+          "--stable-jobs",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-gl=angle --use-angle=gl --use-cmd-decoder=passthrough --force_high_performance_gpu",
+          "--webgl-conformance-version=2.0.1",
+          "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl2_conformance_tests_output.json",
+          "--jobs=4"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "webgl2_conformance_gl_passthrough_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "dimension_sets": [
+            {
+              "gpu": "10de:2184-440.100",
+              "os": "Ubuntu-18.04.5|Ubuntu-18.04.6",
+              "pool": "chromium.tests.gpu"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 20
+        },
+        "test_id_prefix": "ninja://chrome/test:telemetry_gpu_integration_test/"
+      },
+      {
+        "args": [
           "webgl1_conformance",
           "--show-stdout",
           "--browser=release",
           "--passthrough",
           "-v",
           "--stable-jobs",
-          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --force_high_performance_gpu",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-gl=angle --use-angle=gl --use-cmd-decoder=passthrough --force_high_performance_gpu",
           "--read-abbreviated-json-results-from=../../content/test/data/gpu/webgl_conformance_tests_output.json",
           "--jobs=4"
         ],
@@ -644,7 +762,7 @@
           "args": [],
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
-        "name": "webgl_conformance_tests",
+        "name": "webgl_conformance_gl_passthrough_tests",
         "resultdb": {
           "enable": true,
           "has_native_resultdb_integration": true
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index f982529..8a4ae8b 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -3658,6 +3658,17 @@
       'VR Linux',
     ],
   },
+  'vulkan_pixel_skia_gold_test': {
+    'replacements': {
+      # The V8 builders pass the V8 revision for ${got_revision}, so instead
+      # use ${got_cr_revision}, which is only set on the V8 bots.
+      'Linux V8 FYI Release (NVIDIA)': {
+        'args': {
+          '--git-revision': '${got_cr_revision}',
+        },
+      },
+    },
+  },
   'wayland_client_perftests': {
     'remove_from': [
       'linux-chromeos-dbg',  # https://crbug.com/859307
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index e10483b..eb0efd105 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -6318,7 +6318,7 @@
           'linux_nvidia_gtx_1660_stable',
         ],
         'test_suites': {
-          'gpu_telemetry_tests': 'gpu_v8_desktop_passthrough_telemetry_tests',
+          'gpu_telemetry_tests': 'gpu_fyi_linux_release_vulkan_telemetry_tests',
         },
       },
       'Linux V8 FYI Release - pointer compression (NVIDIA)': {
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index efb322c..40e21512 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -775,6 +775,33 @@
                     "enable_features": [
                         "AssistMultiWord"
                     ]
+                },
+                {
+                    "name": "EnabledWithGboard_20221213",
+                    "params": {
+                        "group": "gboard"
+                    },
+                    "enable_features": [
+                        "AssistMultiWord"
+                    ]
+                },
+                {
+                    "name": "EnabledWithGboardD_20221213",
+                    "params": {
+                        "group": "gboard_d"
+                    },
+                    "enable_features": [
+                        "AssistMultiWord"
+                    ]
+                },
+                {
+                    "name": "EnabledWithGboardE_20221213",
+                    "params": {
+                        "group": "gboard_e"
+                    },
+                    "enable_features": [
+                        "AssistMultiWord"
+                    ]
                 }
             ]
         }
@@ -904,6 +931,21 @@
             ]
         }
     ],
+    "AutocorrectByDefault": [
+        {
+            "platforms": [
+                "chromeos"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled_20221208",
+                    "enable_features": [
+                        "AutocorrectByDefault"
+                    ]
+                }
+            ]
+        }
+    ],
     "AutofillAcrossIframes": [
         {
             "platforms": [
@@ -3174,30 +3216,6 @@
             ]
         }
     ],
-    "ContextualPageActions": [
-        {
-            "platforms": [
-                "android"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "params": {
-                        "action_chip": "true",
-                        "action_chip_time_ms": "6000",
-                        "action_chip_with_different_color": "false",
-                        "enable_ui": "true",
-                        "reader_mode_session_rate_limiting": "true"
-                    },
-                    "enable_features": [
-                        "ContextualPageActionPriceTracking",
-                        "ContextualPageActionReaderMode",
-                        "ContextualPageActions"
-                    ]
-                }
-            ]
-        }
-    ],
     "CrOSBluetoothLLPrivacy": [
         {
             "platforms": [
@@ -5410,21 +5428,6 @@
             ]
         }
     ],
-    "HttpsOnlyMode": [
-        {
-            "platforms": [
-                "ios"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "HttpsOnlyMode"
-                    ]
-                }
-            ]
-        }
-    ],
     "IOSAutofillBranding": [
         {
             "platforms": [
@@ -7391,7 +7394,7 @@
                 {
                     "name": "Enabled",
                     "params": {
-                        "omnibox_answer_color_reversal_countries": "zh-CN,zh-TW,ja-JP,ko-KR",
+                        "omnibox_answer_color_reversal_countries": "ja-JP,ko-KR,zh-CN,zh-TW",
                         "omnibox_answer_color_reversal_finance_only": "true"
                     },
                     "enable_features": [
diff --git a/third_party/blink/public/mojom/speculation_rules/speculation_rules.mojom b/third_party/blink/public/mojom/speculation_rules/speculation_rules.mojom
index 99d0cc2..5acb443 100644
--- a/third_party/blink/public/mojom/speculation_rules/speculation_rules.mojom
+++ b/third_party/blink/public/mojom/speculation_rules/speculation_rules.mojom
@@ -22,6 +22,8 @@
   // sent.
   UpdateSpeculationCandidates(
       array<SpeculationCandidate> candidates);
+  // Enables matching prefetches according to No-Vary-Search response headers.
+  EnableNoVarySearchSupport();
 };
 
 // The action that is proposed.
diff --git a/third_party/blink/renderer/core/fetch/request.cc b/third_party/blink/renderer/core/fetch/request.cc
index 8de39af4..ae9eb2e50 100644
--- a/third_party/blink/renderer/core/fetch/request.cc
+++ b/third_party/blink/renderer/core/fetch/request.cc
@@ -793,6 +793,14 @@
     input_request->BodyBuffer()->CloseAndLockAndDisturb();
   }
 
+  // Back/forward-cache is interested in use of the "Authorization" header.
+  if (r->getHeaders() &&
+      r->getHeaders()->has("Authorization", exception_state)) {
+    execution_context->GetScheduler()->RegisterStickyFeature(
+        SchedulingPolicy::Feature::kAuthorizationHeader,
+        {SchedulingPolicy::DisableBackForwardCache()});
+  }
+
   // "Return |r|."
   return r;
 }
diff --git a/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.cc b/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.cc
index 0955b9b..f38352a 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.cc
@@ -512,9 +512,9 @@
         fixedpos_containing_block_offset = converter.ToLogical(
             multicol_info->fixedpos_containing_block.Offset(),
             fixedpos_containing_block_fragment->Size());
-        fixedpos_containing_block_rel_offset = converter.ToLogical(
+        fixedpos_containing_block_rel_offset = RelativeInsetToLogical(
             multicol_info->fixedpos_containing_block.RelativeOffset(),
-            fixedpos_containing_block_fragment->Size());
+            GetWritingDirection());
         fixedpos_containing_block_rel_offset += relative_offset;
         // We want the fixedpos containing block offset to be the offset from
         // the containing block to the top of the fragmentation context root,
@@ -612,9 +612,8 @@
     LogicalOffset containing_block_offset =
         converter.ToLogical(descendant.containing_block.Offset(),
                             containing_block_fragment->Size());
-    LogicalOffset containing_block_rel_offset =
-        converter.ToLogical(descendant.containing_block.RelativeOffset(),
-                            containing_block_fragment->Size());
+    LogicalOffset containing_block_rel_offset = RelativeInsetToLogical(
+        descendant.containing_block.RelativeOffset(), GetWritingDirection());
     containing_block_rel_offset += relative_offset;
     if (!fragment.IsFragmentainerBox())
       containing_block_offset += offset;
@@ -658,9 +657,9 @@
       fixedpos_containing_block_offset =
           converter.ToLogical(descendant.fixedpos_containing_block.Offset(),
                               fixedpos_containing_block_fragment->Size());
-      fixedpos_containing_block_rel_offset = converter.ToLogical(
+      fixedpos_containing_block_rel_offset = RelativeInsetToLogical(
           descendant.fixedpos_containing_block.RelativeOffset(),
-          fixedpos_containing_block_fragment->Size());
+          GetWritingDirection());
       fixedpos_containing_block_rel_offset += relative_offset;
       if (!fragment.IsFragmentainerBox())
         fixedpos_containing_block_offset += offset;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
index b1e7189..228df94 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
@@ -1016,9 +1016,9 @@
         fixedpos_containing_block_offset =
             converter.ToLogical(descendant.fixedpos_containing_block.Offset(),
                                 fixedpos_containing_block_fragment->Size());
-        fixedpos_containing_block_rel_offset = converter.ToLogical(
+        fixedpos_containing_block_rel_offset = RelativeInsetToLogical(
             descendant.fixedpos_containing_block.RelativeOffset(),
-            fixedpos_containing_block_fragment->Size());
+            writing_direction);
       }
 
       NGInlineContainer<LogicalOffset> inline_container(
diff --git a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_positioned_node.h b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_positioned_node.h
index 7295d86..f1ffccc5 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_positioned_node.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_positioned_node.h
@@ -380,6 +380,20 @@
   MulticolCollection multicols_with_pending_oofs;
 };
 
+inline PhysicalOffset RelativeInsetToPhysical(
+    LogicalOffset relative_inset,
+    WritingDirectionMode writing_direction) {
+  return relative_inset.ConvertToPhysical(writing_direction, PhysicalSize(),
+                                          PhysicalSize());
+}
+
+inline LogicalOffset RelativeInsetToLogical(
+    PhysicalOffset relative_inset,
+    WritingDirectionMode writing_direction) {
+  return relative_inset.ConvertToLogical(writing_direction, PhysicalSize(),
+                                         PhysicalSize());
+}
+
 }  // namespace blink
 
 WTF_ALLOW_CLEAR_UNUSED_SLOTS_WITH_MEM_FUNCTIONS(
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 cc03ca42..401c132 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
@@ -102,8 +102,8 @@
   return NGContainingBlock<PhysicalOffset>(
       containing_block.Offset().ConvertToPhysical(
           builder->Style().GetWritingDirection(), outer_size, inner_size),
-      containing_block.RelativeOffset().ConvertToPhysical(
-          builder->Style().GetWritingDirection(), outer_size, inner_size),
+      RelativeInsetToPhysical(containing_block.RelativeOffset(),
+                              builder->Style().GetWritingDirection()),
       containing_block.Fragment(), containing_block.IsInsideColumnSpanner(),
       containing_block.RequiresContentBeforeBreaking());
 }
diff --git a/third_party/blink/renderer/core/loader/threadable_loader.cc b/third_party/blink/renderer/core/loader/threadable_loader.cc
index b56f74b8..b0377b28 100644
--- a/third_party/blink/renderer/core/loader/threadable_loader.cc
+++ b/third_party/blink/renderer/core/loader/threadable_loader.cc
@@ -112,13 +112,6 @@
 }
 
 void ThreadableLoader::Start(ResourceRequest request) {
-  // Back/forward-cache is interested in use of the "Authorization" header.
-  if (request.HttpHeaderField("Authorization")) {
-    execution_context_->GetScheduler()->RegisterStickyFeature(
-        SchedulingPolicy::Feature::kAuthorizationHeader,
-        {SchedulingPolicy::DisableBackForwardCache()});
-  }
-
   const auto request_context = request.GetRequestContext();
   if (request.GetMode() == network::mojom::RequestMode::kNoCors) {
     SECURITY_CHECK(cors::IsNoCorsAllowedContext(request_context));
diff --git a/third_party/blink/renderer/core/paint/html_canvas_painter_test.cc b/third_party/blink/renderer/core/paint/html_canvas_painter_test.cc
index b3e7c1c4..ae25b305 100644
--- a/third_party/blink/renderer/core/paint/html_canvas_painter_test.cc
+++ b/third_party/blink/renderer/core/paint/html_canvas_painter_test.cc
@@ -75,8 +75,6 @@
   std::unique_ptr<Canvas2DLayerBridge> bridge = MakeCanvas2DLayerBridge(size);
   element->SetResourceProviderForTesting(nullptr, std::move(bridge), size);
   ASSERT_EQ(context, element->RenderingContext());
-  ASSERT_TRUE(context->IsComposited());
-  ASSERT_TRUE(element->IsAccelerated());
 
   // Force the page to paint.
   element->PreFinalizeFrame();
@@ -84,6 +82,9 @@
   element->PostFinalizeFrame();
   UpdateAllLifecyclePhasesForTest();
 
+  ASSERT_TRUE(context->IsComposited());
+  ASSERT_TRUE(element->IsAccelerated());
+
   // Fetch the layer associated with the <canvas>, and check that it was
   // correctly configured in the layer tree.
   const cc::Layer* layer = context->CcLayer();
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
index 9336ba1..b797ed6 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
@@ -4240,6 +4240,10 @@
     const LayoutObject& object) {
   DCHECK(CanDoDeferredTransformNodeUpdate(object));
 
+  // GeometryMapper depends on paint properties. This is typically called from
+  // the PrePaintTreeWalk, but we may skip that for this direct update.
+  GeometryMapper::ClearCache();
+
   auto& box = To<LayoutBox>(object);
   PhysicalSize size = PhysicalSize(box.Size());
   FragmentData* fragment_data = &object.GetMutableForPainting().FirstFragment();
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc
index fb1a0e1f..3c67a49 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.cc
@@ -87,12 +87,6 @@
       .PaintProperties();
 }
 
-const GeometryMapperTransformCache&
-PaintPropertyTreeBuilderTest::GetTransformCache(
-    const TransformPaintPropertyNode& transform) {
-  return transform.GetTransformCache();
-}
-
 void PaintPropertyTreeBuilderTest::SetUp() {
   EnableCompositing();
   RenderingTest::SetUp();
@@ -7383,30 +7377,4 @@
   EXPECT_FALSE(properties->Effect()->OutputClip());
 }
 
-TEST_P(PaintPropertyTreeBuilderTest, TransformChangesInvalidateGeometryMapper) {
-  SetBodyInnerHTML(R"HTML(
-    <style>#div { width:10px; height:10px; transform:translateX(9px); }</style>
-    <div id="div" style="transform: translateX(5px);"></div>
-  )HTML");
-
-  const auto* properties = PaintPropertiesForElement("div");
-  const auto& transform_cache = GetTransformCache(*properties->Transform());
-  EXPECT_TRUE(transform_cache.IsValid());
-
-  // Change the transform and ensure the geometry mapper cache is invalidated.
-  auto* div = GetDocument().getElementById("div");
-  div->removeAttribute(html_names::kStyleAttr);
-  UpdateAllLifecyclePhasesExceptPaint();
-  EXPECT_FALSE(transform_cache.IsValid());
-
-  UpdateAllLifecyclePhasesForTest();
-  EXPECT_TRUE(transform_cache.IsValid());
-
-  // Make a color change and ensure the geometry mapper cache is not
-  // invalidated.
-  div->setAttribute(html_names::kStyleAttr, "background: green;");
-  UpdateAllLifecyclePhasesExceptPaint();
-  EXPECT_TRUE(transform_cache.IsValid());
-}
-
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.h b/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.h
index 419bcbf..76e25ed2 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.h
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder_test.h
@@ -13,7 +13,6 @@
 namespace blink {
 
 class ClipPaintPropertyNode;
-class GeometryMapperTransformCache;
 class ScrollPaintPropertyNode;
 class TransformPaintPropertyNode;
 struct PhysicalOffset;
@@ -42,9 +41,6 @@
 
   const ObjectPaintProperties* PaintPropertiesForElement(const char* name);
 
-  const GeometryMapperTransformCache& GetTransformCache(
-      const TransformPaintPropertyNode&);
-
   static unsigned NumFragments(const LayoutObject* obj) {
     unsigned count = 0;
     auto* fragment = &obj->FirstFragment();
diff --git a/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc b/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc
index 6829a72..9d6f47a 100644
--- a/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc
+++ b/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc
@@ -27,6 +27,7 @@
 #include "third_party/blink/renderer/core/paint/object_paint_invalidator.h"
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
 #include "third_party/blink/renderer/core/paint/paint_property_tree_printer.h"
+#include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 
 namespace blink {
@@ -55,10 +56,11 @@
 
   PrePaintTreeWalkContext context;
 
-#if DCHECK_IS_ON()
-  bool needed_tree_builder_context_update =
+  // GeometryMapper depends on paint properties.
+  bool needs_tree_builder_context_update =
       NeedsTreeBuilderContextUpdate(root_frame_view, context);
-#endif
+  if (needs_tree_builder_context_update)
+    GeometryMapper::ClearCache();
 
   VisualViewport& visual_viewport =
       root_frame_view.GetPage()->GetVisualViewport();
@@ -72,7 +74,7 @@
   paint_invalidator_.ProcessPendingDelayedPaintInvalidations();
 
 #if DCHECK_IS_ON()
-  if (needed_tree_builder_context_update && VLOG_IS_ON(1))
+  if (needs_tree_builder_context_update && VLOG_IS_ON(1))
     ShowAllPropertyTrees(root_frame_view);
 #endif
 
diff --git a/third_party/blink/renderer/core/speculation_rules/build.gni b/third_party/blink/renderer/core/speculation_rules/build.gni
index 784aa8c..5a40c52 100644
--- a/third_party/blink/renderer/core/speculation_rules/build.gni
+++ b/third_party/blink/renderer/core/speculation_rules/build.gni
@@ -18,6 +18,7 @@
 ]
 
 blink_core_tests_speculation_rules = [
+  "no_vary_search_origin_trial_test.cc",
   "speculation_rule_set_test.cc",
   "speculation_rules_header_test.cc",
   "speculation_rules_origin_trial_test.cc",
diff --git a/third_party/blink/renderer/core/speculation_rules/document_speculation_rules.cc b/third_party/blink/renderer/core/speculation_rules/document_speculation_rules.cc
index 53343d46..46920683 100644
--- a/third_party/blink/renderer/core/speculation_rules/document_speculation_rules.cc
+++ b/third_party/blink/renderer/core/speculation_rules/document_speculation_rules.cc
@@ -313,6 +313,7 @@
   auto* execution_context = GetSupplementable()->GetExecutionContext();
   if (!execution_context)
     return;
+
   has_pending_update_ = true;
   execution_context->GetAgent()->event_loop()->EnqueueMicrotask(
       WTF::BindOnce(&DocumentSpeculationRules::UpdateSpeculationCandidates,
@@ -389,6 +390,11 @@
   // Add candidates derived from document rule predicates.
   AddLinkBasedSpeculationCandidates(candidates);
 
+  if (!sent_is_part_of_no_vary_search_trial_ &&
+      RuntimeEnabledFeatures::NoVarySearchPrefetchEnabled(execution_context)) {
+    sent_is_part_of_no_vary_search_trial_ = true;
+    host->EnableNoVarySearchSupport();
+  }
   host->UpdateSpeculationCandidates(std::move(candidates));
 }
 
diff --git a/third_party/blink/renderer/core/speculation_rules/document_speculation_rules.h b/third_party/blink/renderer/core/speculation_rules/document_speculation_rules.h
index ebe2986..51836fb 100644
--- a/third_party/blink/renderer/core/speculation_rules/document_speculation_rules.h
+++ b/third_party/blink/renderer/core/speculation_rules/document_speculation_rules.h
@@ -106,6 +106,7 @@
 
   bool has_pending_update_ = false;
   bool initialized_ = false;
+  bool sent_is_part_of_no_vary_search_trial_ = false;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/speculation_rules/no_vary_search_origin_trial_test.cc b/third_party/blink/renderer/core/speculation_rules/no_vary_search_origin_trial_test.cc
new file mode 100644
index 0000000..fe1b2f88
--- /dev/null
+++ b/third_party/blink/renderer/core/speculation_rules/no_vary_search_origin_trial_test.cc
@@ -0,0 +1,141 @@
+// Copyright 2021 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+
+#include "base/run_loop.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/browser_interface_broker_proxy.h"
+#include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/common/features_generated.h"
+#include "third_party/blink/public/common/origin_trials/scoped_test_origin_trial_policy.h"
+#include "third_party/blink/public/platform/web_url_response.h"
+#include "third_party/blink/public/web/web_navigation_params.h"
+#include "third_party/blink/renderer/core/frame/local_dom_window.h"
+#include "third_party/blink/renderer/core/frame/local_frame.h"
+#include "third_party/blink/renderer/core/frame/settings.h"
+#include "third_party/blink/renderer/core/html/html_head_element.h"
+#include "third_party/blink/renderer/core/html/html_script_element.h"
+#include "third_party/blink/renderer/core/speculation_rules/stub_speculation_host.h"
+#include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
+#include "third_party/blink/renderer/platform/weborigin/kurl.h"
+#include "third_party/blink/renderer/platform/wtf/functional.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
+
+namespace blink {
+namespace {
+
+void CommitTestNavigation(
+    LocalFrame& frame,
+    const KURL& url,
+    const Vector<std::pair<String, String>>& response_headers) {
+  auto navigation_params = std::make_unique<WebNavigationParams>();
+  navigation_params->url = url;
+  WebNavigationParams::FillStaticResponse(navigation_params.get(), "text/html",
+                                          "UTF-8", "<!DOCTYPE html>");
+  for (const auto& [header, value] : response_headers)
+    navigation_params->response.AddHttpHeaderField(header, value);
+  frame.Loader().CommitNavigation(std::move(navigation_params), nullptr);
+}
+
+HTMLScriptElement* InsertSpeculationRules(Document& document,
+                                          const String& speculation_script) {
+  HTMLScriptElement* script =
+      MakeGarbageCollected<HTMLScriptElement>(document, CreateElementFlags());
+  script->setAttribute(html_names::kTypeAttr, "SpEcUlAtIoNrUlEs");
+  script->setText(speculation_script);
+  document.head()->appendChild(script);
+  return script;
+}
+
+// Generated by:
+//  tools/origin_trials/generate_token.py --version 3 --expire-days 3650
+//  https://speculationrules.test NoVarySearchPrefetch
+// Token details:
+//  Version: 3
+//  Origin: https://speculationrules.test:443
+//  Is Subdomain: None
+//  Is Third Party: None
+//  Usage Restriction: None
+//  Feature: NoVarySearchPrefetch
+//  Expiry: 1985830923 (2032-12-05 03:42:03 UTC)
+//  Signature (Base64):
+//  fFyfaSvsR9K2Wqm/Nvo3KWQsdLUaEGHZj+La5IUXRK/LdCrvtdggLOtoQiEZwkL8rJJz3S+/Mfa6I/LOY0KOCA==
+[[maybe_unused]] constexpr char kNoVarySearchPrefetchToken[] =
+    "A3xcn2kr7EfStlqpvzb6NylkLHS1GhBh2Y/i2uSFF0Svy3Qq77XYICzraEIhGcJC/"
+    "KySc90vvzH2uiPyzmNCjggAAABoeyJvcmlnaW4iOiAiaHR0cHM6Ly9zcGVjdWxhdGlvbnJ1bGV"
+    "zLnRlc3Q6NDQzIiwgImZlYXR1cmUiOiAiTm9WYXJ5U2VhcmNoUHJlZmV0Y2giLCAiZXhwaXJ5I"
+    "jogMTk4NTgzMDkyM30=";
+
+TEST(PrefetchNoVarySearchOriginTrialTest, CanEnableFromToken) {
+  ScopedTestOriginTrialPolicy using_test_keys;
+  DummyPageHolder page_holder;
+  LocalFrame& frame = page_holder.GetFrame();
+
+  CommitTestNavigation(frame, KURL("https://speculationrules.test/"),
+                       {{"Origin-Trial", kNoVarySearchPrefetchToken}});
+
+  // This should have enabled the origin trial and all its dependent features.
+  EXPECT_TRUE(
+      RuntimeEnabledFeatures::NoVarySearchPrefetchEnabled(frame.DomWindow()));
+  EXPECT_TRUE(RuntimeEnabledFeatures::SpeculationRulesPrefetchProxyEnabled(
+      frame.DomWindow()));
+}
+
+TEST(PrefetchNoVarySearchOriginTrialTest, DoesNotEnableWithoutToken) {
+  ScopedTestOriginTrialPolicy using_test_keys;
+  DummyPageHolder page_holder;
+  LocalFrame& frame = page_holder.GetFrame();
+
+  // Do not send the Origin-Trial token.
+  CommitTestNavigation(frame, KURL("https://speculationrules.test/"), {});
+
+  // This should not have enabled the origin trial.
+  EXPECT_FALSE(
+      RuntimeEnabledFeatures::NoVarySearchPrefetchEnabled(frame.DomWindow()));
+}
+
+void NoVarySearchPrefetchEnabledTest(StubSpeculationHost& speculation_host) {
+  DummyPageHolder page_holder;
+  LocalFrame& frame = page_holder.GetFrame();
+  frame.GetSettings()->SetScriptEnabled(true);
+
+  auto& broker = frame.DomWindow()->GetBrowserInterfaceBroker();
+  broker.SetBinderForTesting(
+      mojom::blink::SpeculationHost::Name_,
+      WTF::BindRepeating(&StubSpeculationHost::BindUnsafe,
+                         WTF::Unretained(&speculation_host)));
+
+  base::RunLoop run_loop;
+  speculation_host.SetDoneClosure(run_loop.QuitClosure());
+
+  const String speculation_script =
+      R"({"prefetch": [
+           {"source": "list",
+            "urls": ["https://example.com/foo"],
+            "requires": ["anonymous-client-ip-when-cross-origin"]}
+         ]})";
+  InsertSpeculationRules(page_holder.GetDocument(), speculation_script);
+  run_loop.Run();
+
+  broker.SetBinderForTesting(mojom::blink::SpeculationHost::Name_, {});
+}
+
+TEST(PrefetchNoVarySearchOriginTrialTest,
+     EnabledNoVarySearchPrefetchInBrowser) {
+  ScopedNoVarySearchPrefetchForTest enable_no_vary_search_prefetch_{true};
+  StubSpeculationHost speculation_host;
+  NoVarySearchPrefetchEnabledTest(speculation_host);
+  EXPECT_TRUE(speculation_host.sent_no_vary_search_support_to_browser());
+}
+
+TEST(PrefetchNoVarySearchOriginTrialTest,
+     DoNotEnableNoVarySearchPrefetchInBrowser) {
+  StubSpeculationHost speculation_host;
+  NoVarySearchPrefetchEnabledTest(speculation_host);
+  EXPECT_FALSE(speculation_host.sent_no_vary_search_support_to_browser());
+}
+
+}  // namespace
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/speculation_rules/stub_speculation_host.cc b/third_party/blink/renderer/core/speculation_rules/stub_speculation_host.cc
index 4d6bfdd..21fef772 100644
--- a/third_party/blink/renderer/core/speculation_rules/stub_speculation_host.cc
+++ b/third_party/blink/renderer/core/speculation_rules/stub_speculation_host.cc
@@ -32,4 +32,8 @@
     std::move(done_closure_).Run();
 }
 
+void StubSpeculationHost::EnableNoVarySearchSupport() {
+  sent_no_vary_search_support_to_browser_ = true;
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/speculation_rules/stub_speculation_host.h b/third_party/blink/renderer/core/speculation_rules/stub_speculation_host.h
index 41c5f4f2..7226199 100644
--- a/third_party/blink/renderer/core/speculation_rules/stub_speculation_host.h
+++ b/third_party/blink/renderer/core/speculation_rules/stub_speculation_host.h
@@ -37,13 +37,21 @@
   // mojom::blink::SpeculationHost.
   void UpdateSpeculationCandidates(Candidates candidates) override;
 
+  // mojom::blink::SpeculationHost.
+  void EnableNoVarySearchSupport() override;
+
   void OnConnectionLost();
 
   bool is_bound() const { return receiver_.is_bound(); }
 
+  bool sent_no_vary_search_support_to_browser() const {
+    return sent_no_vary_search_support_to_browser_;
+  }
+
  private:
   mojo::Receiver<SpeculationHost> receiver_{this};
   Vector<mojom::blink::SpeculationCandidatePtr> candidates_;
+  bool sent_no_vary_search_support_to_browser_ = false;
   base::OnceClosure done_closure_;
   base::RepeatingCallback<void(const Candidates&)> candidates_updated_callback_;
 };
diff --git a/third_party/blink/renderer/core/timing/performance.cc b/third_party/blink/renderer/core/timing/performance.cc
index 0f343820a..f998630 100644
--- a/third_party/blink/renderer/core/timing/performance.cc
+++ b/third_party/blink/renderer/core/timing/performance.cc
@@ -483,41 +483,49 @@
   if (!window) {
     return entries;
   }
-  LocalFrame* parent_frame = window->GetFrame();
-  if (!parent_frame) {
+  LocalFrame* root_frame = window->GetFrame();
+  if (!root_frame) {
     return entries;
   }
+  const SecurityOrigin* root_origin =
+      ExecutionContext::From(script_state)->GetSecurityOrigin();
 
   HeapDeque<Member<Frame>> queue;
-  queue.push_back(parent_frame);
+  queue.push_back(root_frame);
 
   while (!queue.empty()) {
     Frame* current_frame = queue.TakeFirst();
 
-    if (LocalFrame* local_frame = DynamicTo<LocalFrame>(current_frame);
-        local_frame && !local_frame->IsCrossOriginToNearestMainFrame()) {
+    if (LocalFrame* local_frame = DynamicTo<LocalFrame>(current_frame)) {
       // Get the Performance object from the current frame.
       LocalDOMWindow* current_window = local_frame->DomWindow();
       // As we verified that the frame this was called with is not detached when
       // entring this loop, we can assume that all its children are also not
       // detached, and hence have a window object.
       DCHECK(current_window);
-      WindowPerformance* window_performance =
-          DOMWindowPerformance::performance(*current_window);
 
-      // Get the performance entries based on entry_type input.
-      PerformanceEntryVector current_entries;
-      if (entry_type.IsNull()) {
-        current_entries = window_performance->GetEntriesForCurrentFrame();
-      } else {
-        current_entries =
-            window_performance->GetEntriesByTypeForCurrentFrame(entry_type);
+      // Validate that the child frame's origin is the same as the root
+      // frame.
+      const SecurityOrigin* current_origin =
+          current_window->GetSecurityOrigin();
+      if (root_origin->IsSameOriginWith(current_origin)) {
+        WindowPerformance* window_performance =
+            DOMWindowPerformance::performance(*current_window);
+
+        // Get the performance entries based on entry_type input.
+        PerformanceEntryVector current_entries;
+        if (entry_type.IsNull()) {
+          current_entries = window_performance->GetEntriesForCurrentFrame();
+        } else {
+          current_entries =
+              window_performance->GetEntriesByTypeForCurrentFrame(entry_type);
+        }
+
+        entries.AppendVector(current_entries);
       }
-
-      entries.AppendVector(current_entries);
     }
 
-    // Add both Local and Remote Frames to the queue.
+    // Add both Local and Remote Frame children to the queue.
     for (Frame* child = current_frame->FirstChild(); child;
          child = child->NextSibling()) {
       queue.push_back(child);
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
index 020b4d9..e46a8fcb 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
@@ -189,7 +189,10 @@
 }
 
 bool CanvasRenderingContext2D::IsComposited() const {
-  return IsAccelerated();
+  if (Canvas2DLayerBridge* layer_bridge = canvas()->GetCanvas2DLayerBridge()) {
+    return layer_bridge->IsComposited();
+  }
+  return false;
 }
 
 void CanvasRenderingContext2D::Stop() {
diff --git a/third_party/blink/renderer/modules/webaudio/async_audio_decoder.cc b/third_party/blink/renderer/modules/webaudio/async_audio_decoder.cc
index 08b2f69a..661b0cea 100644
--- a/third_party/blink/renderer/modules/webaudio/async_audio_decoder.cc
+++ b/third_party/blink/renderer/modules/webaudio/async_audio_decoder.cc
@@ -32,7 +32,6 @@
 #include "third_party/blink/renderer/modules/webaudio/audio_buffer.h"
 #include "third_party/blink/renderer/modules/webaudio/base_audio_context.h"
 #include "third_party/blink/renderer/platform/audio/audio_bus.h"
-#include "third_party/blink/renderer/platform/audio/audio_file_reader.h"
 #include "third_party/blink/renderer/platform/bindings/cross_thread_copier.h"
 #include "third_party/blink/renderer/platform/bindings/exception_context.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
@@ -79,7 +78,7 @@
     scoped_refptr<base::SingleThreadTaskRunner> task_runner,
     const ExceptionContext& exception_context) {
   DCHECK(!IsMainThread());
-  scoped_refptr<AudioBus> bus = CreateBusFromInMemoryAudioFile(
+  scoped_refptr<AudioBus> bus = AudioBus::CreateBusFromInMemoryAudioFile(
       audio_data->Data(), audio_data->ByteLength(), false, sample_rate);
 
   // Decoding is finished, but we need to do the callbacks on the main thread.
diff --git a/third_party/blink/renderer/modules/webaudio/audio_buffer.cc b/third_party/blink/renderer/modules/webaudio/audio_buffer.cc
index 5933e32..2e03280 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_buffer.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_buffer.cc
@@ -33,7 +33,6 @@
 #include "third_party/blink/renderer/bindings/modules/v8/v8_audio_buffer_options.h"
 #include "third_party/blink/renderer/modules/webaudio/base_audio_context.h"
 #include "third_party/blink/renderer/platform/audio/audio_bus.h"
-#include "third_party/blink/renderer/platform/audio/audio_file_reader.h"
 #include "third_party/blink/renderer/platform/audio/audio_utilities.h"
 #include "third_party/blink/renderer/platform/bindings/exception_messages.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index 9c01b81..7c8fc318 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -304,7 +304,6 @@
     "audio/audio_dsp_kernel.h",
     "audio/audio_dsp_kernel_processor.cc",
     "audio/audio_dsp_kernel_processor.h",
-    "audio/audio_file_reader.h",
     "audio/audio_io_callback.h",
     "audio/audio_processor.cc",
     "audio/audio_processor.h",
diff --git a/third_party/blink/renderer/platform/audio/audio_bus.cc b/third_party/blink/renderer/platform/audio/audio_bus.cc
index 9a4d923..e8a48f1 100644
--- a/third_party/blink/renderer/platform/audio/audio_bus.cc
+++ b/third_party/blink/renderer/platform/audio/audio_bus.cc
@@ -37,7 +37,6 @@
 #include "base/ranges/algorithm.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/web_audio_bus.h"
-#include "third_party/blink/renderer/platform/audio/audio_file_reader.h"
 #include "third_party/blink/renderer/platform/audio/denormal_disabler.h"
 #include "third_party/blink/renderer/platform/audio/sinc_resampler.h"
 #include "third_party/blink/renderer/platform/audio/vector_math.h"
@@ -744,10 +743,11 @@
                                                 sample_rate);
 }
 
-scoped_refptr<AudioBus> CreateBusFromInMemoryAudioFile(const void* data,
-                                                       size_t data_size,
-                                                       bool mix_to_mono,
-                                                       float sample_rate) {
+scoped_refptr<AudioBus> AudioBus::CreateBusFromInMemoryAudioFile(
+    const void* data,
+    size_t data_size,
+    bool mix_to_mono,
+    float sample_rate) {
   scoped_refptr<AudioBus> audio_bus =
       DecodeAudioFileData(static_cast<const char*>(data), data_size);
   if (!audio_bus.get()) {
diff --git a/third_party/blink/renderer/platform/audio/audio_bus.h b/third_party/blink/renderer/platform/audio/audio_bus.h
index 3290de3..39c64c0e 100644
--- a/third_party/blink/renderer/platform/audio/audio_bus.h
+++ b/third_party/blink/renderer/platform/audio/audio_bus.h
@@ -70,6 +70,16 @@
                                         uint32_t length,
                                         bool allocate = true);
 
+  // Pass in 0.0 for sampleRate to use the file's sample-rate, otherwise a
+  // sample-rate conversion to the requested sampleRate will be made (if it
+  // doesn't already match the file's sample-rate).  The created buffer will
+  // have its sample-rate set correctly to the result.
+  static scoped_refptr<AudioBus> CreateBusFromInMemoryAudioFile(
+      const void* data,
+      size_t data_size,
+      bool mix_to_mono,
+      float sample_rate);
+
   AudioBus(const AudioBus&) = delete;
   AudioBus& operator=(const AudioBus&) = delete;
 
diff --git a/third_party/blink/renderer/platform/audio/audio_file_reader.h b/third_party/blink/renderer/platform/audio/audio_file_reader.h
deleted file mode 100644
index 8a499b79..0000000
--- a/third_party/blink/renderer/platform/audio/audio_file_reader.h
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2010 Google Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1.  Redistributions of source code must retain the above copyright
- *     notice, this list of conditions and the following disclaimer.
- * 2.  Redistributions in binary form must reproduce the above copyright
- *     notice, this list of conditions and the following disclaimer in the
- *     documentation and/or other materials provided with the distribution.
- * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
- *     its contributors may be used to endorse or promote products derived
- *     from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_AUDIO_AUDIO_FILE_READER_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_AUDIO_AUDIO_FILE_READER_H_
-
-#include "base/memory/scoped_refptr.h"
-#include "third_party/blink/renderer/platform/platform_export.h"
-
-namespace blink {
-
-class AudioBus;
-
-// For both create functions:
-// Pass in 0.0 for sampleRate to use the file's sample-rate, otherwise a
-// sample-rate conversion to the requested sampleRate will be made (if it
-// doesn't already match the file's sample-rate).  The created buffer will have
-// its sample-rate set correctly to the result.
-
-PLATFORM_EXPORT scoped_refptr<AudioBus> CreateBusFromInMemoryAudioFile(
-    const void* data,
-    size_t data_size,
-    bool mix_to_mono,
-    float sample_rate);
-
-PLATFORM_EXPORT scoped_refptr<AudioBus> CreateBusFromAudioFile(
-    const char* file_path,
-    bool mix_to_mono,
-    float sample_rate);
-
-// May pass in 0.0 for sampleRate in which case it will use the AudioBus's
-// sampleRate
-PLATFORM_EXPORT void WriteBusToAudioFile(AudioBus* bus,
-                                         const char* file_path,
-                                         double file_sample_rate);
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_AUDIO_AUDIO_FILE_READER_H_
diff --git a/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.cc b/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.cc
index 262d8ac..ceeb6c8 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.cc
@@ -152,6 +152,25 @@
   return ShouldAccelerate();
 }
 
+bool Canvas2DLayerBridge::IsComposited() const {
+  if (IsHibernating()) {
+    return false;
+  }
+
+  if (UNLIKELY(!resource_host_)) {
+    return false;
+  }
+
+  CanvasResourceProvider* resource_provider =
+      resource_host_->ResourceProvider();
+  if (UNLIKELY(!resource_provider)) {
+    return false;
+  }
+
+  return resource_provider->SupportsDirectCompositing() &&
+         !resource_host_->LowLatencyEnabled();
+}
+
 static void HibernateWrapper(base::WeakPtr<Canvas2DLayerBridge> bridge,
                              base::TimeTicks /*idleDeadline*/) {
   if (bridge) {
@@ -268,14 +287,13 @@
 
   if (resource_provider && resource_provider->IsValid()) {
 #if DCHECK_IS_ON()
-    // If resource provider is accelerated, a layer should already exist.
+    // If resource provider is composited, a layer should already exist.
     // unless this is a canvas in low latency mode.
     // If this DCHECK fails, it probably means that
     // CanvasRenderingContextHost::GetOrCreateCanvasResourceProvider() was
     // called on a 2D context before this function.
-    if (IsAccelerated()) {
-      DCHECK(!!layer_ ||
-             (resource_host_ && resource_host_->LowLatencyEnabled()));
+    if (IsComposited()) {
+      DCHECK(!!layer_);
     }
 #endif
     return resource_provider;
@@ -310,7 +328,7 @@
   // TODO crbug/1090081: Check possibility to move DidDraw inside Clear.
   DidDraw();
 
-  if (IsAccelerated() && !layer_) {
+  if (IsComposited() && !layer_) {
     layer_ = cc::TextureLayer::CreateForMailbox(this);
     layer_->SetIsDrawable(true);
     layer_->SetHitTestable(true);
@@ -320,6 +338,7 @@
                                cc::PaintFlags::FilterQuality::kNone);
     layer_->SetHDRConfiguration(resource_host_->GetHDRMode(),
                                 resource_host_->GetHDRMetadata());
+    layer_->SetFlipped(!resource_provider->IsOriginTopLeft());
   }
   // After the page becomes visible and successfully restored the canvas
   // resource provider, set |lose_context_in_background_| to false.
@@ -747,7 +766,7 @@
     constexpr unsigned kMaxCanvasAnimationBacklog = 2;
     if (frames_since_last_commit_ >=
         static_cast<int>(kMaxCanvasAnimationBacklog)) {
-      if (IsAccelerated() && !rate_limiter_) {
+      if (IsComposited() && !rate_limiter_) {
         rate_limiter_ = std::make_unique<SharedContextRateLimiter>(
             kMaxCanvasAnimationBacklog);
       }
@@ -759,8 +778,9 @@
 }
 
 void Canvas2DLayerBridge::DoPaintInvalidation(const gfx::Rect& dirty_rect) {
-  if (layer_ && raster_mode_ == RasterMode::kGPU)
+  if (layer_ && IsComposited()) {
     layer_->SetNeedsDisplayRect(dirty_rect);
+  }
 }
 
 scoped_refptr<StaticBitmapImage> Canvas2DLayerBridge::NewImageSnapshot() {
diff --git a/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.h b/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.h
index af3cb0fe..66a9272 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.h
+++ b/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.h
@@ -95,6 +95,8 @@
   virtual void DidRestoreCanvasMatrixClipStack(cc::PaintCanvas*) {}
   virtual bool IsAccelerated() const;
 
+  bool IsComposited() const;
+
   // This may recreate CanvasResourceProvider
   cc::PaintCanvas* GetPaintCanvas();
   bool IsValid();
diff --git a/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge_test.cc b/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge_test.cc
index d620c77..3160461 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge_test.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge_test.cc
@@ -1019,4 +1019,29 @@
   EXPECT_FALSE(bridge->HasRateLimiterForTesting());
 }
 
+TEST_F(Canvas2DLayerBridgeTest, SoftwareCanvasIsCompositedIfImageChromium) {
+  ScopedTestingPlatformSupport<GpuMemoryBufferTestPlatform> platform;
+  ScopedCanvas2dImageChromiumForTest canvas_2d_image_chromium(true);
+  const_cast<gpu::Capabilities&>(SharedGpuContext::ContextProviderWrapper()
+                                     ->ContextProvider()
+                                     ->GetCapabilities())
+      .gpu_memory_buffer_formats.Add(gfx::BufferFormat::BGRA_8888);
+  std::unique_ptr<Canvas2DLayerBridge> bridge =
+      MakeBridge(gfx::Size(300, 150), RasterMode::kCPU, kNonOpaque);
+  EXPECT_TRUE(bridge->IsValid());
+  DrawSomething(bridge.get());
+  EXPECT_FALSE(bridge->IsAccelerated());
+  EXPECT_TRUE(bridge->IsComposited());
+}
+
+TEST_F(Canvas2DLayerBridgeTest, SoftwareCanvasNotCompositedIfNotImageChromium) {
+  ScopedCanvas2dImageChromiumForTest canvas_2d_image_chromium(false);
+  std::unique_ptr<Canvas2DLayerBridge> bridge =
+      MakeBridge(gfx::Size(300, 150), RasterMode::kCPU, kNonOpaque);
+  EXPECT_TRUE(bridge->IsValid());
+  DrawSomething(bridge.get());
+  EXPECT_FALSE(bridge->IsAccelerated());
+  EXPECT_FALSE(bridge->IsComposited());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource.cc b/third_party/blink/renderer/platform/graphics/canvas_resource.cc
index 2989bd8..f0b976c3 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource.cc
@@ -130,7 +130,12 @@
     scoped_refptr<CanvasResource>&& resource,
     const gpu::SyncToken& sync_token,
     bool lost_resource) {
-  if (!resource)
+  // If there is a LastUnrefCallback, we need to abort because recycling the
+  // resource now will prevent the LastUnrefCallback from ever being called.
+  // In such cases, ReleaseFrameResources will be called again when
+  // CanvasResourceDispatcher destroys the corresponding FrameResource object,
+  // at which time this resource will be safely recycled.
+  if (!resource || resource->HasLastUnrefCallback())
     return;
 
   resource->WaitSyncToken(sync_token);
@@ -144,8 +149,9 @@
   if (lost_resource)
     resource->NotifyResourceLost();
   if (resource_provider && !lost_resource && resource->IsRecycleable() &&
-      resource->HasOneRef())
+      resource->HasOneRef()) {
     resource_provider->RecycleResource(std::move(resource));
+  }
 }
 
 bool CanvasResource::PrepareTransferableResource(
@@ -699,7 +705,10 @@
   auto surface = SkSurface::MakeRasterDirect(CreateSkImageInfo(),
                                              gpu_memory_buffer_->memory(0),
                                              gpu_memory_buffer_->stride(0));
-  surface->getCanvas()->drawImage(image, 0, 0);
+
+  SkPixmap pixmap;
+  image->peekPixels(&pixmap);
+  surface->writePixels(pixmap, 0, 0);
   auto* sii =
       ContextProviderWrapper()->ContextProvider()->SharedImageInterface();
   gpu_memory_buffer_->Unmap();
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource.h b/third_party/blink/renderer/platform/graphics/canvas_resource.h
index f80baf6..8a74aa78 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource.h
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource.h
@@ -83,6 +83,8 @@
     last_unref_callback_ = std::move(callback);
   }
 
+  bool HasLastUnrefCallback() { return !!last_unref_callback_; }
+
   // We perform a lazy copy on write if the canvas content needs to be updated
   // while its current resource is in use. In order to avoid re-allocating
   // resources, its preferable to reuse a resource if its no longer in use.
diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource_provider_test.cc b/third_party/blink/renderer/platform/graphics/canvas_resource_provider_test.cc
index 4c4c7a25..86fb8ec 100644
--- a/third_party/blink/renderer/platform/graphics/canvas_resource_provider_test.cc
+++ b/third_party/blink/renderer/platform/graphics/canvas_resource_provider_test.cc
@@ -251,6 +251,44 @@
   EXPECT_EQ(original_mailbox, provider->Snapshot()->GetMailboxHolder().mailbox);
 }
 
+TEST_F(CanvasResourceProviderTest, NoRecycleIfLastRefCallback) {
+  const SkImageInfo kInfo = SkImageInfo::MakeN32Premul(10, 10);
+
+  const uint32_t shared_image_usage_flags =
+      gpu::SHARED_IMAGE_USAGE_DISPLAY_READ | gpu::SHARED_IMAGE_USAGE_SCANOUT;
+
+  auto provider = CanvasResourceProvider::CreateSharedImageProvider(
+      kInfo, cc::PaintFlags::FilterQuality::kMedium,
+      CanvasResourceProvider::ShouldInitialize::kCallClear,
+      context_provider_wrapper_, RasterMode::kGPU, true /*is_origin_top_left*/,
+      shared_image_usage_flags);
+
+  ASSERT_TRUE(provider->IsValid());
+
+  scoped_refptr<StaticBitmapImage> snapshot1 = provider->Snapshot();
+  ASSERT_TRUE(snapshot1);
+
+  // Set up a LastUnrefCallback that recycles the resource asynchronously,
+  // similarly to what OffscreenCanvasPlaceholder would do.
+  provider->ProduceCanvasResource()->SetLastUnrefCallback(
+      base::BindOnce([](scoped_refptr<CanvasResource> resource) {}));
+
+  // Resource updated after draw.
+  provider->Canvas()->clear(SkColors::kWhite);
+  provider->FlushCanvas();
+  scoped_refptr<StaticBitmapImage> snapshot2 = provider->Snapshot();
+  EXPECT_NE(snapshot2->GetMailboxHolder().mailbox,
+            snapshot1->GetMailboxHolder().mailbox);
+
+  auto snapshot1_mailbox = snapshot1->GetMailboxHolder().mailbox;
+  snapshot1.reset();  // resource not recycled due to LastUnrefCallback
+  provider->Canvas()->clear(SkColors::kBlack);
+  provider->FlushCanvas();
+  scoped_refptr<StaticBitmapImage> snapshot3 = provider->Snapshot();
+  // confirm resource is not recycled.
+  EXPECT_NE(snapshot3->GetMailboxHolder().mailbox, snapshot1_mailbox);
+}
+
 TEST_F(CanvasResourceProviderTest,
        CanvasResourceProviderSharedImageCopyOnWriteDisabled) {
   auto* fake_context = static_cast<FakeWebGraphicsContext3DProvider*>(
diff --git a/third_party/blink/renderer/platform/graphics/gradient.cc b/third_party/blink/renderer/platform/graphics/gradient.cc
index 315e111..50f2af1 100644
--- a/third_party/blink/renderer/platform/graphics/gradient.cc
+++ b/third_party/blink/renderer/platform/graphics/gradient.cc
@@ -104,7 +104,7 @@
   if (stops_.empty()) {
     // A gradient with no stops must be transparent black.
     pos.push_back(WebCoreDoubleToSkScalar(0));
-    colors.push_back(SK_ColorTRANSPARENT);
+    colors.push_back(SkColors::kTransparent);
   } else if (stops_.front().stop > 0) {
     // Copy the first stop to 0.0. The first stop position may have a slight
     // rounding error, but we don't care in this float comparison, since
@@ -112,20 +112,20 @@
     // with a stop at (0 + epsilon).
     pos.push_back(WebCoreDoubleToSkScalar(0));
     if (color_filter_) {
-      colors.push_back(color_filter_->filterColor(
-          stops_.front().color.ToSkColorDeprecated()));
+      colors.push_back(color_filter_->filterColor4f(
+          stops_.front().color.toSkColor4f(), nullptr, nullptr));
     } else {
-      colors.push_back(stops_.front().color.ToSkColorDeprecated());
+      colors.push_back(stops_.front().color.toSkColor4f());
     }
   }
 
   for (const auto& stop : stops_) {
     pos.push_back(WebCoreDoubleToSkScalar(stop.stop));
     if (color_filter_) {
-      colors.push_back(
-          color_filter_->filterColor(stop.color.ToSkColorDeprecated()));
+      colors.push_back(color_filter_->filterColor4f(stop.color.toSkColor4f(),
+                                                    nullptr, nullptr));
     } else {
-      colors.push_back(stop.color.ToSkColorDeprecated());
+      colors.push_back(stop.color.toSkColor4f());
     }
   }
 
@@ -232,10 +232,8 @@
 
   if (is_dark_mode_enabled_) {
     for (auto& color : colors) {
-      color = EnsureDarkModeFilter()
-                  .InvertColorIfNeeded(SkColor4f::FromColor(color),
-                                       DarkModeFilter::ElementRole::kBackground)
-                  .toSkColor();
+      color = EnsureDarkModeFilter().InvertColorIfNeeded(
+          color, DarkModeFilter::ElementRole::kBackground);
     }
   }
   // The matrix type is mutable and set lazily. Force it to be computed here to
@@ -298,23 +296,16 @@
                                   SkTileMode tile_mode,
                                   SkGradientShader::Interpolation interpolation,
                                   const SkMatrix& local_matrix,
-                                  SkColor fallback_color) const override {
+                                  SkColor4f fallback_color) const override {
     if (GetDegenerateHandling() == DegenerateHandling::kDisallow &&
         p0_ == p1_) {
       return PaintShader::MakeEmpty();
     }
 
     SkPoint pts[2] = {FloatPointToSkPoint(p0_), FloatPointToSkPoint(p1_)};
-    // TODO(crbug/1308932): Remove this helper vector colors4f and make all
-    // SkColor4f.
-    std::vector<SkColor4f> colors4f;
-    colors4f.reserve(colors.size());
-    for (auto& color : colors)
-      colors4f.push_back(SkColor4f::FromColor(color));
     return PaintShader::MakeLinearGradient(
-        pts, colors4f.data(), pos.data(), static_cast<int>(colors4f.size()),
-        tile_mode, interpolation, 0 /* flags */, &local_matrix,
-        SkColor4f::FromColor(fallback_color));
+        pts, colors.data(), pos.data(), static_cast<int>(colors.size()),
+        tile_mode, interpolation, 0 /* flags */, &local_matrix, fallback_color);
   }
 
  private:
@@ -348,7 +339,7 @@
                                   SkTileMode tile_mode,
                                   SkGradientShader::Interpolation interpolation,
                                   const SkMatrix& local_matrix,
-                                  SkColor fallback_color) const override {
+                                  SkColor4f fallback_color) const override {
     const SkMatrix* matrix = &local_matrix;
     absl::optional<SkMatrix> adjusted_local_matrix;
     if (aspect_ratio_ != 1) {
@@ -370,17 +361,10 @@
       return PaintShader::MakeEmpty();
     }
 
-    // TODO(crbug/1308932): Remove this helper vector colors4f and make all
-    // SkColor4f.
-    std::vector<SkColor4f> colors4f;
-    colors4f.reserve(colors.size());
-    for (auto& color : colors)
-      colors4f.push_back(SkColor4f::FromColor(color));
     return PaintShader::MakeTwoPointConicalGradient(
         FloatPointToSkPoint(p0_), radius0, FloatPointToSkPoint(p1_), radius1,
-        colors4f.data(), pos.data(), static_cast<int>(colors4f.size()),
-        tile_mode, interpolation, 0 /* flags */, matrix,
-        SkColor4f::FromColor(fallback_color));
+        colors.data(), pos.data(), static_cast<int>(colors.size()), tile_mode,
+        interpolation, 0 /* flags */, matrix, fallback_color);
   }
 
  private:
@@ -415,7 +399,7 @@
                                   SkTileMode tile_mode,
                                   SkGradientShader::Interpolation interpolation,
                                   const SkMatrix& local_matrix,
-                                  SkColor fallback_color) const override {
+                                  SkColor4f fallback_color) const override {
     if (GetDegenerateHandling() == DegenerateHandling::kDisallow &&
         start_angle_ == end_angle_) {
       return PaintShader::MakeEmpty();
@@ -432,17 +416,10 @@
       matrix = &*adjusted_local_matrix;
     }
 
-    // TODO(crbug/1308932): Remove this helper vector colors4f and make all
-    // SkColor4f.
-    std::vector<SkColor4f> colors4f;
-    colors4f.reserve(colors.size());
-    for (auto& color : colors)
-      colors4f.push_back(SkColor4f::FromColor(color));
     return PaintShader::MakeSweepGradient(
-        position_.x(), position_.y(), colors4f.data(), pos.data(),
-        static_cast<int>(colors4f.size()), tile_mode, start_angle_, end_angle_,
-        interpolation, 0 /* flags */, matrix,
-        SkColor4f::FromColor(fallback_color));
+        position_.x(), position_.y(), colors.data(), pos.data(),
+        static_cast<int>(colors.size()), tile_mode, start_angle_, end_angle_,
+        interpolation, 0 /* flags */, matrix, fallback_color);
   }
 
  private:
diff --git a/third_party/blink/renderer/platform/graphics/gradient.h b/third_party/blink/renderer/platform/graphics/gradient.h
index 7706f8c6..05e00d6 100644
--- a/third_party/blink/renderer/platform/graphics/gradient.h
+++ b/third_party/blink/renderer/platform/graphics/gradient.h
@@ -128,14 +128,14 @@
  protected:
   Gradient(Type, GradientSpreadMethod, ColorInterpolation, DegenerateHandling);
 
-  using ColorBuffer = Vector<SkColor, 8>;
+  using ColorBuffer = Vector<SkColor4f, 8>;
   using OffsetBuffer = Vector<SkScalar, 8>;
   virtual sk_sp<PaintShader> CreateShader(const ColorBuffer&,
                                           const OffsetBuffer&,
                                           SkTileMode,
                                           SkGradientShader::Interpolation,
                                           const SkMatrix&,
-                                          SkColor) const = 0;
+                                          SkColor4f) const = 0;
 
   DegenerateHandling GetDegenerateHandling() const {
     return degenerate_handling_;
diff --git a/third_party/blink/renderer/platform/graphics/paint/clip_paint_property_node.h b/third_party/blink/renderer/platform/graphics/paint/clip_paint_property_node.h
index 2dd3c43..e4f5dba 100644
--- a/third_party/blink/renderer/platform/graphics/paint/clip_paint_property_node.h
+++ b/third_party/blink/renderer/platform/graphics/paint/clip_paint_property_node.h
@@ -199,8 +199,14 @@
       : ClipPaintPropertyNodeOrAlias(parent), state_(std::move(state)) {}
 
   void AddChanged(PaintPropertyChangeType changed) {
+    // TODO(crbug.com/814815): This is a workaround of the bug. When the bug is
+    // fixed, change the following condition to
+    //   DCHECK(!clip_cache_ || !clip_cache_->IsValid());
     DCHECK_NE(PaintPropertyChangeType::kUnchanged, changed);
-    GeometryMapperClipCache::ClearCache();
+    if (clip_cache_ && clip_cache_->IsValid()) {
+      DLOG(WARNING) << "Clip tree changed without invalidating the cache.";
+      GeometryMapperClipCache::ClearCache();
+    }
     PaintPropertyNode::AddChanged(changed);
   }
 
diff --git a/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h b/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h
index a1040d2..30d6776 100644
--- a/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h
+++ b/third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h
@@ -426,11 +426,15 @@
   }
 
   void AddChanged(PaintPropertyChangeType changed) {
+    // TODO(crbug.com/814815): This is a workaround of the bug. When the bug is
+    // fixed, change the following condition to
+    //   DCHECK(!transform_cache_ || !transform_cache_->IsValid());
     DCHECK_NE(PaintPropertyChangeType::kUnchanged, changed);
-
-    GeometryMapperTransformCache::ClearCache();
-    GeometryMapperClipCache::ClearCache();
-
+    if (transform_cache_ && transform_cache_->IsValid()) {
+      DLOG(WARNING) << "Transform tree changed without invalidating the cache.";
+      GeometryMapperTransformCache::ClearCache();
+      GeometryMapperClipCache::ClearCache();
+    }
     TransformPaintPropertyNodeOrAlias::AddChanged(changed);
   }
 
@@ -439,7 +443,6 @@
   friend class GeometryMapperTest;
   friend class GeometryMapperTransformCache;
   friend class GeometryMapperTransformCacheTest;
-  friend class PaintPropertyTreeBuilderTest;
 
   const GeometryMapperTransformCache& GetTransformCache() const {
     if (!transform_cache_)
diff --git a/third_party/blink/renderer/platform/network/http_parsers_test.cc b/third_party/blink/renderer/platform/network/http_parsers_test.cc
index 668c1a57d..ae7d707 100644
--- a/third_party/blink/renderer/platform/network/http_parsers_test.cc
+++ b/third_party/blink/renderer/platform/network/http_parsers_test.cc
@@ -860,7 +860,16 @@
 
 class NoVarySearchPrefetchDisabledTest
     : public ::testing::Test,
-      public ::testing::WithParamInterface<base::StringPiece> {};
+      public ::testing::WithParamInterface<base::StringPiece> {
+ public:
+  NoVarySearchPrefetchDisabledTest() {
+    scoped_feature_list_.InitAndDisableFeature(
+        network::features::kPrefetchNoVarySearch);
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
 
 TEST_P(NoVarySearchPrefetchDisabledTest, ParsingNVSReturnsDefaultURLVariance) {
   const auto parsed_headers =
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 9489e7b70..a437ceb 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -1888,6 +1888,16 @@
       origin_trial_feature_name: "NotificationTriggers",
       status: "experimental",
     },
+    // Origin trial to experiment with No-Vary-Search in Prefetch Cache
+    // See crbug.com/1378075. Depends on SpeculationRulesPrefetchProxy to be
+    // enabled.
+    // On the browser side, the trial needs features::kPrefetchUseContentRefactor
+    // enabled. If features::kPrefetchUseContentRefactor is disabled, then
+    // the code will behave as if PrefetchNoVarySearch is disabled.
+    {
+      name: "NoVarySearchPrefetch",
+      origin_trial_feature_name: "NoVarySearchPrefetch",
+    },
     {
       name: "OffMainThreadCSSPaint",
       status: "stable",
@@ -2617,7 +2627,7 @@
       status: "stable",
       base_feature: "SpeculationRulesPrefetchProxy",
       copied_from_base_feature_if: "overridden",
-      implied_by: ["SpeculationRulesPrefetchFuture"],
+      implied_by: ["SpeculationRulesPrefetchFuture", "NoVarySearchPrefetch"],
     },
     {
       "name": "SpeculationRulesPrefetchWithSubresources",
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index 86583ca..a3de662 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -614,6 +614,13 @@
     "args": ["--disable-site-isolation-trials"],
     "expires": "Jul 1, 2023"
   },
+  {
+    "prefix": "not-site-per-process-nonexclusive",
+    "platforms": ["Linux", "Mac", "Win"],
+    "bases": ["external/wpt/performance-timeline/tentative"],
+    "args": ["--disable-site-isolation-trials"],
+    "expires": "Jul 1, 2023"
+  },
 
   "----------------------- origin-keyed agent clusters --------------------",
   "Origin-keyed agent clusters web platform tests are for the feature at",
@@ -635,7 +642,6 @@
              "--reset-browsing-instance-between-tests"],
     "expires": "Jul 1, 2023"
   },
-
   {
     "prefix": "focusless-spat-nav",
     "platforms": ["Linux", "Mac", "Win"],
@@ -1544,13 +1550,26 @@
     ],
     "exclusive_tests": "ALL",
     "args": [
-      "--enable-blink-features=SpeculationRulesPrefetchProxy,SpeculationRulesFetchFromHeader,SpeculationRulesReferrerPolicyKey,SpeculationRulesDocumentRules",
-      "--enable-features=SpeculationRulesPrefetchProxy,PrefetchUseContentRefactor,PrefetchNoVarySearch",
+      "--enable-blink-features=SpeculationRulesPrefetchProxy,SpeculationRulesFetchFromHeader,SpeculationRulesReferrerPolicyKey,SpeculationRulesDocumentRules,NoVarySearchPrefetch",
+      "--enable-features=SpeculationRulesPrefetchProxy,PrefetchUseContentRefactor",
       "--bypass-prefetch-proxy-for-host=not-web-platform.test"
     ],
     "expires": "Jul 1, 2023"
   },
   {
+    "prefix": "prefetch-no-vary-search",
+    "platforms": ["Linux", "Mac", "Win"],
+    "bases": [
+      "external/wpt/speculation-rules/prefetch/no-vary-search"
+    ],
+    "exclusive_tests": "ALL",
+    "args": [
+      "--enable-blink-features=NoVarySearchPrefetch",
+      "--enable-features=PrefetchUseContentRefactor"
+    ],
+    "expires": "Jul 1, 2023"
+  },
+  {
     "prefix": "cookie-reject-domain-non-ascii",
     "platforms": ["Linux", "Mac", "Win"],
     "bases": [ "http/tests/inspector-protocol/issues/cookie-domain-non-ascii.js" ],
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-100.html b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-100.html
new file mode 100644
index 0000000..1bf71e83
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-100.html
@@ -0,0 +1,13 @@
+<!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=1395581">
+<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="columns:2; column-fill:auto; height:200px;">
+  <div style="width:60px; height:60px; direction:rtl; border:20px solid green; background:red;">
+    <div style="position:relative;">
+      <div style="position:absolute; left:0; top:0; width:60px; height:30px; background:green;"></div>
+      <div style="position:absolute; right:0; top:30px; width:60px; height:30px; background:green;"></div>
+    </div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-101.html b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-101.html
new file mode 100644
index 0000000..ed7e1e23
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-101.html
@@ -0,0 +1,13 @@
+<!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=1395581">
+<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="columns:2; column-fill:auto; gap:0; width:100px; height:100px; background:red;">
+  <div style="width:30px; height:180px; direction:rtl; border:10px solid green;">
+    <div style="position:relative;">
+      <div style="position:absolute; left:0; top:0; width:30px; height:90px; background:green;"></div>
+      <div style="position:absolute; right:0; top:90px; width:30px; height:90px; background:green;"></div>
+    </div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-102.html b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-102.html
new file mode 100644
index 0000000..45cbc0b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-102.html
@@ -0,0 +1,13 @@
+<!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=1395581">
+<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:absolute; margin-left:15px; margin-top:15px; width:30px; height:70px; background:green;"></div>
+<div style="columns:2; column-fill:auto; height:200px;">
+  <div style="width:70px; height:70px; direction:rtl; border:solid 15px green; background:red;">
+    <div style="position:relative;">
+      <div style="position:absolute; width:40px; height:70px; background:green;"></div>
+    </div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-103.html b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-103.html
new file mode 100644
index 0000000..b54cbcd
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-break/out-of-flow-in-multicolumn-103.html
@@ -0,0 +1,14 @@
+<!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=1395581">
+<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:absolute; margin-left:10px; margin-top:10px; width:10px; height:90px; background:green;"></div>
+<div style="position:absolute; margin-left:60px; width:10px; height:90px; background:green;"></div>
+<div style="columns:2; column-fill:auto; gap:0; width:100px; height:100px; background:red;">
+  <div style="width:32px; height:180px; direction:rtl; border:solid 10px green; border-right-width:8px;">
+    <div style="position:relative;">
+      <div style="position:absolute; width:22px; height:180px; background:green;"></div>
+    </div>
+  </div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/gradient/css-color-4-colors-default-to-oklab-gradient.html b/third_party/blink/web_tests/external/wpt/css/css-images/gradient/css-color-4-colors-default-to-oklab-gradient.html
index 5be18ac..63fdd6f 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-images/gradient/css-color-4-colors-default-to-oklab-gradient.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/gradient/css-color-4-colors-default-to-oklab-gradient.html
@@ -11,7 +11,7 @@
    .test {
        width: 480px;
        height: 50px;
-       background: linear-gradient(to right, rgb(255, 0, 0), color(srgb 0 255 0));
+       background: linear-gradient(to right, rgb(255, 0, 0), color(srgb 0 1 0));
    }
  </style>
  </head>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-contain-png-001c.html b/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-contain-png-001c.html
index 4fb7f113..0f8195157 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-contain-png-001c.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-contain-png-001c.html
@@ -12,6 +12,7 @@
     <link rel="help" href="http://www.w3.org/TR/css3-images/#the-object-fit">
     <link rel="help" href="http://www.w3.org/TR/css3-images/#the-object-position">
     <link rel="match" href="object-fit-contain-png-001-ref.html">
+    <meta name=fuzzy content="maxDifference=0-20;totalPixels=0-2000">
     <style type="text/css">
       canvas {
         border: 1px dashed gray;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-contain-png-002c.html b/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-contain-png-002c.html
index 738c015a..14834316 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-contain-png-002c.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-contain-png-002c.html
@@ -12,6 +12,7 @@
     <link rel="help" href="http://www.w3.org/TR/css3-images/#the-object-fit">
     <link rel="help" href="http://www.w3.org/TR/css3-images/#the-object-position">
     <link rel="match" href="object-fit-contain-png-002-ref.html">
+    <meta name=fuzzy content="maxDifference=0-20;totalPixels=0-2000">
     <style type="text/css">
       canvas {
         border: 1px dashed gray;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-fill-png-001c.html b/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-fill-png-001c.html
index 36031175..0e2a388 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-fill-png-001c.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-fill-png-001c.html
@@ -12,6 +12,7 @@
     <link rel="help" href="http://www.w3.org/TR/css3-images/#the-object-fit">
     <link rel="help" href="http://www.w3.org/TR/css3-images/#the-object-position">
     <link rel="match" href="object-fit-fill-png-001-ref.html">
+    <meta name=fuzzy content="maxDifference=0-20;totalPixels=0-3200">
     <style type="text/css">
       canvas {
         border: 1px dashed gray;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-fill-png-002c.html b/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-fill-png-002c.html
index a332c37..43bcced 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-fill-png-002c.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/object-fit-fill-png-002c.html
@@ -12,6 +12,7 @@
     <link rel="help" href="http://www.w3.org/TR/css3-images/#the-object-fit">
     <link rel="help" href="http://www.w3.org/TR/css3-images/#the-object-position">
     <link rel="match" href="object-fit-fill-png-002-ref.html">
+    <meta name=fuzzy content="maxDifference=0-20;totalPixels=0-3200">
     <style type="text/css">
       canvas {
         border: 1px dashed gray;
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/resources/parent-frame-with-child.sub.html b/third_party/blink/web_tests/external/wpt/performance-timeline/resources/parent-frame-with-cross-origin-child.sub.html
similarity index 67%
rename from third_party/blink/web_tests/external/wpt/performance-timeline/resources/parent-frame-with-child.sub.html
rename to third_party/blink/web_tests/external/wpt/performance-timeline/resources/parent-frame-with-cross-origin-child.sub.html
index 32dd4cb..4be0df87 100644
--- a/third_party/blink/web_tests/external/wpt/performance-timeline/resources/parent-frame-with-child.sub.html
+++ b/third_party/blink/web_tests/external/wpt/performance-timeline/resources/parent-frame-with-cross-origin-child.sub.html
@@ -2,6 +2,7 @@
 <head></head>
 <body></body>
 <script>
+    // Create child frame that is cross-origin with its parent.
     const childFrame = document.createElement('iframe')
     childFrame.src = "http://{{hosts[][]}}:{{ports[http][0]}}/performance-timeline/resources/child-frame.html"
     document.body.appendChild(childFrame)
@@ -9,6 +10,7 @@
     performance.mark("entry-name")
 
     childFrame.addEventListener('load', () => {
-        window.parent.postMessage("DONE", "*")
+        const entries = performance.getEntries(true)
+        window.parent.postMessage(entries.length, "*")
     })
 </script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/resources/parent-frame-with-same-origin-child.html b/third_party/blink/web_tests/external/wpt/performance-timeline/resources/parent-frame-with-same-origin-child.html
new file mode 100644
index 0000000..c9248a4
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/performance-timeline/resources/parent-frame-with-same-origin-child.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<head></head>
+<body></body>
+<script>
+    // Create child frame that is same-origin with its parent.
+    const childFrame = document.createElement('iframe')
+    childFrame.src = "child-frame.html"
+    document.body.appendChild(childFrame)
+
+    performance.mark("entry-name")
+
+    childFrame.addEventListener('load', () => {
+        const entries = performance.getEntries(true)
+        window.parent.postMessage(entries.length, "*")
+    })
+</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-from-child-cross-origin-grandchild.sub.html b/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-from-child-cross-origin-grandchild.sub.html
new file mode 100644
index 0000000..dcd716d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-from-child-cross-origin-grandchild.sub.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+</body>
+<script>
+promise_test(() => {
+    return new Promise(resolve => {
+        performance.clearResourceTimings()
+
+        // Create child iframe with an embedded frame that is same-origin with its parent but cross-origin with the current frame.
+        const crossOriginChildFrame = document.createElement('iframe')
+        crossOriginChildFrame.src = "http://{{hosts[][www]}}:{{ports[http][0]}}/performance-timeline/resources/parent-frame-with-same-origin-child.html"
+        document.body.appendChild(crossOriginChildFrame)
+
+        // Listen for postMessage() from child frame.
+        window.addEventListener("message", e => {
+            // 0 entries for parent, 4 for child, 2 for grandchild.
+            assert_equals(e.data, 6)
+
+            resolve()
+        })
+    })
+}, "GetEntries of a Cross-Origin child frame with one Cross-Origin grandchild frame")
+</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-from-child-same-origin-grandchild.sub.html b/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-from-child-same-origin-grandchild.sub.html
new file mode 100644
index 0000000..991715f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-from-child-same-origin-grandchild.sub.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<head>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+</body>
+<script>
+promise_test(() => {
+    return new Promise(resolve => {
+        performance.clearResourceTimings()
+
+        // Create child iframe with an embedded frame that is cross-origin with its parent but same-origin with the current frame.
+        const crossOriginChildFrame = document.createElement('iframe')
+        crossOriginChildFrame.src = "http://{{hosts[][www]}}:{{ports[http][0]}}/performance-timeline/resources/parent-frame-with-cross-origin-child.sub.html"
+        document.body.appendChild(crossOriginChildFrame)
+
+        // Listen for postMessage() from child frame.
+        window.addEventListener("message", e => {
+            // 0 entries for parent, 4 for child, 0 for grandchild.
+            assert_equals(e.data, 4)
+
+            resolve()
+        })
+    })
+}, "GetEntries of a Cross-Origin child frame with one Same-Origin grandchild frame")
+</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-one-local-child-one-local-grandchild.html b/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-one-local-child-one-local-grandchild.html
index ee2dadd..06a13f3 100644
--- a/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-one-local-child-one-local-grandchild.html
+++ b/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-one-local-child-one-local-grandchild.html
@@ -10,9 +10,9 @@
     return new Promise(resolve => {
         performance.clearResourceTimings()
 
-        // Create child iframe.
+        // Create child iframe with an embedded frame that is cross-origin with its parent, but same-origin with the current frame.
         const childFrame = document.createElement('iframe')
-        childFrame.src = "../resources/parent-frame-with-child.sub.html"
+        childFrame.src = "../resources/parent-frame-with-cross-origin-child.sub.html"
         document.body.appendChild(childFrame)
 
         // Listen for postMessage() from grandchild frame.
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-one-remote-child-one-local-grandchild.sub.html b/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-one-remote-child-one-local-grandchild.sub.html
index a9efe41..3248d601 100644
--- a/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-one-remote-child-one-local-grandchild.sub.html
+++ b/third_party/blink/web_tests/external/wpt/performance-timeline/tentative/include-frames-one-remote-child-one-local-grandchild.sub.html
@@ -10,9 +10,9 @@
     return new Promise(resolve => {
         performance.clearResourceTimings()
 
-        // Create child iframe.
+        // Create child iframe with an embedded frame that is cross-origin with its parent, but same-origin with the current frame.
         const remoteChildFrame = document.createElement('iframe')
-        remoteChildFrame.src = "http://{{hosts[][www]}}:{{ports[http][0]}}/performance-timeline/resources/parent-frame-with-child.sub.html"
+        remoteChildFrame.src = "http://{{hosts[][www]}}:{{ports[http][0]}}/performance-timeline/resources/parent-frame-with-cross-origin-child.sub.html"
         document.body.appendChild(remoteChildFrame)
 
         // Listen for postMessage() from grandchild frame.
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/no-vary-search/README.txt b/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/no-vary-search/README.txt
new file mode 100644
index 0000000..60ac226f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/no-vary-search/README.txt
@@ -0,0 +1 @@
+Web Platform Tests for No-Vary-Search support in prefetch cache.
diff --git a/third_party/blink/web_tests/flag-specific/disable-layout-ng/fast/gradients/generated-gradients-expected.png b/third_party/blink/web_tests/flag-specific/disable-layout-ng/fast/gradients/generated-gradients-expected.png
index a03c012..7f32aa6f 100644
--- a/third_party/blink/web_tests/flag-specific/disable-layout-ng/fast/gradients/generated-gradients-expected.png
+++ b/third_party/blink/web_tests/flag-specific/disable-layout-ng/fast/gradients/generated-gradients-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/disable-layout-ng/fast/gradients/simple-gradients-expected.png b/third_party/blink/web_tests/flag-specific/disable-layout-ng/fast/gradients/simple-gradients-expected.png
index 61a0d2f..16450ae6 100644
--- a/third_party/blink/web_tests/flag-specific/disable-layout-ng/fast/gradients/simple-gradients-expected.png
+++ b/third_party/blink/web_tests/flag-specific/disable-layout-ng/fast/gradients/simple-gradients-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/disable-layout-ng/svg/wicd/test-scalable-background-image2-expected.png b/third_party/blink/web_tests/flag-specific/disable-layout-ng/svg/wicd/test-scalable-background-image2-expected.png
index bb0f5b2..be7d1ba 100644
--- a/third_party/blink/web_tests/flag-specific/disable-layout-ng/svg/wicd/test-scalable-background-image2-expected.png
+++ b/third_party/blink/web_tests/flag-specific/disable-layout-ng/svg/wicd/test-scalable-background-image2-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/highdpi/svg/batik/text/textEffect-expected.png b/third_party/blink/web_tests/flag-specific/highdpi/svg/batik/text/textEffect-expected.png
index b4db6398..6ef1fe26 100644
--- a/third_party/blink/web_tests/flag-specific/highdpi/svg/batik/text/textEffect-expected.png
+++ b/third_party/blink/web_tests/flag-specific/highdpi/svg/batik/text/textEffect-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/flag-specific/highdpi/svg/wicd/test-scalable-background-image2-expected.png b/third_party/blink/web_tests/flag-specific/highdpi/svg/wicd/test-scalable-background-image2-expected.png
new file mode 100644
index 0000000..481f4f2
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/highdpi/svg/wicd/test-scalable-background-image2-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/gradients/generated-gradients-expected.png b/third_party/blink/web_tests/platform/linux/fast/gradients/generated-gradients-expected.png
index e138595..1481503 100644
--- a/third_party/blink/web_tests/platform/linux/fast/gradients/generated-gradients-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/gradients/generated-gradients-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/gradients/simple-gradients-expected.png b/third_party/blink/web_tests/platform/linux/fast/gradients/simple-gradients-expected.png
index d916621..99755b1 100644
--- a/third_party/blink/web_tests/platform/linux/fast/gradients/simple-gradients-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/gradients/simple-gradients-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/svg/W3C-SVG-1.1/pservers-grad-14-b-expected.png b/third_party/blink/web_tests/platform/linux/svg/W3C-SVG-1.1/pservers-grad-14-b-expected.png
index d76cc76..8226e918 100644
--- a/third_party/blink/web_tests/platform/linux/svg/W3C-SVG-1.1/pservers-grad-14-b-expected.png
+++ b/third_party/blink/web_tests/platform/linux/svg/W3C-SVG-1.1/pservers-grad-14-b-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/svg/as-background-image/svg-as-background-3-expected.png b/third_party/blink/web_tests/platform/linux/svg/as-background-image/svg-as-background-3-expected.png
index df70ddad..b95cdbf 100644
--- a/third_party/blink/web_tests/platform/linux/svg/as-background-image/svg-as-background-3-expected.png
+++ b/third_party/blink/web_tests/platform/linux/svg/as-background-image/svg-as-background-3-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/svg/wicd/test-scalable-background-image2-expected.png b/third_party/blink/web_tests/platform/linux/svg/wicd/test-scalable-background-image2-expected.png
index 481f4f2..5c1a25a8 100644
--- a/third_party/blink/web_tests/platform/linux/svg/wicd/test-scalable-background-image2-expected.png
+++ b/third_party/blink/web_tests/platform/linux/svg/wicd/test-scalable-background-image2-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.13/svg/wicd/test-scalable-background-image2-expected.png b/third_party/blink/web_tests/platform/mac-mac10.13/svg/wicd/test-scalable-background-image2-expected.png
index 10e1a16..3ab85fb 100644
--- a/third_party/blink/web_tests/platform/mac-mac10.13/svg/wicd/test-scalable-background-image2-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac10.13/svg/wicd/test-scalable-background-image2-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/canvas/canvas-incremental-repaint-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/canvas/canvas-incremental-repaint-expected.png
index 5f61902..1662057d 100644
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/canvas/canvas-incremental-repaint-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/canvas/canvas-incremental-repaint-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color-scheme/color/color-picker-appearance-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color-scheme/color/color-picker-appearance-expected.png
deleted file mode 100644
index 5d03ab2a..0000000
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color-scheme/color/color-picker-appearance-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-click-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-click-expected.png
deleted file mode 100644
index 9597811..0000000
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-left-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-left-corner-expected.png
deleted file mode 100644
index 2d91917..0000000
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-left-corner-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-right-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-right-corner-expected.png
deleted file mode 100644
index ef045b8..0000000
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-right-corner-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-drag-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-drag-expected.png
deleted file mode 100644
index 9597811..0000000
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-drag-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-keyboard-navigation-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-keyboard-navigation-expected.png
deleted file mode 100644
index 68e5798..0000000
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-keyboard-navigation-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-bottom-right-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-bottom-right-corner-expected.png
deleted file mode 100644
index c8b2b51..0000000
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-bottom-right-corner-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-top-right-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-top-right-corner-expected.png
deleted file mode 100644
index 7a299d1..0000000
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-top-right-corner-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-bottom-left-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-bottom-left-corner-expected.png
deleted file mode 100644
index 9176a3a1..0000000
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-bottom-left-corner-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-top-left-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-top-left-corner-expected.png
deleted file mode 100644
index 220dc263..0000000
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-top-left-corner-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-touch-drag-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-touch-drag-expected.png
deleted file mode 100644
index 4090cebd..0000000
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-touch-drag-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-left-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-left-corner-expected.png
deleted file mode 100644
index 66da85c..0000000
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-left-corner-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-right-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-right-corner-expected.png
deleted file mode 100644
index cb7b5b4..0000000
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-right-corner-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-hex-format-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-hex-format-expected.png
deleted file mode 100644
index c8acdf4d..0000000
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-hex-format-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-hsl-format-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-hsl-format-expected.png
deleted file mode 100644
index 86aa0df..0000000
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-hsl-format-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-hue-slider-left-edge-zero-hue-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-hue-slider-left-edge-zero-hue-expected.png
deleted file mode 100644
index 6892e25b..0000000
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-hue-slider-left-edge-zero-hue-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-hue-slider-right-edge-zero-hue-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-hue-slider-right-edge-zero-hue-expected.png
deleted file mode 100644
index 450d929c..0000000
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-hue-slider-right-edge-zero-hue-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-imperfect-match-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-imperfect-match-expected.png
deleted file mode 100644
index ea6faa4..0000000
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-imperfect-match-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-manual-color-change-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-manual-color-change-expected.png
deleted file mode 100644
index 3d6f2cb..0000000
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-manual-color-change-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-set-value-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-set-value-expected.png
deleted file mode 100644
index 6027fc7..0000000
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-set-value-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-tap-hex-format-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-tap-hex-format-expected.png
deleted file mode 100644
index 36fe0c2..0000000
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-tap-hex-format-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-value-attribute-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-value-attribute-expected.png
deleted file mode 100644
index 92335fbd..0000000
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-appearance-value-attribute-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-top-left-selection-position-after-reopen-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-top-left-selection-position-after-reopen-expected.png
deleted file mode 100644
index 54909a8..0000000
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/forms/color/color-picker-top-left-selection-position-after-reopen-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/gradients/conic-gradient-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/gradients/conic-gradient-expected.png
index c5e3412..ba20a1ca 100644
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/gradients/conic-gradient-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/gradients/conic-gradient-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/gradients/generated-gradients-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/gradients/generated-gradients-expected.png
new file mode 100644
index 0000000..448329e36
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/gradients/generated-gradients-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/gradients/unprefixed-linear-gradients-color-hints-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/gradients/unprefixed-linear-gradients-color-hints-expected.png
index ea05f2f..35559e0c 100644
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/gradients/unprefixed-linear-gradients-color-hints-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/fast/gradients/unprefixed-linear-gradients-color-hints-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/media/alpha-video-playback-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/media/alpha-video-playback-expected.png
deleted file mode 100644
index 7fc316c..0000000
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/media/alpha-video-playback-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/paint/invalidation/canvas-resize-no-full-invalidation-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/paint/invalidation/canvas-resize-no-full-invalidation-expected.png
deleted file mode 100644
index 2bfe7f7..0000000
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/paint/invalidation/canvas-resize-no-full-invalidation-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/svg/wicd/test-scalable-background-image2-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/svg/wicd/test-scalable-background-image2-expected.png
index 84df5d5..49428ee 100644
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/svg/wicd/test-scalable-background-image2-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/svg/wicd/test-scalable-background-image2-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/controls-refresh-hc/fast/forms/color-scheme/color/color-picker-appearance-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/controls-refresh-hc/fast/forms/color-scheme/color/color-picker-appearance-expected.png
deleted file mode 100644
index 3d15b57..0000000
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/controls-refresh-hc/fast/forms/color-scheme/color/color-picker-appearance-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/dark-color-scheme/fast/forms/color-scheme/color/color-picker-appearance-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/dark-color-scheme/fast/forms/color-scheme/color/color-picker-appearance-expected.png
deleted file mode 100644
index 8d0f8d8..0000000
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/dark-color-scheme/fast/forms/color-scheme/color/color-picker-appearance-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/dark-system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/dark-system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png
deleted file mode 100644
index c231417..0000000
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/dark-system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/gpu/fast/canvas/canvas-incremental-repaint-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/gpu/fast/canvas/canvas-incremental-repaint-expected.png
new file mode 100644
index 0000000..5f61902
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/gpu/fast/canvas/canvas-incremental-repaint-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png b/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png
deleted file mode 100644
index 1344ecc..0000000
--- a/third_party/blink/web_tests/platform/mac-mac11-arm64/virtual/system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11/fast/canvas/canvas-incremental-repaint-expected.png b/third_party/blink/web_tests/platform/mac-mac11/fast/canvas/canvas-incremental-repaint-expected.png
new file mode 100644
index 0000000..f0252fb
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac11/fast/canvas/canvas-incremental-repaint-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11/svg/wicd/test-scalable-background-image2-expected.png b/third_party/blink/web_tests/platform/mac-mac11/svg/wicd/test-scalable-background-image2-expected.png
index 3b37f98ad..4d997c6 100644
--- a/third_party/blink/web_tests/platform/mac-mac11/svg/wicd/test-scalable-background-image2-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac11/svg/wicd/test-scalable-background-image2-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11/virtual/exotic-color-space/images/color-profile-munsell-adobe-to-srgb-expected.png b/third_party/blink/web_tests/platform/mac-mac11/virtual/exotic-color-space/images/color-profile-munsell-adobe-to-srgb-expected.png
index d9ba88e..66a699e 100644
--- a/third_party/blink/web_tests/platform/mac-mac11/virtual/exotic-color-space/images/color-profile-munsell-adobe-to-srgb-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac11/virtual/exotic-color-space/images/color-profile-munsell-adobe-to-srgb-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac11/virtual/exotic-color-space/images/color-profile-munsell-srgb-to-srgb-expected.png b/third_party/blink/web_tests/platform/mac-mac11/virtual/exotic-color-space/images/color-profile-munsell-srgb-to-srgb-expected.png
index ba341e1b..5b6ab5c 100644
--- a/third_party/blink/web_tests/platform/mac-mac11/virtual/exotic-color-space/images/color-profile-munsell-srgb-to-srgb-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac11/virtual/exotic-color-space/images/color-profile-munsell-srgb-to-srgb-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/canvas/canvas-incremental-repaint-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/canvas/canvas-incremental-repaint-expected.png
index 5f61902..1662057d 100644
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/canvas/canvas-incremental-repaint-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/canvas/canvas-incremental-repaint-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color-scheme/color/color-picker-appearance-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color-scheme/color/color-picker-appearance-expected.png
deleted file mode 100644
index 5d03ab2a..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color-scheme/color/color-picker-appearance-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-click-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-click-expected.png
deleted file mode 100644
index 9597811..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-click-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-left-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-left-corner-expected.png
deleted file mode 100644
index 2d91917..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-left-corner-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-right-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-right-corner-expected.png
deleted file mode 100644
index ef045b8..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-right-corner-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-drag-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-drag-expected.png
deleted file mode 100644
index 9597811..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-drag-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-keyboard-navigation-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-keyboard-navigation-expected.png
deleted file mode 100644
index 68e5798..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-keyboard-navigation-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-bottom-right-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-bottom-right-corner-expected.png
deleted file mode 100644
index c8b2b51..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-bottom-right-corner-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-top-right-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-top-right-corner-expected.png
deleted file mode 100644
index 7a299d1..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-top-right-corner-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-bottom-left-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-bottom-left-corner-expected.png
deleted file mode 100644
index 9176a3a1..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-bottom-left-corner-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-top-left-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-top-left-corner-expected.png
deleted file mode 100644
index 220dc263..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-top-left-corner-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-touch-drag-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-touch-drag-expected.png
deleted file mode 100644
index 4090cebd..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-touch-drag-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-left-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-left-corner-expected.png
deleted file mode 100644
index 66da85c..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-left-corner-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-right-corner-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-right-corner-expected.png
deleted file mode 100644
index cb7b5b4..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-right-corner-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-hex-format-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-hex-format-expected.png
deleted file mode 100644
index c8acdf4d..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-hex-format-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-hsl-format-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-hsl-format-expected.png
deleted file mode 100644
index 86aa0df..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-hsl-format-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-hue-slider-left-edge-zero-hue-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-hue-slider-left-edge-zero-hue-expected.png
deleted file mode 100644
index 6892e25b..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-hue-slider-left-edge-zero-hue-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-hue-slider-right-edge-zero-hue-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-hue-slider-right-edge-zero-hue-expected.png
deleted file mode 100644
index 450d929c..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-hue-slider-right-edge-zero-hue-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-imperfect-match-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-imperfect-match-expected.png
deleted file mode 100644
index ea6faa4..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-imperfect-match-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-manual-color-change-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-manual-color-change-expected.png
deleted file mode 100644
index 3d6f2cb..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-manual-color-change-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-set-value-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-set-value-expected.png
deleted file mode 100644
index 6027fc7..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-set-value-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-tap-hex-format-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-tap-hex-format-expected.png
deleted file mode 100644
index 36fe0c2..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-tap-hex-format-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-value-attribute-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-value-attribute-expected.png
deleted file mode 100644
index 92335fbd..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-appearance-value-attribute-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-top-left-selection-position-after-reopen-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-top-left-selection-position-after-reopen-expected.png
deleted file mode 100644
index 54909a8..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/forms/color/color-picker-top-left-selection-position-after-reopen-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/gradients/conic-gradient-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/gradients/conic-gradient-expected.png
index c5e3412..ba20a1ca 100644
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/gradients/conic-gradient-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/gradients/conic-gradient-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/gradients/generated-gradients-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/gradients/generated-gradients-expected.png
new file mode 100644
index 0000000..448329e36
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/gradients/generated-gradients-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/gradients/unprefixed-linear-gradients-color-hints-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/gradients/unprefixed-linear-gradients-color-hints-expected.png
index ea05f2f..35559e0c 100644
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/gradients/unprefixed-linear-gradients-color-hints-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/fast/gradients/unprefixed-linear-gradients-color-hints-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/media/alpha-video-playback-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/media/alpha-video-playback-expected.png
deleted file mode 100644
index 7fc316c..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/media/alpha-video-playback-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/paint/invalidation/canvas-resize-no-full-invalidation-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/paint/invalidation/canvas-resize-no-full-invalidation-expected.png
deleted file mode 100644
index 2bfe7f7..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/paint/invalidation/canvas-resize-no-full-invalidation-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/svg/wicd/test-scalable-background-image2-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/svg/wicd/test-scalable-background-image2-expected.png
index cd18903b..6f110d15 100644
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/svg/wicd/test-scalable-background-image2-expected.png
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/svg/wicd/test-scalable-background-image2-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/controls-refresh-hc/fast/forms/color-scheme/color/color-picker-appearance-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/controls-refresh-hc/fast/forms/color-scheme/color/color-picker-appearance-expected.png
deleted file mode 100644
index 3d15b57..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/controls-refresh-hc/fast/forms/color-scheme/color/color-picker-appearance-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/dark-color-scheme/fast/forms/color-scheme/color/color-picker-appearance-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/dark-color-scheme/fast/forms/color-scheme/color/color-picker-appearance-expected.png
deleted file mode 100644
index 8d0f8d8..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/dark-color-scheme/fast/forms/color-scheme/color/color-picker-appearance-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/dark-system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/dark-system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png
deleted file mode 100644
index c231417..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/dark-system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/exotic-color-space/images/jpeg-yuv-progressive-canvas-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/exotic-color-space/images/jpeg-yuv-progressive-canvas-expected.png
deleted file mode 100644
index ffa89d0..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/exotic-color-space/images/jpeg-yuv-progressive-canvas-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/gpu/fast/canvas/canvas-incremental-repaint-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/gpu/fast/canvas/canvas-incremental-repaint-expected.png
new file mode 100644
index 0000000..5f61902
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/gpu/fast/canvas/canvas-incremental-repaint-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png b/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png
deleted file mode 100644
index 1344ecc..0000000
--- a/third_party/blink/web_tests/platform/mac-mac12-arm64/virtual/system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/canvas/canvas-hidpi-blurry-expected.png b/third_party/blink/web_tests/platform/mac/fast/canvas/canvas-hidpi-blurry-expected.png
index 6a9b251..92e03005 100644
--- a/third_party/blink/web_tests/platform/mac/fast/canvas/canvas-hidpi-blurry-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/canvas/canvas-hidpi-blurry-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/canvas/canvas-incremental-repaint-expected.png b/third_party/blink/web_tests/platform/mac/fast/canvas/canvas-incremental-repaint-expected.png
index aef4be0..f0252fb 100644
--- a/third_party/blink/web_tests/platform/mac/fast/canvas/canvas-incremental-repaint-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/canvas/canvas-incremental-repaint-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/canvas/canvas-toDataURL-jpeg-maximum-quality-expected.png b/third_party/blink/web_tests/platform/mac/fast/canvas/canvas-toDataURL-jpeg-maximum-quality-expected.png
index ad7239b..28a0a35c 100644
--- a/third_party/blink/web_tests/platform/mac/fast/canvas/canvas-toDataURL-jpeg-maximum-quality-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/canvas/canvas-toDataURL-jpeg-maximum-quality-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/canvas/canvas-toDataURL-webp-expected.png b/third_party/blink/web_tests/platform/mac/fast/canvas/canvas-toDataURL-webp-expected.png
new file mode 100644
index 0000000..2159358a
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/fast/canvas/canvas-toDataURL-webp-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/canvas/pixelated-expected.png b/third_party/blink/web_tests/platform/mac/fast/canvas/pixelated-expected.png
new file mode 100644
index 0000000..b75d52bc
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/fast/canvas/pixelated-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color-scheme/color/color-picker-appearance-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color-scheme/color/color-picker-appearance-expected.png
index 577c247..9e2444f 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color-scheme/color/color-picker-appearance-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color-scheme/color/color-picker-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-click-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-click-expected.png
index 6770f89..5210665 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-click-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-click-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-left-corner-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-left-corner-expected.png
index 9af4d8d..73668a6 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-left-corner-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-left-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-right-corner-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-right-corner-expected.png
index 0d159ed..13b9281 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-right-corner-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-down-keyboard-navigation-from-top-right-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-drag-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-drag-expected.png
index 6770f89..5210665 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-drag-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-drag-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-keyboard-navigation-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-keyboard-navigation-expected.png
index c824cba..3344e87 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-keyboard-navigation-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-keyboard-navigation-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-bottom-right-corner-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-bottom-right-corner-expected.png
index bf48c62d..c1d9b25b 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-bottom-right-corner-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-bottom-right-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-top-right-corner-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-top-right-corner-expected.png
index 6744078..5bc891c 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-top-right-corner-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-left-keyboard-navigation-from-top-right-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-bottom-left-corner-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-bottom-left-corner-expected.png
index ba54ccd..867acb27 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-bottom-left-corner-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-bottom-left-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-top-left-corner-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-top-left-corner-expected.png
index 16af243d..4e5b644 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-top-left-corner-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-right-keyboard-navigation-from-top-left-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-touch-drag-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-touch-drag-expected.png
index 02ae9d4..6e61b30 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-touch-drag-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-touch-drag-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-left-corner-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-left-corner-expected.png
index f863fcd..23fe32a 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-left-corner-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-left-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-right-corner-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-right-corner-expected.png
index db826b4..f1e886ee 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-right-corner-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-color-well-up-keyboard-navigation-from-bottom-right-corner-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hex-format-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hex-format-expected.png
index 2f59f7f..0368d30d 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hex-format-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hex-format-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hsl-format-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hsl-format-expected.png
index 574a796..c621520 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hsl-format-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hsl-format-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-accelerated-keyboard-navigation-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-accelerated-keyboard-navigation-expected.png
index 4de189f..dd4d416 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-accelerated-keyboard-navigation-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-accelerated-keyboard-navigation-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-click-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-click-expected.png
index 42f81d5a..aa13f9c 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-click-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-click-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-drag-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-drag-expected.png
index 42f81d5a..aa13f9c 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-drag-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-drag-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-keyboard-navigation-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-keyboard-navigation-expected.png
index 3189d77..4dd743b 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-keyboard-navigation-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-keyboard-navigation-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-left-edge-zero-hue-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-left-edge-zero-hue-expected.png
index f75fe963..0476678 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-left-edge-zero-hue-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-left-edge-zero-hue-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-right-edge-zero-hue-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-right-edge-zero-hue-expected.png
index a2bc636..2a1980a 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-right-edge-zero-hue-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-right-edge-zero-hue-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-touch-drag-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-touch-drag-expected.png
index 8d82275..a5c05a8 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-touch-drag-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-hue-slider-touch-drag-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-imperfect-match-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-imperfect-match-expected.png
index f65ba0d..a095fc9 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-imperfect-match-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-imperfect-match-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-manual-color-change-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-manual-color-change-expected.png
index dec5aeb..63d3411 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-manual-color-change-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-manual-color-change-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-set-value-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-set-value-expected.png
index 9e27acf..0c84723 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-set-value-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-set-value-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-tap-hex-format-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-tap-hex-format-expected.png
index 777f415b..114e9dc 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-tap-hex-format-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-tap-hex-format-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-value-attribute-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-value-attribute-expected.png
index f400434..ecf3bcc5 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-value-attribute-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-value-attribute-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-zoom125-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-zoom125-expected.png
index 5bb2acd0..dcce6db4 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-zoom125-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-zoom125-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-zoom200-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-zoom200-expected.png
index 900f5a7..fe6ecd6d 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-zoom200-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-appearance-zoom200-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-top-left-selection-position-after-reopen-expected.png b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-top-left-selection-position-after-reopen-expected.png
index a434e66..0ad74dc 100644
--- a/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-top-left-selection-position-after-reopen-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/forms/color/color-picker-top-left-selection-position-after-reopen-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/gradients/conic-gradient-expected.png b/third_party/blink/web_tests/platform/mac/fast/gradients/conic-gradient-expected.png
index 9f4d9b65..114f0cfb 100644
--- a/third_party/blink/web_tests/platform/mac/fast/gradients/conic-gradient-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/gradients/conic-gradient-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/gradients/generated-gradients-expected.png b/third_party/blink/web_tests/platform/mac/fast/gradients/generated-gradients-expected.png
index 448329e36..00855abf 100644
--- a/third_party/blink/web_tests/platform/mac/fast/gradients/generated-gradients-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/gradients/generated-gradients-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/gradients/simple-gradients-expected.png b/third_party/blink/web_tests/platform/mac/fast/gradients/simple-gradients-expected.png
index 1bda305..bc6d9a7 100644
--- a/third_party/blink/web_tests/platform/mac/fast/gradients/simple-gradients-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/gradients/simple-gradients-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/gradients/unprefixed-linear-gradients-color-hints-expected.png b/third_party/blink/web_tests/platform/mac/fast/gradients/unprefixed-linear-gradients-color-hints-expected.png
index 7d3b99d5..303e8178 100644
--- a/third_party/blink/web_tests/platform/mac/fast/gradients/unprefixed-linear-gradients-color-hints-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/gradients/unprefixed-linear-gradients-color-hints-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/replaced/border-radius-clip-content-edge-expected.png b/third_party/blink/web_tests/platform/mac/fast/replaced/border-radius-clip-content-edge-expected.png
index 50044ee..ef8cc96 100644
--- a/third_party/blink/web_tests/platform/mac/fast/replaced/border-radius-clip-content-edge-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/replaced/border-radius-clip-content-edge-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/inspector-protocol/overlay/overlay-with-emulation-scale-expected.txt b/third_party/blink/web_tests/platform/mac/inspector-protocol/overlay/overlay-with-emulation-scale-expected.txt
new file mode 100644
index 0000000..f546a917
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/inspector-protocol/overlay/overlay-with-emulation-scale-expected.txt
@@ -0,0 +1,4 @@
+Verifies that overlay is correctly rendered with emulation scale > 1.
+The test passes if the image URL below is 300x600 image containing a 270x420 brown rectangle, without any green or red.
+data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAJYCAYAAADYJts/AAAAAXNSR0IArs4c6QAAB9JJREFUeJzt20EKhEAMAMGdxf9/OT5BBUVaqu6BnJpcsmZmfgAB/7cXADhLsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjK2yxNrPbAG8Gk3vSy7sIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8jYLk/MPLAGwDEXFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARk7HLgMrk9KayMAAAAASUVORK5CYII=
+
diff --git a/third_party/blink/web_tests/platform/mac/media/alpha-video-playback-expected.png b/third_party/blink/web_tests/platform/mac/media/alpha-video-playback-expected.png
index e2872f31..7fc316c 100644
--- a/third_party/blink/web_tests/platform/mac/media/alpha-video-playback-expected.png
+++ b/third_party/blink/web_tests/platform/mac/media/alpha-video-playback-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/canvas-putImageData-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/canvas-putImageData-expected.txt
new file mode 100644
index 0000000..9051839
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/canvas-putImageData-expected.txt
@@ -0,0 +1,16 @@
+{
+  "layers": [
+    {
+      "name": "Scrolling background of LayoutNGView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='canvas'",
+      "position": [8, 8],
+      "bounds": [100, 100]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/canvas-resize-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/canvas-resize-expected.txt
new file mode 100644
index 0000000..dce35520
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/canvas-resize-expected.txt
@@ -0,0 +1,16 @@
+{
+  "layers": [
+    {
+      "name": "Scrolling background of LayoutNGView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutHTMLCanvas (positioned) CANVAS id='canvas'",
+      "position": [50, 50],
+      "bounds": [500, 500]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/canvas-resize-no-full-invalidation-expected.png b/third_party/blink/web_tests/platform/mac/paint/invalidation/canvas-resize-no-full-invalidation-expected.png
index 4359ac3..2bfe7f7 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/canvas-resize-no-full-invalidation-expected.png
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/canvas-resize-no-full-invalidation-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/canvas-resize-no-full-invalidation-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/canvas-resize-no-full-invalidation-expected.txt
new file mode 100644
index 0000000..dbfdd46
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/canvas-resize-no-full-invalidation-expected.txt
@@ -0,0 +1,20 @@
+{
+  "layers": [
+    {
+      "name": "Scrolling background of LayoutNGView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "invalidations": [
+        [50, 50, 600, 500]
+      ]
+    },
+    {
+      "name": "LayoutHTMLCanvas (positioned) CANVAS id='canvas'",
+      "position": [50, 50],
+      "bounds": [500, 500],
+      "backgroundColor": "#003300"
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt
index 5598dfc..f5ec0f3 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/image/canvas-composite-repaint-by-all-imagesource-expected.txt
@@ -1,56 +1,235 @@
 {
   "layers": [
     {
-      "name": "Scrolling background of LayoutView #document",
+      "name": "Scrolling background of LayoutNGView #document",
       "bounds": [785, 928],
       "contentsOpaque": true,
-      "backgroundColor": "#FFFFFF",
-      "invalidations": [
-        [569, 562, 133, 42],
-        [569, 512, 133, 42],
-        [569, 462, 133, 42],
-        [569, 412, 133, 42],
-        [569, 362, 133, 42],
-        [569, 312, 133, 42],
-        [569, 262, 133, 42],
-        [569, 212, 133, 42],
-        [569, 162, 133, 42],
-        [569, 112, 133, 42],
-        [569, 62, 133, 42],
-        [427, 562, 133, 42],
-        [427, 512, 133, 42],
-        [427, 462, 133, 42],
-        [427, 412, 133, 42],
-        [427, 362, 133, 42],
-        [427, 312, 133, 42],
-        [427, 262, 133, 42],
-        [427, 212, 133, 42],
-        [427, 162, 133, 42],
-        [427, 112, 133, 42],
-        [427, 62, 133, 42],
-        [285, 562, 133, 42],
-        [285, 512, 133, 42],
-        [285, 462, 133, 42],
-        [285, 412, 133, 42],
-        [285, 362, 133, 42],
-        [285, 312, 133, 42],
-        [285, 262, 133, 42],
-        [285, 212, 133, 42],
-        [285, 162, 133, 42],
-        [285, 112, 133, 42],
-        [285, 62, 133, 42],
-        [143, 562, 133, 42],
-        [143, 512, 133, 42],
-        [143, 462, 133, 42],
-        [143, 412, 133, 42],
-        [143, 362, 133, 42],
-        [143, 312, 133, 42],
-        [143, 262, 133, 42],
-        [143, 212, 133, 42],
-        [143, 162, 133, 42],
-        [143, 112, 133, 42],
-        [143, 62, 133, 42]
-      ]
+      "backgroundColor": "#FFFFFF"
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='source-oversolid color'",
+      "position": [145, 63],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='source-overimage'",
+      "position": [287, 63],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='source-overcanvas'",
+      "position": [429, 63],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='source-overvideo'",
+      "position": [571, 63],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='source-insolid color'",
+      "position": [145, 113],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='source-inimage'",
+      "position": [287, 113],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='source-incanvas'",
+      "position": [429, 113],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='source-invideo'",
+      "position": [571, 113],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='source-outsolid color'",
+      "position": [145, 163],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='source-outimage'",
+      "position": [287, 163],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='source-outcanvas'",
+      "position": [429, 163],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='source-outvideo'",
+      "position": [571, 163],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='source-atopsolid color'",
+      "position": [145, 213],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='source-atopimage'",
+      "position": [287, 213],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='source-atopcanvas'",
+      "position": [429, 213],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='source-atopvideo'",
+      "position": [571, 213],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='destination-oversolid color'",
+      "position": [145, 263],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='destination-overimage'",
+      "position": [287, 263],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='destination-overcanvas'",
+      "position": [429, 263],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='destination-overvideo'",
+      "position": [571, 263],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='destination-insolid color'",
+      "position": [145, 313],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='destination-inimage'",
+      "position": [287, 313],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='destination-incanvas'",
+      "position": [429, 313],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='destination-invideo'",
+      "position": [571, 313],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='destination-outsolid color'",
+      "position": [145, 363],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='destination-outimage'",
+      "position": [287, 363],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='destination-outcanvas'",
+      "position": [429, 363],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='destination-outvideo'",
+      "position": [571, 363],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='destination-atopsolid color'",
+      "position": [145, 413],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='destination-atopimage'",
+      "position": [287, 413],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='destination-atopcanvas'",
+      "position": [429, 413],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='destination-atopvideo'",
+      "position": [571, 413],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='lightersolid color'",
+      "position": [145, 463],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='lighterimage'",
+      "position": [287, 463],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='lightercanvas'",
+      "position": [429, 463],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='lightervideo'",
+      "position": [571, 463],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='copysolid color'",
+      "position": [145, 513],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='copyimage'",
+      "position": [287, 513],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='copycanvas'",
+      "position": [429, 513],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='copyvideo'",
+      "position": [571, 513],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='xorsolid color'",
+      "position": [145, 563],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='xorimage'",
+      "position": [287, 563],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='xorcanvas'",
+      "position": [429, 563],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='xorvideo'",
+      "position": [571, 563],
+      "bounds": [130, 40]
+    },
+    {
+      "name": "LayoutHTMLCanvas CANVAS id='source-canvas'",
+      "position": [16, 751],
+      "bounds": [150, 60]
     },
     {
       "name": "LayoutVideo VIDEO id='video'",
diff --git a/third_party/blink/web_tests/platform/mac/svg/W3C-SVG-1.1/pservers-grad-14-b-expected.png b/third_party/blink/web_tests/platform/mac/svg/W3C-SVG-1.1/pservers-grad-14-b-expected.png
index 82032ccc..d848959 100644
--- a/third_party/blink/web_tests/platform/mac/svg/W3C-SVG-1.1/pservers-grad-14-b-expected.png
+++ b/third_party/blink/web_tests/platform/mac/svg/W3C-SVG-1.1/pservers-grad-14-b-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/svg/as-background-image/svg-as-background-3-expected.png b/third_party/blink/web_tests/platform/mac/svg/as-background-image/svg-as-background-3-expected.png
index b662737a..67644339 100644
--- a/third_party/blink/web_tests/platform/mac/svg/as-background-image/svg-as-background-3-expected.png
+++ b/third_party/blink/web_tests/platform/mac/svg/as-background-image/svg-as-background-3-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/svg/wicd/test-scalable-background-image2-expected.png b/third_party/blink/web_tests/platform/mac/svg/wicd/test-scalable-background-image2-expected.png
index b52fb41..b30acc9 100644
--- a/third_party/blink/web_tests/platform/mac/svg/wicd/test-scalable-background-image2-expected.png
+++ b/third_party/blink/web_tests/platform/mac/svg/wicd/test-scalable-background-image2-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/controls-refresh-hc/fast/forms/color-scheme/color/color-picker-appearance-expected.png b/third_party/blink/web_tests/platform/mac/virtual/controls-refresh-hc/fast/forms/color-scheme/color/color-picker-appearance-expected.png
index e53a7e8..9b27031 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/controls-refresh-hc/fast/forms/color-scheme/color/color-picker-appearance-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/controls-refresh-hc/fast/forms/color-scheme/color/color-picker-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/dark-color-scheme/fast/forms/color-scheme/color/color-picker-appearance-expected.png b/third_party/blink/web_tests/platform/mac/virtual/dark-color-scheme/fast/forms/color-scheme/color/color-picker-appearance-expected.png
index 4eb24ef..da2f7b8c 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/dark-color-scheme/fast/forms/color-scheme/color/color-picker-appearance-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/dark-color-scheme/fast/forms/color-scheme/color/color-picker-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/dark-system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png b/third_party/blink/web_tests/platform/mac/virtual/dark-system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png
index fb5bce8..91c1688 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/dark-system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/dark-system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/color-profile-image-canvas-svg-expected.png b/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/color-profile-image-canvas-svg-expected.png
index 93ddabe..ddfd164 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/color-profile-image-canvas-svg-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/color-profile-image-canvas-svg-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/color-profile-munsell-adobe-to-srgb-expected.png b/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/color-profile-munsell-adobe-to-srgb-expected.png
index 183ab2f..13013cb5b 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/color-profile-munsell-adobe-to-srgb-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/color-profile-munsell-adobe-to-srgb-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/color-profile-munsell-srgb-to-srgb-expected.png b/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/color-profile-munsell-srgb-to-srgb-expected.png
index 7606226..0d95b5e3 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/color-profile-munsell-srgb-to-srgb-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/color-profile-munsell-srgb-to-srgb-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/jpeg-yuv-progressive-canvas-expected.png b/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/jpeg-yuv-progressive-canvas-expected.png
new file mode 100644
index 0000000..1aa13baa
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/jpeg-yuv-progressive-canvas-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/optimize-contrast-canvas-expected.png b/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/optimize-contrast-canvas-expected.png
new file mode 100644
index 0000000..4b949f6
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/optimize-contrast-canvas-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/paint-subrect-expected.png b/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/paint-subrect-expected.png
new file mode 100644
index 0000000..1fca07f0
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/paint-subrect-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/paint-subrect-grid-expected.png b/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/paint-subrect-grid-expected.png
new file mode 100644
index 0000000..f55c833
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/paint-subrect-grid-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/pixelated-canvas-expected.png b/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/pixelated-canvas-expected.png
new file mode 100644
index 0000000..1021936
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/virtual/exotic-color-space/images/pixelated-canvas-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/media-foundation-for-clear-dcomp/media/alpha-video-playback-expected.png b/third_party/blink/web_tests/platform/mac/virtual/media-foundation-for-clear-dcomp/media/alpha-video-playback-expected.png
new file mode 100644
index 0000000..e2872f31
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/virtual/media-foundation-for-clear-dcomp/media/alpha-video-playback-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/media-foundation-for-clear-frameserver/media/alpha-video-playback-expected.png b/third_party/blink/web_tests/platform/mac/virtual/media-foundation-for-clear-frameserver/media/alpha-video-playback-expected.png
new file mode 100644
index 0000000..e2872f31
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/virtual/media-foundation-for-clear-frameserver/media/alpha-video-playback-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/no-alloc-direct-call/fast/canvas/canvas-hidpi-blurry-expected.png b/third_party/blink/web_tests/platform/mac/virtual/no-alloc-direct-call/fast/canvas/canvas-hidpi-blurry-expected.png
new file mode 100644
index 0000000..6a9b251
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/virtual/no-alloc-direct-call/fast/canvas/canvas-hidpi-blurry-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/no-alloc-direct-call/fast/canvas/canvas-toDataURL-jpeg-maximum-quality-expected.png b/third_party/blink/web_tests/platform/mac/virtual/no-alloc-direct-call/fast/canvas/canvas-toDataURL-jpeg-maximum-quality-expected.png
new file mode 100644
index 0000000..ad7239b
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/virtual/no-alloc-direct-call/fast/canvas/canvas-toDataURL-jpeg-maximum-quality-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/virtual/system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png b/third_party/blink/web_tests/platform/mac/virtual/system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png
index fa3bcbc..c0ced16 100644
--- a/third_party/blink/web_tests/platform/mac/virtual/system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png
+++ b/third_party/blink/web_tests/platform/mac/virtual/system-color-picker-appearance/fast/forms/color-scheme/color/color-picker-appearance-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/gradients/conic-gradient-expected.png b/third_party/blink/web_tests/platform/win/fast/gradients/conic-gradient-expected.png
index cab834f..92291e33 100644
--- a/third_party/blink/web_tests/platform/win/fast/gradients/conic-gradient-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/gradients/conic-gradient-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/gradients/generated-gradients-expected.png b/third_party/blink/web_tests/platform/win/fast/gradients/generated-gradients-expected.png
index 5478656..3b7b9f1 100644
--- a/third_party/blink/web_tests/platform/win/fast/gradients/generated-gradients-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/gradients/generated-gradients-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/gradients/simple-gradients-expected.png b/third_party/blink/web_tests/platform/win/fast/gradients/simple-gradients-expected.png
index 24061be..4cf5a70 100644
--- a/third_party/blink/web_tests/platform/win/fast/gradients/simple-gradients-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/gradients/simple-gradients-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/gradients/unprefixed-linear-gradients-color-hints-expected.png b/third_party/blink/web_tests/platform/win/fast/gradients/unprefixed-linear-gradients-color-hints-expected.png
index 3970797..b83e74d 100644
--- a/third_party/blink/web_tests/platform/win/fast/gradients/unprefixed-linear-gradients-color-hints-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/gradients/unprefixed-linear-gradients-color-hints-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/gradients/unprefixed-repeating-gradient-color-hint-expected.png b/third_party/blink/web_tests/platform/win/fast/gradients/unprefixed-repeating-gradient-color-hint-expected.png
index b784d383..0ebb499 100644
--- a/third_party/blink/web_tests/platform/win/fast/gradients/unprefixed-repeating-gradient-color-hint-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/gradients/unprefixed-repeating-gradient-color-hint-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/svg/W3C-SVG-1.1/pservers-grad-14-b-expected.png b/third_party/blink/web_tests/platform/win/svg/W3C-SVG-1.1/pservers-grad-14-b-expected.png
index 922589a..90034c74 100644
--- a/third_party/blink/web_tests/platform/win/svg/W3C-SVG-1.1/pservers-grad-14-b-expected.png
+++ b/third_party/blink/web_tests/platform/win/svg/W3C-SVG-1.1/pservers-grad-14-b-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/svg/as-background-image/svg-as-background-3-expected.png b/third_party/blink/web_tests/platform/win/svg/as-background-image/svg-as-background-3-expected.png
index 524e955..cc57d74 100644
--- a/third_party/blink/web_tests/platform/win/svg/as-background-image/svg-as-background-3-expected.png
+++ b/third_party/blink/web_tests/platform/win/svg/as-background-image/svg-as-background-3-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/svg/wicd/test-scalable-background-image2-expected.png b/third_party/blink/web_tests/platform/win/svg/wicd/test-scalable-background-image2-expected.png
index 4bf8691..46ad5fe 100644
--- a/third_party/blink/web_tests/platform/win/svg/wicd/test-scalable-background-image2-expected.png
+++ b/third_party/blink/web_tests/platform/win/svg/wicd/test-scalable-background-image2-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/svg/as-background-image/svg-as-background-with-relative-size-expected.png b/third_party/blink/web_tests/svg/as-background-image/svg-as-background-with-relative-size-expected.png
index c4d42e1..e2e753a 100644
--- a/third_party/blink/web_tests/svg/as-background-image/svg-as-background-with-relative-size-expected.png
+++ b/third_party/blink/web_tests/svg/as-background-image/svg-as-background-with-relative-size-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/not-site-per-process-nonexclusive/README.md b/third_party/blink/web_tests/virtual/not-site-per-process-nonexclusive/README.md
new file mode 100644
index 0000000..6481c20
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/not-site-per-process-nonexclusive/README.md
@@ -0,0 +1,5 @@
+# virtual/not-site-per-process
+
+## Summary
+
+See virtual/not-site-per-process. This virtual test suite removes the `exclusive_tests: "ALL"` configuration, and specifies tests that should be run both with and without site isolation enabled.
\ No newline at end of file
diff --git a/third_party/blink/web_tests/virtual/prefetch-no-vary-search/README.md b/third_party/blink/web_tests/virtual/prefetch-no-vary-search/README.md
new file mode 100644
index 0000000..60ac226f
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/prefetch-no-vary-search/README.md
@@ -0,0 +1 @@
+Web Platform Tests for No-Vary-Search support in prefetch cache.
diff --git a/third_party/nearby/README.chromium b/third_party/nearby/README.chromium
index 6e4f2f01..dda6a8b 100644
--- a/third_party/nearby/README.chromium
+++ b/third_party/nearby/README.chromium
@@ -1,7 +1,7 @@
 Name: Nearby Connections Library
 Short Name: Nearby
 URL: https://github.com/google/nearby
-Version: 73c1fba668a86dd003fa2ac6af86ab7d89c09908
+Version: b3f85342261db10bbeb2e5a07c7e54b616309b9e
 License: Apache 2.0
 License File: LICENSE
 Security Critical: yes
diff --git a/tools/licenses/licenses_test.py b/tools/licenses/licenses_test.py
index 4772582..1af0ebe 100755
--- a/tools/licenses/licenses_test.py
+++ b/tools/licenses/licenses_test.py
@@ -40,6 +40,16 @@
             'Name': 'lib3',
             'License File': os.path.join('third_party', 'lib3', 'LICENSE'),
         },
+        os.path.join('third_party', 'lib3-v1'): {
+            # Test SPDX license file dedup. (different name, same license file)
+            'Name': 'lib3-v1',
+            'License File': os.path.join('third_party', 'lib3', 'LICENSE'),
+        },
+        os.path.join('third_party', 'lib3-v2'): {
+            # Test SPDX id dedup. (same name, different license file)
+            'Name': 'lib3',
+            'License File': os.path.join('third_party', 'lib3-v2', 'LICENSE'),
+        },
     }
 
   def test_get_third_party_deps_from_gn_deps_output(self):
@@ -91,6 +101,8 @@
         'lib1 license text\n',
         'lib2 license text\n',
         'lib3 license text\n',
+        'lib3 license text\n',
+        'lib3-v2 license text\n',
     ]
 
     license_txt = licenses.GenerateLicenseFilePlainText(
@@ -113,6 +125,16 @@
         'lib3',
         '--------------------',
         'lib3 license text',
+        '',
+        '--------------------',
+        'lib3-v1',
+        '--------------------',
+        'lib3 license text',
+        '',
+        '--------------------',
+        'lib3-v2',
+        '--------------------',
+        'lib3-v2 license text',
     ]) + '\n'  # extra new line to account for join not adding one to the end
     self.assertEqual(license_txt, expected)
 
@@ -122,6 +144,7 @@
         'lib1\nlicense text\n',
         'lib2\nlicense text\n',
         'lib3\nlicense text\n',
+        'lib3-v2\nlicense text\n',
     ]
 
     license_txt = licenses.GenerateLicenseFileSpdx(
@@ -167,11 +190,21 @@
             "SPDXID": "SPDXRef-Package-lib3",
             "name": "lib3",
             "licenseConcluded": "LicenseRef-lib3"
+        },
+        {
+            "SPDXID": "SPDXRef-Package-lib3-v1",
+            "name": "lib3-v1",
+            "licenseConcluded": "LicenseRef-lib3"
+        },
+        {
+            "SPDXID": "SPDXRef-Package-lib3-1",
+            "name": "lib3",
+            "licenseConcluded": "LicenseRef-lib3-1"
         }
     ],
     "hasExtractedLicensingInfos": [
         {
-            "name": "Chromium License",
+            "name": "Chromium",
             "licenseId": "LicenseRef-Chromium",
             "extractedText": "root\\nlicense text\\n",
             "crossRefs": [
@@ -181,7 +214,7 @@
             ]
         },
         {
-            "name": "lib1 License",
+            "name": "lib1",
             "licenseId": "LicenseRef-lib1",
             "extractedText": "lib1\\nlicense text\\n",
             "crossRefs": [
@@ -191,7 +224,7 @@
             ]
         },
         {
-            "name": "lib2 License",
+            "name": "lib2",
             "licenseId": "LicenseRef-lib2",
             "extractedText": "lib2\\nlicense text\\n",
             "crossRefs": [
@@ -201,7 +234,7 @@
             ]
         },
         {
-            "name": "lib3 License",
+            "name": "lib3",
             "licenseId": "LicenseRef-lib3",
             "extractedText": "lib3\\nlicense text\\n",
             "crossRefs": [
@@ -209,6 +242,16 @@
                     "url": "http://google.com/third_party/lib3/LICENSE"
                 }
             ]
+        },
+        {
+            "name": "lib3",
+            "licenseId": "LicenseRef-lib3-1",
+            "extractedText": "lib3-v2\\nlicense text\\n",
+            "crossRefs": [
+                {
+                    "url": "http://google.com/third_party/lib3-v2/LICENSE"
+                }
+            ]
         }
     ],
     "relationships": [
@@ -226,6 +269,16 @@
             "spdxElementId": "SPDXRef-Package-Chromium",
             "relationshipType": "CONTAINS",
             "relatedSpdxElement": "SPDXRef-Package-lib3"
+        },
+        {
+            "spdxElementId": "SPDXRef-Package-Chromium",
+            "relationshipType": "CONTAINS",
+            "relatedSpdxElement": "SPDXRef-Package-lib3-v1"
+        },
+        {
+            "spdxElementId": "SPDXRef-Package-Chromium",
+            "relationshipType": "CONTAINS",
+            "relatedSpdxElement": "SPDXRef-Package-lib3-1"
         }
     ]
 }'''
diff --git a/tools/licenses/spdx_writer.py b/tools/licenses/spdx_writer.py
index 0ede233..c612d18 100644
--- a/tools/licenses/spdx_writer.py
+++ b/tools/licenses/spdx_writer.py
@@ -2,12 +2,13 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import collections
 import dataclasses
 import json
 import os
 import pathlib
 import re
-from typing import Callable
+from typing import Callable, DefaultDict, Tuple
 
 
 class SpdxWriter:
@@ -22,7 +23,9 @@
                doc_namespace: str = None,
                read_file=lambda x: pathlib.Path(x).read_text(encoding='utf-8')):
     self.root_package = _Package(root_package_name, root_license)
-    self.packages = [self.root_package]
+    # Use dict to ensure no duplicate pkgs.
+    # In >=py3.7 dicts are ordered by insertion.
+    self.packages = {}
 
     self.root = root
     self.link_prefix = link_prefix
@@ -35,7 +38,7 @@
 
   def add_package(self, name: str, license_file: str):
     """Add a package to the SPDX output."""
-    self.packages.append(_Package(name, license_file))
+    self.packages[_Package(name, license_file)] = None
 
   def write_to_file(self, file_path: str):
     """Writes the content to a file."""
@@ -50,13 +53,10 @@
     for pkg in self.packages:
       writer.add_package(pkg)
 
-    for pkg in self.packages:
-      writer.add_license_file(pkg)
-
     return writer.write()
 
 
-@dataclasses.dataclass
+@dataclasses.dataclass(frozen=True)
 class _Package:
   """Stores needed data for a package to output SPDX."""
   name: str
@@ -118,37 +118,93 @@
         'relationships': [],
     }
 
+    # Used to dedup license files based on file path.
+    self.existing_license_files = {}  # 'file path': 'licenseId'
+    # Used to make sure that there are no duplicate ids.
+    self.existing_package_ids = collections.defaultdict(int)  # 'packageId': num
+    self.existing_license_ids = collections.defaultdict(int)  # 'licenseId': num
+
+    # Add the root package to make sure that its ID isn't taken.
+    self.add_package(root_package)
+
   def write(self) -> str:
     """Returns a JSON string for the current state of the writer."""
     return json.dumps(self.content, indent=4)
 
+  def _get_dedup_id(self, elem_id: str, id_dict: DefaultDict[str, int]) -> str:
+    """Returns a unique id given a dictionary with existing ids.
+
+    IDs are case sensitive, so this method ignores casing for uniqueness.
+
+    Args:
+      elem_id: the requested id to use for the element.
+      id_dict: dictionary holding already used ids.
+
+    Returns:
+      When the elem_id is already unique, return elem_id.
+      When the elem_id has been used, return elem_id + '-[next num]'.
+    """
+    suffix = id_dict[elem_id]
+    id_dict[elem_id] += 1
+    return f'{elem_id}-{suffix}' if suffix > 0 else elem_id
+
+  def _get_package_id(self, pkg: _Package) -> str:
+    """Makes sure that there are no pkg id duplicates."""
+    return self._get_dedup_id(pkg.package_spdx_id, self.existing_package_ids)
+
+  def _get_license_id(self, pkg: _Package) -> Tuple[str, bool]:
+    """Handles license deduplication.
+
+    If this pkg.file has already been seen, reuse that same id instead. If
+    there are two packages with the same name but different license files,
+    handle deduping the names.
+
+    Args:
+      pkg: The package to get a license id for.
+
+    Returns:
+      First return value is the id, second is whether the license needs to be
+        added to the SPDX doc (False if it already exists in the doc).
+    """
+    existing = self.existing_license_files.get(pkg.file)
+    if existing:
+      return existing, False
+
+    license_id = self._get_dedup_id(pkg.license_spdx_id,
+                                    self.existing_license_ids)
+    self.existing_license_files[pkg.file] = license_id
+    return license_id, True
+
   def add_package(self, pkg: _Package):
     """Writes a package to the file (package metadata)."""
+    pkg_id = self._get_package_id(pkg)
+    license_id, need_to_add_license = self._get_license_id(pkg)
+
     self.content['packages'].append({
-        'SPDXID': pkg.package_spdx_id,
+        'SPDXID': pkg_id,
         'name': pkg.name,
-        'licenseConcluded': pkg.license_spdx_id,
+        'licenseConcluded': license_id,
     })
 
     if pkg.package_spdx_id != self.root_package_id:
       self.content['relationships'].append({
-          'spdxElementId':
-          self.root_package_id,
-          'relationshipType':
-          'CONTAINS',
-          'relatedSpdxElement':
-          pkg.package_spdx_id,
+          'spdxElementId': self.root_package_id,
+          'relationshipType': 'CONTAINS',
+          'relatedSpdxElement': pkg_id,
       })
 
-  def add_license_file(self, pkg: _Package):
+    if need_to_add_license:
+      self._add_license_file(pkg, license_id)
+
+  def _add_license_file(self, pkg: _Package, license_id: str):
     """Writes a license to the file (raw license text)."""
     spdx_path = _get_spdx_path(self.root, pkg.file)
     url = f'{self.link_prefix}{spdx_path.replace(os.sep, "/")}'
     self.content['hasExtractedLicensingInfos'].append({
         'name':
-        f'{pkg.name} License',
+        f'{pkg.name}',
         'licenseId':
-        pkg.license_spdx_id,
+        license_id,
         'extractedText':
         self.read_file(pkg.file),
         'crossRefs': [{
diff --git a/tools/licenses/spdx_writer_test.py b/tools/licenses/spdx_writer_test.py
index 67746dd..15945c9d 100755
--- a/tools/licenses/spdx_writer_test.py
+++ b/tools/licenses/spdx_writer_test.py
@@ -4,6 +4,7 @@
 # found in the LICENSE file.
 """Unit tests for //tools/spdx_writer.py."""
 
+import collections
 import os
 import sys
 import unittest
@@ -13,6 +14,7 @@
 
 from spdx_writer import _get_spdx_path
 from spdx_writer import _Package
+from spdx_writer import _SPDXJSONWriter
 from test_utils import path_from_root
 
 
@@ -41,5 +43,38 @@
     self.assertEqual(self.p.license_spdx_id, 'LicenseRef-abc-def-ghi')
 
 
+class SPDXJSONWriterTest(unittest.TestCase):
+  def setUp(self):
+    super().setUp()
+
+    root_pkg = _Package('root', path_from_root('src', 'LICENSE'))
+    self.writer = _SPDXJSONWriter(path_from_root('src'), root_pkg, '', '',
+                                  '', lambda _: '')
+
+  def test_get_dedup_id(self):
+    id_dict = collections.defaultdict(int)
+    elem_id = 'abc'
+
+    id1 = self.writer._get_dedup_id(elem_id, id_dict)
+    self.assertEqual(id1, elem_id)
+
+    id2 = self.writer._get_dedup_id(elem_id, id_dict)
+    self.assertEqual(id2, elem_id + '-1')
+
+  def test_get_license_id(self):
+    license_path = path_from_root('src', 'p1', 'LICENSE')
+    p1 = _Package('p1', license_path)
+
+    p1_id, need_license = self.writer._get_license_id(p1)
+    self.assertEqual(p1_id, p1.license_spdx_id)
+    self.assertTrue(need_license)
+
+    # Try a new package with the same license path.
+    p2 = _Package('p2', license_path)
+    p2_id, need_license = self.writer._get_license_id(p2)
+    self.assertEqual(p2_id, p1.license_spdx_id)
+    self.assertFalse(need_license)
+
+
 if __name__ == '__main__':
   unittest.main()
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 040a599..65dd903f 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -29430,6 +29430,18 @@
   <int value="9" label="WMV"/>
 </enum>
 
+<enum name="DownloadWarningAction">
+  <int value="0" label="SHOWN"/>
+  <int value="1" label="PROCEED"/>
+  <int value="2" label="DISCARD"/>
+  <int value="3" label="KEEP"/>
+  <int value="4" label="CLOSE"/>
+  <int value="5" label="CANCEL"/>
+  <int value="6" label="DISMISS"/>
+  <int value="7" label="BACK"/>
+  <int value="8" label="OPEN_SUBPAGE"/>
+</enum>
+
 <enum name="DownloadWarningSurface">
   <int value="1" label="BUBBLE_MAINPAGE"/>
   <int value="2" label="BUBBLE_SUBPAGE"/>
@@ -32315,6 +32327,7 @@
   <int value="1042" label="ShowCastSessionsStartedByOtherDevices"/>
   <int value="1043" label="CloudAPAuthEnabled"/>
   <int value="1044" label="UsbDetectorNotificationEnabled"/>
+  <int value="1045" label="LacrosSelection"/>
 </enum>
 
 <enum name="EnterprisePoliciesSources">
@@ -51322,6 +51335,7 @@
   <int value="9" label="Text node sibling (includes &lt;b&gt;, etc)"/>
   <int value="10" label="value attribute"/>
   <int value="11" label="&lt;label&gt; for attribute"/>
+  <int value="12" label="Succeeding DOM node overlaying the input"/>
 </enum>
 
 <enum name="InfoBarClosingStates">
diff --git a/tools/metrics/histograms/metadata/android/histograms.xml b/tools/metrics/histograms/metadata/android/histograms.xml
index dcc63339..fde8bad 100644
--- a/tools/metrics/histograms/metadata/android/histograms.xml
+++ b/tools/metrics/histograms/metadata/android/histograms.xml
@@ -1335,6 +1335,21 @@
   </summary>
 </histogram>
 
+<histogram
+    name="Android.FamilyLinkUser.LocalWebApprovalParentAuthenticationError"
+    units="Status error codes raised in GMS Core" expires_after="2023-03-26">
+  <owner>anthie@google.com</owner>
+  <owner>ljjlee@google.com</owner>
+  <owner>chrome-kids-eng@google.com</owner>
+  <owner>cros-families-eng@google.com</owner>
+  <summary>
+    The different GMS error status codes that a user may encounter during the
+    parent authentication step in the local web approval flow. Recorded on
+    Android only. See go/chrome-local-web-approval-parent-auth-error-codes
+    (Googlers only) for the error code definitions.
+  </summary>
+</histogram>
+
 <histogram base="true" name="Android.FeatureModules.AvailabilityStatus"
     enum="FeatureModuleAvailabilityStatus" expires_after="2023-10-16">
 <!-- Name completed by histogram_suffixes
diff --git a/tools/metrics/histograms/metadata/arc/histograms.xml b/tools/metrics/histograms/metadata/arc/histograms.xml
index 6907b1e..faab4f5 100644
--- a/tools/metrics/histograms/metadata/arc/histograms.xml
+++ b/tools/metrics/histograms/metadata/arc/histograms.xml
@@ -498,7 +498,7 @@
 </histogram>
 
 <histogram name="Arc.AppShortcutSearchResult.ShortcutStatus"
-    enum="ArcAppShortcutStatus" expires_after="2022-12-04">
+    enum="ArcAppShortcutStatus" expires_after="2023-06-10">
   <owner>batoon@google.com</owner>
   <owner>arc-core@google.com</owner>
   <summary>
@@ -509,7 +509,7 @@
 </histogram>
 
 <histogram name="Arc.AppShortcutsRequest.ShortcutStatus"
-    enum="ArcAppShortcutStatus" expires_after="2022-12-04">
+    enum="ArcAppShortcutStatus" expires_after="2023-06-10">
   <owner>batoon@google.com</owner>
   <owner>arc-core@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/ash/histograms.xml b/tools/metrics/histograms/metadata/ash/histograms.xml
index 7f72314..1a8060f 100644
--- a/tools/metrics/histograms/metadata/ash/histograms.xml
+++ b/tools/metrics/histograms/metadata/ash/histograms.xml
@@ -346,6 +346,22 @@
   </summary>
 </histogram>
 
+<histogram name="Ash.ArcAppInitialAppsInstallDuration" units="ms"
+    expires_after="2023-12-01">
+  <owner>alemate@chromium.org</owner>
+  <owner>khmel@chromium.org</owner>
+  <owner>arc-performance@google.com</owner>
+  <owner>chromeos-perfmetrics-eng@google.com</owner>
+  <summary>
+    For each user after they opted-in into ARC++, records the duration of
+    initial apps installation. Duration is measured as an interval between the
+    opt-in and the moment when all default apps become either &quot;ready&quot;
+    or &quot;update error&quot;. This is only reported within the same opt-in
+    session, and is not reported if default apps were not installed within the
+    session.
+  </summary>
+</histogram>
+
 <histogram
     name="Ash.Assistant.AnimationSmoothness{AshAssistantAnimationSmoothness}"
     units="%" expires_after="2021-11-21">
@@ -3323,6 +3339,25 @@
   </summary>
 </histogram>
 
+<histogram name="Ash.NotifierFramework.Nudge.TimeToAction.{TimeRange}"
+    enum="NudgeCatalogName" expires_after="2023-02-10">
+  <owner>kradtke@google.com</owner>
+  <owner>cros-status-area-eng@google.com</owner>
+  <summary>
+    Tracks the time from when a specific educational nudge is shown to when it's
+    interacted with. Starts measuring time when the nudge is shown and records
+    the Nudge catalog name in one of the time buckets available if the user
+    performs the expected action that the nudge is informing about. This metric
+    can be compared with `Ash.NotifierFramework.Nudge.ShownCount` to infer the
+    nudge action wasn't taken within that session.
+  </summary>
+  <token key="TimeRange">
+    <variant name="Within1h"/>
+    <variant name="Within1m"/>
+    <variant name="WithinSession"/>
+  </token>
+</histogram>
+
 <histogram name="Ash.NotifierFramework.Toast.Dismissed.{TimeRange}"
     enum="ToastCatalogName" expires_after="2023-02-10">
   <owner>kradtke@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/download/histograms.xml b/tools/metrics/histograms/metadata/download/histograms.xml
index 54d1cf78..04f8ca9 100644
--- a/tools/metrics/histograms/metadata/download/histograms.xml
+++ b/tools/metrics/histograms/metadata/download/histograms.xml
@@ -1447,6 +1447,19 @@
   <summary>Records events for local video thumbnail retrieval.</summary>
 </histogram>
 
+<histogram name="Download.WarningData.ActionAdded" enum="DownloadWarningAction"
+    expires_after="2023-06-07">
+  <owner>xinghuilu@chromium.org</owner>
+  <owner>chrome-counter-abuse-alerts@google.com</owner>
+  <summary>
+    Records the warning action that is successfully added in warning action
+    event. Logged each time an action is taken on the download warning. For the
+    SHOWN action, only logged for the first time. Not logged if the action is
+    not added because the first warning is missing or the events have exceeded
+    the max length.
+  </summary>
+</histogram>
+
 <histogram name="Download.WarningData.AddWarningActionEventOutcome"
     enum="DownloadAddWarningActionEventOutcome" expires_after="2023-05-22">
   <owner>xinghuilu@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/gpu/histograms.xml b/tools/metrics/histograms/metadata/gpu/histograms.xml
index df5966a..7bcca37 100644
--- a/tools/metrics/histograms/metadata/gpu/histograms.xml
+++ b/tools/metrics/histograms/metadata/gpu/histograms.xml
@@ -1129,7 +1129,7 @@
 </histogram>
 
 <histogram name="GPU.PaintOpReader.DeserializationError"
-    enum="PaintOpDeserializationError" expires_after="2023-01-22">
+    enum="PaintOpDeserializationError" expires_after="2024-01-22">
   <owner>junov@chromium.org</owner>
   <owner>graphics-dev@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/history/histograms.xml b/tools/metrics/histograms/metadata/history/histograms.xml
index ed3fac18c..7fff9a1 100644
--- a/tools/metrics/histograms/metadata/history/histograms.xml
+++ b/tools/metrics/histograms/metadata/history/histograms.xml
@@ -1353,6 +1353,27 @@
   </token>
 </histogram>
 
+<histogram name="History.Clusters.UpdateClusters.TimeBetweenTasks" units="ms"
+    expires_after="2023-10-01">
+  <owner>manukh@chromium.org</owner>
+  <owner>chrome-journeys@google.com</owner>
+  <component>UI&gt;Browser&gt;Journeys</component>
+  <summary>
+    The time elapsed between subsequent cluster update tasks
+    (HistoryClustersServiceTaskUpdateClusters). Depending on finch params, this
+    occurs either:
+
+    1) Never (e.g. if updating and persisting clusters is disabled).
+
+    2) OR N time after startup in addition to every M time; where N is typically
+    5-60 minutes, and M is typically 1-12 hours.
+
+    3) OR on keyword refresh request bounded to at most every M time, where M is
+    typically 1-12 hours. Keyword refresh requests occur when the omnibox input
+    looks like a search query and journeys in the omnibox is enabled.
+  </summary>
+</histogram>
+
 <histogram name="History.Clusters.WebUISessionDuration" units="ms"
     expires_after="2023-04-16">
   <owner>tommycli@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index fa42a9a..b59c6ed 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -7983,7 +7983,7 @@
 </histogram>
 
 <histogram base="true" name="LoadingPredictor.PreconnectLearningPrecision"
-    units="%" expires_after="2022-12-19">
+    units="%" expires_after="2023-12-12">
   <owner>spelchat@chromium.org</owner>
   <owner>chrome-brapp-loading@chromium.org</owner>
   <summary>
@@ -8018,7 +8018,7 @@
 </histogram>
 
 <histogram name="LoadingPredictor.PreresolveCount" units="hosts"
-    expires_after="2022-12-19">
+    expires_after="2023-12-12">
   <owner>spelchat@chromium.org</owner>
   <owner>chrome-brapp-loading@chromium.org</owner>
   <summary>
@@ -8029,7 +8029,7 @@
 </histogram>
 
 <histogram name="LoadingPredictor.PreresolveHitsPercentage" units="%"
-    expires_after="2022-12-19">
+    expires_after="2023-12-12">
   <owner>spelchat@chromium.org</owner>
   <owner>chrome-brapp-loading@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/startup/histograms.xml b/tools/metrics/histograms/metadata/startup/histograms.xml
index 0fb01294..f4c76d3 100644
--- a/tools/metrics/histograms/metadata/startup/histograms.xml
+++ b/tools/metrics/histograms/metadata/startup/histograms.xml
@@ -219,7 +219,7 @@
 </histogram>
 
 <histogram name="Startup.Android.FeedsLoadingPlaceholderShown.Instant"
-    units="ms" expires_after="2023-01-15">
+    units="ms" expires_after="2023-06-30">
   <owner>hanxi@chromium.org</owner>
   <owner>wychen@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/tab/histograms.xml b/tools/metrics/histograms/metadata/tab/histograms.xml
index c69d0ec..65db5f3 100644
--- a/tools/metrics/histograms/metadata/tab/histograms.xml
+++ b/tools/metrics/histograms/metadata/tab/histograms.xml
@@ -266,6 +266,16 @@
   </summary>
 </histogram>
 
+<histogram name="Tab.AgeAtDeletion" units="ms" expires_after="2023-12-13">
+  <owner>ewannpv@chromium.org</owner>
+  <owner>gambard@chromium.org</owner>
+  <owner>bling-team@google.com</owner>
+  <summary>
+    The age of a Tab when closed (Time between creation time on the current
+    device and closure time).
+  </summary>
+</histogram>
+
 <histogram name="Tab.AgeUponRestoreFromColdStart" units="minutes"
     expires_after="M88">
   <owner>dtrainor@chromium.org</owner>
diff --git a/tools/metrics/structured/structured.xml b/tools/metrics/structured/structured.xml
index 33930cc8..7c449c5 100644
--- a/tools/metrics/structured/structured.xml
+++ b/tools/metrics/structured/structured.xml
@@ -416,6 +416,13 @@
     Project used to record a sequence of events that are related to each other.
   </summary>
 
+  <event name="UserLogin">
+    <summary>
+      An event to signify a user is using the system.
+    </summary>
+
+  </event>
+
   <event name="Test1">
     <summary>
       Test event used for unit tests to ensure that the code-gen for the event
diff --git a/tools/perf/core/bot_platforms.py b/tools/perf/core/bot_platforms.py
index d83fe216..3a45dcbb 100644
--- a/tools/perf/core/bot_platforms.py
+++ b/tools/perf/core/bot_platforms.py
@@ -681,7 +681,7 @@
     'android-pixel4a_power-perf-pgo', 'Android QD4A.200102.001.A1',
     _ANDROID_PIXEL4A_POWER_BENCHMARK_CONFIGS, 12, 'android')
 ANDROID_GO_WEMBLEY = PerfPlatform('android-go-wembley-perf',
-                                  'Android M | MASTER',
+                                  'Android 9131443',
                                   _ANDROID_GO_WEMBLEY_BENCHMARK_CONFIGS, 2,
                                   'android')
 ANDROID_NEW_PIXEL = PerfPlatform('android-new-pixel-perf',
diff --git a/tools/perf/core/perf_data_generator.py b/tools/perf/core/perf_data_generator.py
index 888e428d..8bde4a9 100755
--- a/tools/perf/core/perf_data_generator.py
+++ b/tools/perf/core/perf_data_generator.py
@@ -904,8 +904,7 @@
         'dimension': {
             'pool': 'chrome.tests.perf',
             'os': 'Android',
-            'device_type': 'wembley',
-            'device_os': 'MASTER',
+            'device_type': 'wembley_2GB',
             'device_os_flavor': 'google',
         },
     },
diff --git a/tools/perf/core/perf_json_config_validator.py b/tools/perf/core/perf_json_config_validator.py
index 3a88d3e..907c3bd 100644
--- a/tools/perf/core/perf_json_config_validator.py
+++ b/tools/perf/core/perf_json_config_validator.py
@@ -68,7 +68,6 @@
         raise ValueError('Invalid perf pool %s in %s' % (v, builder_name))
       if k == 'os' and v == 'Android':
         if (not 'device_type' in dimension.keys() or
-            not 'device_os' in dimension.keys() or
             not 'device_os_flavor' in dimension.keys()):
           raise ValueError(
               'Invalid android dimensions %s in %s' % (v, builder_name))
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 0d90eae..24101be 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,8 +5,8 @@
             "full_remote_path": "perfetto-luci-artifacts/v31.0/linux-arm64/trace_processor_shell"
         },
         "win": {
-            "hash": "ecbfa1398bd09e5a57bc5218aa9c7aec691cce25",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/ae7f044c460c89dac35657f618549998d665d7eb/trace_processor_shell.exe"
+            "hash": "cc817f9c3998e1c2dea11752c61456413c98f1f6",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/aa7e493dcf9bf77707735b98723b916e08561bbe/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "6373f26144aad58f230d11d6a91efda5a09c9873",
@@ -21,8 +21,8 @@
             "full_remote_path": "perfetto-luci-artifacts/v31.0/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "47d0feffdbcd8c0dfe7650dd0874b4c7a291153f",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/ae7f044c460c89dac35657f618549998d665d7eb/trace_processor_shell"
+            "hash": "375e45389f78aec067a6df88497229d65190aa06",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/aa7e493dcf9bf77707735b98723b916e08561bbe/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/perf/core/shard_maps/android-go-wembley-perf_map.json b/tools/perf/core/shard_maps/android-go-wembley-perf_map.json
index 89006ec..b0494fa 100644
--- a/tools/perf/core/shard_maps/android-go-wembley-perf_map.json
+++ b/tools/perf/core/shard_maps/android-go-wembley-perf_map.json
@@ -3,23 +3,28 @@
         "benchmarks": {
             "speedometer2": {
                 "abridged": false
+            },
+            "startup.mobile": {
+                "end": 2,
+                "abridged": false
             }
         }
     },
     "1": {
         "benchmarks": {
             "startup.mobile": {
+                "begin": 2,
                 "abridged": false
             }
         }
     },
     "extra_infos": {
         "num_stories": 5,
-        "predicted_min_shard_time": 40,
+        "predicted_min_shard_time": 20,
         "predicted_min_shard_index": 1,
-        "predicted_max_shard_time": 140.0,
+        "predicted_max_shard_time": 30,
         "predicted_max_shard_index": 0,
-        "shard #0": 140.0,
-        "shard #1": 40
+        "shard #0": 30,
+        "shard #1": 20
     }
 }
\ No newline at end of file
diff --git a/tools/perf/core/shard_maps/timing_data/android-go-wembley-perf_timing.json b/tools/perf/core/shard_maps/timing_data/android-go-wembley-perf_timing.json
index 08f2d08..0637a08 100644
--- a/tools/perf/core/shard_maps/timing_data/android-go-wembley-perf_timing.json
+++ b/tools/perf/core/shard_maps/timing_data/android-go-wembley-perf_timing.json
@@ -1,6 +1 @@
-[
-    {
-        "duration": "140.0",
-        "name": "speedometer2/Speedometer2"
-    }
-]
\ No newline at end of file
+[]
\ No newline at end of file
diff --git a/tools/traffic_annotation/auditor/chromeos/safe_list.txt b/tools/traffic_annotation/auditor/chromeos/safe_list.txt
index 96fc26073..e79f113 100644
--- a/tools/traffic_annotation/auditor/chromeos/safe_list.txt
+++ b/tools/traffic_annotation/auditor/chromeos/safe_list.txt
@@ -11,7 +11,6 @@
 all,chromeos/ash/services/device_sync/cryptauth_client_impl.cc
 all,chrome/browser/supervised_user/kids_chrome_management/kids_chrome_management_client.cc
 all,chrome/browser/ash/net/network_diagnostics/network_diagnostics_util.cc
-all,chrome/browser/ash/net/network_diagnostics/http_request_manager.cc
 all,chrome/browser/ash/net/network_diagnostics/tls_prober.cc
 all,ash/quick_pair/repository/fast_pair/fast_pair_image_decoder.cc
 all,chrome/browser/search/background/ntp_background_service.cc
diff --git a/tools/traffic_annotation/summary/annotations.xml b/tools/traffic_annotation/summary/annotations.xml
index ca60eed2..f637e36 100644
--- a/tools/traffic_annotation/summary/annotations.xml
+++ b/tools/traffic_annotation/summary/annotations.xml
@@ -405,4 +405,5 @@
  <item id="projector_xhr_loader" added_in_milestone="110" content_hash_code="071c4ac5" os_list="chromeos" file_path="ash/webui/projector_app/projector_xhr_sender.cc" />
  <item id="bruschetta_installer_download" added_in_milestone="110" content_hash_code="01b953f4" os_list="chromeos" file_path="chrome/browser/ash/bruschetta/bruschetta_installer.cc" />
  <item id="quick_start_session_auth_requester" added_in_milestone="110" content_hash_code="08353e70" os_list="chromeos" file_path="chrome/browser/ash/login/oobe_quick_start/second_device_auth_broker.cc" />
+ <item id="network_diagnostics_routines" added_in_milestone="110" content_hash_code="0007b237" os_list="chromeos" file_path="chrome/browser/ash/net/network_diagnostics/http_request_manager.cc" />
 </annotations>
diff --git a/tools/traffic_annotation/summary/grouping.xml b/tools/traffic_annotation/summary/grouping.xml
index 62326cd..64aa0f0a 100644
--- a/tools/traffic_annotation/summary/grouping.xml
+++ b/tools/traffic_annotation/summary/grouping.xml
@@ -272,6 +272,7 @@
       <annotation id="projector_xhr_loader"/>
       <annotation id="bruschetta_installer_download"/>
       <annotation id="quick_start_session_auth_requester"/>
+      <annotation id="network_diagnostics_routines"/>
     </sender>
   </group>
   <group name="Admin Features" hidden="true">
diff --git a/ui/android/BUILD.gn b/ui/android/BUILD.gn
index 67289b87..8f6206c 100644
--- a/ui/android/BUILD.gn
+++ b/ui/android/BUILD.gn
@@ -131,6 +131,14 @@
 
 android_resources("ui_java_resources") {
   sources = [
+    "java/res/anim/menu_enter.xml",
+    "java/res/anim/menu_enter_from_bottom.xml",
+    "java/res/anim/menu_enter_from_bottom_left.xml",
+    "java/res/anim/menu_enter_from_top_left.xml",
+    "java/res/anim/menu_exit.xml",
+    "java/res/anim/menu_exit_from_bottom.xml",
+    "java/res/anim/menu_exit_from_bottom_left.xml",
+    "java/res/anim/menu_exit_from_top_left.xml",
     "java/res/color/blue_when_enabled_dark.xml",
     "java/res/color/blue_when_enabled_list.xml",
     "java/res/color/default_text_color_light_list.xml",
@@ -179,6 +187,7 @@
     "java/res/values-ldrtl/values.xml",
     "java/res/values-night/colors.xml",
     "java/res/values-night/dimens.xml",
+    "java/res/values-sw600dp/dimens.xml",
     "java/res/values-sw600dp/values.xml",
     "java/res/values-sw720dp-v17/values.xml",
     "java/res/values-v17/styles.xml",
@@ -193,6 +202,7 @@
     "java/res/values/semantic_colors_adaptive.xml",
     "java/res/values/semantic_colors_non_adaptive.xml",
     "java/res/values/strings.xml",
+    "java/res/values/styles.xml",
     "java/res/values/values.xml",
   ]
 
diff --git a/components/browser_ui/widget/android/java/res/anim/menu_enter.xml b/ui/android/java/res/anim/menu_enter.xml
similarity index 100%
rename from components/browser_ui/widget/android/java/res/anim/menu_enter.xml
rename to ui/android/java/res/anim/menu_enter.xml
diff --git a/components/browser_ui/widget/android/java/res/anim/menu_enter_from_bottom.xml b/ui/android/java/res/anim/menu_enter_from_bottom.xml
similarity index 100%
rename from components/browser_ui/widget/android/java/res/anim/menu_enter_from_bottom.xml
rename to ui/android/java/res/anim/menu_enter_from_bottom.xml
diff --git a/components/browser_ui/widget/android/java/res/anim/menu_enter_from_bottom_left.xml b/ui/android/java/res/anim/menu_enter_from_bottom_left.xml
similarity index 100%
rename from components/browser_ui/widget/android/java/res/anim/menu_enter_from_bottom_left.xml
rename to ui/android/java/res/anim/menu_enter_from_bottom_left.xml
diff --git a/components/browser_ui/widget/android/java/res/anim/menu_enter_from_top_left.xml b/ui/android/java/res/anim/menu_enter_from_top_left.xml
similarity index 100%
rename from components/browser_ui/widget/android/java/res/anim/menu_enter_from_top_left.xml
rename to ui/android/java/res/anim/menu_enter_from_top_left.xml
diff --git a/components/browser_ui/widget/android/java/res/anim/menu_exit.xml b/ui/android/java/res/anim/menu_exit.xml
similarity index 100%
rename from components/browser_ui/widget/android/java/res/anim/menu_exit.xml
rename to ui/android/java/res/anim/menu_exit.xml
diff --git a/components/browser_ui/widget/android/java/res/anim/menu_exit_from_bottom.xml b/ui/android/java/res/anim/menu_exit_from_bottom.xml
similarity index 100%
rename from components/browser_ui/widget/android/java/res/anim/menu_exit_from_bottom.xml
rename to ui/android/java/res/anim/menu_exit_from_bottom.xml
diff --git a/components/browser_ui/widget/android/java/res/anim/menu_exit_from_bottom_left.xml b/ui/android/java/res/anim/menu_exit_from_bottom_left.xml
similarity index 100%
rename from components/browser_ui/widget/android/java/res/anim/menu_exit_from_bottom_left.xml
rename to ui/android/java/res/anim/menu_exit_from_bottom_left.xml
diff --git a/components/browser_ui/widget/android/java/res/anim/menu_exit_from_top_left.xml b/ui/android/java/res/anim/menu_exit_from_top_left.xml
similarity index 100%
rename from components/browser_ui/widget/android/java/res/anim/menu_exit_from_top_left.xml
rename to ui/android/java/res/anim/menu_exit_from_top_left.xml
diff --git a/ui/android/java/res/values-ldrtl/values.xml b/ui/android/java/res/values-ldrtl/values.xml
index 913ceb9..6e02d750 100644
--- a/ui/android/java/res/values-ldrtl/values.xml
+++ b/ui/android/java/res/values-ldrtl/values.xml
@@ -9,4 +9,8 @@
     <!--  Expand more  -->
     <integer name="expand_more_horizontal_rotation_degree"
         tools:ignore="UnusedResources">90</integer>
+
+    <!-- Menu Animation Constants -->
+    <item type="fraction" format="fraction" name="menu_animation_pivot_x">5%</item>
+    <item type="fraction" format="fraction" name="menu_start_animation_pivot_x">95%</item>
 </resources>
diff --git a/ui/android/java/res/values-sw600dp/dimens.xml b/ui/android/java/res/values-sw600dp/dimens.xml
new file mode 100644
index 0000000..26cc88c
--- /dev/null
+++ b/ui/android/java/res/values-sw600dp/dimens.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2019 The Chromium Authors
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<resources>
+      <!-- Menu Dimensions -->
+      <!-- Necessary to align the menu icon with the actual button. -->
+      <dimen name="menu_negative_software_vertical_offset">6dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/ui/android/java/res/values/dimens.xml b/ui/android/java/res/values/dimens.xml
index afa55dc..7746c7d 100644
--- a/ui/android/java/res/values/dimens.xml
+++ b/ui/android/java/res/values/dimens.xml
@@ -40,6 +40,9 @@
     <dimen name="menu_footer_chip_icon_size">16dp</dimen>
     <dimen name="menu_footer_chip_vertical_inset">8dp</dimen>
 
+    <!-- Custom menu-->
+    <dimen name="menu_negative_software_vertical_offset">0dp</dimen>
+
     <!-- Dropdown default measures -->
     <dimen name="dropdown_item_height">50dp</dimen>
     <dimen name="dropdown_item_divider_height">1px</dimen>
diff --git a/ui/android/java/res/values/styles.xml b/ui/android/java/res/values/styles.xml
new file mode 100644
index 0000000..37042d7
--- /dev/null
+++ b/ui/android/java/res/values/styles.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2022 The Chromium Authors
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- AnchoredPopupAnimationStyle -->
+    <style name="AnchoredPopupAnimEndTop">
+        <item name="android:windowEnterAnimation">@anim/menu_enter</item>
+        <item name="android:windowExitAnimation">@anim/menu_exit</item>
+    </style>
+    <style name="AnchoredPopupAnimEndBottom">
+        <item name="android:windowEnterAnimation">@anim/menu_enter_from_bottom</item>
+        <item name="android:windowExitAnimation">@anim/menu_exit_from_bottom</item>
+    </style>
+    <style name="AnchoredPopupAnimStartTop">
+        <item name="android:windowEnterAnimation">@anim/menu_enter_from_top_left</item>
+        <item name="android:windowExitAnimation">@anim/menu_exit_from_top_left</item>
+    </style>
+    <style name="AnchoredPopupAnimStartBottom">
+        <item name="android:windowEnterAnimation">@anim/menu_enter_from_bottom_left</item>
+        <item name="android:windowExitAnimation">@anim/menu_exit_from_bottom_left</item>
+    </style>
+</resources>
diff --git a/ui/android/java/res/values/values.xml b/ui/android/java/res/values/values.xml
index e86eec9..6de551e 100644
--- a/ui/android/java/res/values/values.xml
+++ b/ui/android/java/res/values/values.xml
@@ -16,4 +16,8 @@
     <!-- Drag shadow resize ratio -->
     <item name="drag_shadow_resize_ratio" format="float" type="dimen">0.6</item>
     <item name="drag_shadow_max_size_to_window_ratio" format="float" type="dimen">0.35</item>
+
+    <!-- Menu Animation Constants -->
+    <item type="fraction" format="fraction" name="menu_animation_pivot_x">95%</item>
+    <item type="fraction" format="fraction" name="menu_start_animation_pivot_x">5%</item>
 </resources>
diff --git a/ui/android/java/src/org/chromium/ui/widget/AnchoredPopupWindow.java b/ui/android/java/src/org/chromium/ui/widget/AnchoredPopupWindow.java
index a2d5e54..ff7a961 100644
--- a/ui/android/java/src/org/chromium/ui/widget/AnchoredPopupWindow.java
+++ b/ui/android/java/src/org/chromium/ui/widget/AnchoredPopupWindow.java
@@ -22,10 +22,12 @@
 import android.widget.PopupWindow.OnDismissListener;
 
 import androidx.annotation.IntDef;
+import androidx.annotation.StyleRes;
 import androidx.annotation.VisibleForTesting;
 
 import org.chromium.base.ObserverList;
 import org.chromium.base.metrics.RecordUserAction;
+import org.chromium.ui.R;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -186,6 +188,9 @@
     private boolean mUpdateOrientationOnChange;
     private boolean mSmartAnchorWithMaxWidth;
 
+    private @StyleRes int mAnimationStyleId;
+    private boolean mAnimateFromAnchor;
+
     /**
      * Constructs an {@link AnchoredPopupWindow} instance.
      * @param context  Context to draw resources from.
@@ -313,15 +318,27 @@
     }
 
     /**
-     * Sets the animation style for the popup.  This should be called before the popup is shown.
+     * Sets the animation style for the popup. This should be called before the popup is shown.
+     * Setting this style will take precedence over {@link #setAnimateFromAnchor(boolean)}.
      * @param animationStyleId The id of the animation style.
      */
     public void setAnimationStyle(int animationStyleId) {
+        mAnimationStyleId = animationStyleId;
         mPopupWindow.setAnimationStyle(animationStyleId);
     }
 
     /**
-     * If set to true, orientation will be updated everytime that the {@link OnRectChanged} is
+     * Set whether the popup should enter from / exit to the anchor point. This should be
+     * called before the popup is shown. If an animation style is specified by
+     * {@link #setAnimationStyle(int)}, this method will have no effect.
+     * @param animateFromAnchor Whether the popup should animator from anchor point.
+     */
+    public void setAnimateFromAnchor(boolean animateFromAnchor) {
+        mAnimateFromAnchor = animateFromAnchor;
+    }
+
+    /**
+     * If set to true, orientation will be updated every time that the {@link OnRectChanged} is
      * called.
      */
     public void setUpdateOrientationOnChange(boolean updateOrientationOnChange) {
@@ -728,7 +745,35 @@
         return value;
     }
 
-    private void showPopupWindow() {
+    /**
+     * Calculate the style Id to use when showing the popup window,
+     * when {@link #setAnimateFromAnchor(true)}.
+     * @param isPositionBelow Whether the popup is positioned below the anchor rect.
+     * @param isPositionToLeft Whether the popup is positioned below the anchor rect.
+     * @return The style resource Id to use for {@link PopupWindow#setAnimationStyle}
+     */
+    @VisibleForTesting
+    static @StyleRes int calculateAnimationStyle(
+            boolean isPositionBelow, boolean isPositionToLeft) {
+        if (isPositionToLeft) {
+            return isPositionBelow
+                    ? R.style.AnchoredPopupAnimEndTop // Left + below -> enter top right (end)
+                    : R.style.AnchoredPopupAnimEndBottom; // Left + above -> enter bottom right
+                                                          // (end)
+        }
+        return isPositionBelow
+                ? R.style.AnchoredPopupAnimStartTop // Right & below -> enter top left (start)
+                : R.style.AnchoredPopupAnimStartBottom; // Right & above -> enter bottom left
+                                                        // (start)
+    }
+
+    @VisibleForTesting
+    void showPopupWindow() {
+        if (mAnimateFromAnchor && mAnimationStyleId == 0) {
+            int animationStyle = calculateAnimationStyle(
+                    mPopupSpec.isPositionBelow, mPopupSpec.isPositionToLeft);
+            mPopupWindow.setAnimationStyle(animationStyle);
+        }
         try {
             mPopupWindow.showAtLocation(mRootView, Gravity.TOP | Gravity.START,
                     mPopupSpec.popupRect.left, mPopupSpec.popupRect.top);
diff --git a/ui/android/junit/src/org/chromium/ui/widget/AnchoredPopupWindowTest.java b/ui/android/junit/src/org/chromium/ui/widget/AnchoredPopupWindowTest.java
index 7ac05767..e038de1 100644
--- a/ui/android/junit/src/org/chromium/ui/widget/AnchoredPopupWindowTest.java
+++ b/ui/android/junit/src/org/chromium/ui/widget/AnchoredPopupWindowTest.java
@@ -7,11 +7,20 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import android.app.Activity;
 import android.graphics.Rect;
+import android.view.View;
 import android.widget.FrameLayout;
+import android.widget.PopupWindow;
 
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -21,6 +30,7 @@
 import org.robolectric.shadows.ShadowView;
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.ui.R;
 import org.chromium.ui.widget.AnchoredPopupWindow.HorizontalOrientation;
 import org.chromium.ui.widget.AnchoredPopupWindow.PopupSpec;
 import org.chromium.ui.widget.AnchoredPopupWindow.VerticalOrientation;
@@ -53,6 +63,7 @@
     private boolean mSmartAnchorWithMaxWidth;
 
     private FrameLayout mContentView;
+    private Activity mActivity;
 
     @Before
     public void setUp() {
@@ -62,14 +73,21 @@
         mPopupHeight = 300;
         mWindowRect = new Rect(0, 0, mRootWidth, mRootHeight);
 
-        final Activity activity = Robolectric.buildActivity(Activity.class).get();
-        mContentView = new FrameLayout(activity);
+        mActivity = Robolectric.buildActivity(Activity.class).get();
+
+        mContentView = new FrameLayout(mActivity);
         mContentView.setMinimumWidth(mPopupWidth);
         mContentView.setMinimumHeight(mPopupHeight);
 
         setDefaultValueForAnchoredPopup();
     }
 
+    @After
+    public void tearDown() {
+        mActivity.finish();
+        UiWidgetFactory.setInstance(null);
+    }
+
     @Test
     public void testGetPopupPosition_BelowRight() {
         Rect anchorRect = new Rect(10, 10, 20, 20);
@@ -600,6 +618,71 @@
                 /*expectedPopupRect*/ new Rect(350, 800, 500, 1000));
     }
 
+    @Test
+    public void calculateAnimationStyleStartTop() {
+        assertEquals("Position below right -> animate from start top.",
+                R.style.AnchoredPopupAnimStartTop,
+                AnchoredPopupWindow.calculateAnimationStyle(/*isPositionBelow*/ true,
+                        /*isPositionToLeft*/ false));
+    }
+
+    @Test
+    public void calculateAnimationStyleStartBottom() {
+        assertEquals("Position above right -> animate from start bottom.",
+                R.style.AnchoredPopupAnimStartBottom,
+                AnchoredPopupWindow.calculateAnimationStyle(/*isPositionBelow*/ false,
+                        /*isPositionToLeft*/ false));
+    }
+
+    @Test
+    public void calculateAnimationStyleEndTop() {
+        assertEquals("Position below left -> animate from end top.",
+                R.style.AnchoredPopupAnimEndTop,
+                AnchoredPopupWindow.calculateAnimationStyle(/*isPositionBelow*/ true,
+                        /*isPositionToLeft*/ true));
+    }
+
+    @Test
+    public void calculateAnimationStyleEndBottom() {
+        assertEquals("Position above left -> animate from end bottom.",
+                R.style.AnchoredPopupAnimEndBottom,
+                AnchoredPopupWindow.calculateAnimationStyle(/*isPositionBelow*/ false,
+                        /*isPositionToLeft*/ true));
+    }
+
+    @Test
+    public void setAnimateFromAnchor() {
+        // Set up for test case, so we have a mock popup window.
+        UiWidgetFactory mockFactory = mock(UiWidgetFactory.class);
+        UiWidgetFactory.setInstance(mockFactory);
+
+        PopupWindow mockPopup = mock(PopupWindow.class);
+        doReturn(mockPopup).when(mockFactory).createPopupWindow(any());
+
+        AnchoredPopupWindow popupWindow = createAnchorPopupWindow();
+        popupWindow.setAnimateFromAnchor(true);
+        popupWindow.showPopupWindow();
+        verify(mockPopup).setAnimationStyle(anyInt());
+    }
+
+    @Test
+    public void setAnimationStyleNotOverrideByAnimateFromAnchor() {
+        // Set up for test case, so we have a mock popup window.
+        UiWidgetFactory mockFactory = mock(UiWidgetFactory.class);
+        UiWidgetFactory.setInstance(mockFactory);
+        PopupWindow mockPopup = mock(PopupWindow.class);
+        doReturn(mockPopup).when(mockFactory).createPopupWindow(any());
+
+        AnchoredPopupWindow popupWindow = createAnchorPopupWindow();
+        popupWindow.setAnimationStyle(R.style.Animation_AppCompat_Dialog);
+        verify(mockPopup).setAnimationStyle(R.style.Animation_AppCompat_Dialog);
+
+        popupWindow.setAnimateFromAnchor(true);
+        popupWindow.showPopupWindow();
+        // setAnimationStyle should only called once, since #setAnimateFromAnchor is no-op.
+        verify(mockPopup, times(1)).setAnimationStyle(anyInt());
+    }
+
     private void setDefaultValueForAnchoredPopup() {
         mPaddingX = 0;
         mPaddingY = 0;
@@ -632,4 +715,10 @@
                 String.format("PopupRect does not match expected Rect. Test case:<%s>", testCase),
                 expectedRect, popupRect);
     }
+
+    private AnchoredPopupWindow createAnchorPopupWindow() {
+        View rootView = mock(View.class);
+        RectProvider provider = new RectProvider(new Rect(0, 0, 0, 0));
+        return new AnchoredPopupWindow(mActivity, rootView, null, mContentView, provider);
+    }
 }
diff --git a/ui/base/clipboard/clipboard_mac.mm b/ui/base/clipboard/clipboard_mac.mm
index e554874..532fdfb 100644
--- a/ui/base/clipboard/clipboard_mac.mm
+++ b/ui/base/clipboard/clipboard_mac.mm
@@ -135,8 +135,8 @@
   // Safari only places RTF on the pasteboard, never HTML. We can convert RTF
   // to HTML, so the presence of either indicates success when looking for HTML.
   if (format == ClipboardFormatType::HtmlType()) {
-    return [types containsObject:NSHTMLPboardType] ||
-           [types containsObject:NSRTFPboardType];
+    return [types containsObject:NSPasteboardTypeHTML] ||
+           [types containsObject:NSPasteboardTypeRTF];
   }
   // Chrome can retrieve an image from the clipboard as either a bitmap or PNG.
   if (format == ClipboardFormatType::PngType() ||
@@ -266,14 +266,15 @@
 
   NSPasteboard* pb = GetPasteboard();
   NSArray* supportedTypes =
-      @[ NSHTMLPboardType, NSRTFPboardType, NSPasteboardTypeString ];
+      @[ NSPasteboardTypeHTML, NSPasteboardTypeRTF, NSPasteboardTypeString ];
   NSString* bestType = [pb availableTypeFromArray:supportedTypes];
   if (bestType) {
     NSString* contents;
-    if ([bestType isEqualToString:NSRTFPboardType])
+    if ([bestType isEqualToString:NSPasteboardTypeRTF]) {
       contents = ClipboardUtil::GetHTMLFromRTFOnPasteboard(pb);
-    else
+    } else {
       contents = [pb stringForType:bestType];
+    }
     *markup = base::SysNSStringToUTF16(contents);
   }
 
@@ -417,7 +418,7 @@
   NSString* html_fragment = base::SysUTF8ToNSString(html_fragment_str);
 
   // TODO(avi): url_data?
-  [GetPasteboard() setString:html_fragment forType:NSHTMLPboardType];
+  [GetPasteboard() setString:html_fragment forType:NSPasteboardTypeHTML];
 }
 
 void ClipboardMac::WriteSvg(const char* markup_data, size_t markup_len) {
diff --git a/ui/base/dragdrop/os_exchange_data_provider_mac.mm b/ui/base/dragdrop/os_exchange_data_provider_mac.mm
index 7f583d5..b5b03f0 100644
--- a/ui/base/dragdrop/os_exchange_data_provider_mac.mm
+++ b/ui/base/dragdrop/os_exchange_data_provider_mac.mm
@@ -364,10 +364,9 @@
 // static
 NSArray* OSExchangeDataProviderMac::SupportedPasteboardTypes() {
   return @[
-    kUTTypeChromiumWebCustomData, kUTTypeWebKitWebURLsWithTitles,
-    NSURLPboardType, NSFilenamesPboardType, kUTTypeChromiumInitiatedDrag,
-    NSStringPboardType, NSHTMLPboardType, NSRTFPboardType,
-    NSFilenamesPboardType, kUTTypeChromiumWebCustomData, NSPasteboardTypeString
+    kUTTypeChromiumInitiatedDrag, kUTTypeChromiumWebCustomData,
+    kUTTypeWebKitWebURLsWithTitles, NSFilenamesPboardType, NSPasteboardTypeHTML,
+    NSPasteboardTypeRTF, NSPasteboardTypeString, NSPasteboardTypeURL
   ];
 }
 
diff --git a/ui/compositor/test/in_process_context_factory.cc b/ui/compositor/test/in_process_context_factory.cc
index 36483ab..77501b9 100644
--- a/ui/compositor/test/in_process_context_factory.cc
+++ b/ui/compositor/test/in_process_context_factory.cc
@@ -55,10 +55,6 @@
 #include "ui/accelerated_widget_mac/ca_transaction_observer.h"
 #endif
 
-#if !defined(GPU_SURFACE_HANDLE_IS_ACCELERATED_WINDOW)
-#include "gpu/ipc/common/gpu_surface_tracker.h"
-#endif
-
 namespace ui {
 namespace {
 
@@ -318,10 +314,6 @@
   PerCompositorData* data = it->second.get();
   frame_sink_manager_->UnregisterBeginFrameSource(data->begin_frame_source());
   DCHECK(data);
-#if !defined(GPU_SURFACE_HANDLE_IS_ACCELERATED_WINDOW)
-  if (data->surface_handle())
-    gpu::GpuSurfaceTracker::Get()->RemoveSurface(data->surface_handle());
-#endif
   per_compositor_data_.erase(it);
 }
 
@@ -399,20 +391,6 @@
   } else {
 #if defined(GPU_SURFACE_HANDLE_IS_ACCELERATED_WINDOW)
     data->SetSurfaceHandle(widget);
-#else
-    gpu::GpuSurfaceTracker* tracker = gpu::GpuSurfaceTracker::Get();
-    data->SetSurfaceHandle(tracker->AddSurfaceForNativeWidget(
-        gpu::GpuSurfaceTracker::SurfaceRecord(
-            widget
-#if BUILDFLAG(IS_ANDROID)
-            // We have to provide a surface too, but we don't have one.  For
-            // now, we don't proide it, since nobody should ask anyway.
-            // If we ever provide a valid surface here, then GpuSurfaceTracker
-            // can be more strict about enforcing it.
-            ,
-            nullptr, false /* can_be_used_with_surface_control */
-#endif
-            )));
 #endif
   }
 
diff --git a/ui/gfx/buffer_format_util.h b/ui/gfx/buffer_format_util.h
index 01f17f55..35f4592f 100644
--- a/ui/gfx/buffer_format_util.h
+++ b/ui/gfx/buffer_format_util.h
@@ -29,6 +29,11 @@
 GFX_EXPORT size_t SubsamplingFactorForBufferFormat(BufferFormat format,
                                                    size_t plane);
 
+// Returns the alignment requirement to store a row of the given zero-indexed
+// |plane| of |format|.
+GFX_EXPORT size_t RowByteAlignmentForBufferFormat(BufferFormat format,
+                                                  size_t plane);
+
 // Returns the number of bytes used to store a row of the given zero-indexed
 // |plane| of |format|.
 GFX_EXPORT size_t RowSizeForBufferFormat(size_t width,
diff --git a/ui/gl/android/scoped_a_native_window.cc b/ui/gl/android/scoped_a_native_window.cc
index 8e93462..a73ea62 100644
--- a/ui/gl/android/scoped_a_native_window.cc
+++ b/ui/gl/android/scoped_a_native_window.cc
@@ -16,6 +16,9 @@
   return ScopedANativeWindow(a_native_window);
 }
 
+ScopedANativeWindow::ScopedANativeWindow() = default;
+ScopedANativeWindow::ScopedANativeWindow(std::nullptr_t) {}
+
 ScopedANativeWindow::ScopedANativeWindow(const ScopedJavaSurface& surface) {
   if (!surface.j_surface()) {
     return;
diff --git a/ui/gl/android/scoped_a_native_window.h b/ui/gl/android/scoped_a_native_window.h
index 339f384..a5e5785 100644
--- a/ui/gl/android/scoped_a_native_window.h
+++ b/ui/gl/android/scoped_a_native_window.h
@@ -5,6 +5,8 @@
 #ifndef UI_GL_ANDROID_SCOPED_A_NATIVE_WINDOW_H_
 #define UI_GL_ANDROID_SCOPED_A_NATIVE_WINDOW_H_
 
+#include <cstddef>
+
 #include "ui/gl/gl_export.h"
 
 struct ANativeWindow;
@@ -16,6 +18,8 @@
 class GL_EXPORT ScopedANativeWindow {
  public:
   static ScopedANativeWindow Wrap(ANativeWindow* a_native_window);
+  ScopedANativeWindow();
+  ScopedANativeWindow(std::nullptr_t);
   explicit ScopedANativeWindow(const ScopedJavaSurface& surface);
   ~ScopedANativeWindow();
 
diff --git a/ui/gl/android/scoped_java_surface.cc b/ui/gl/android/scoped_java_surface.cc
index 8ac2f58c..d3bf54a 100644
--- a/ui/gl/android/scoped_java_surface.cc
+++ b/ui/gl/android/scoped_java_surface.cc
@@ -14,14 +14,15 @@
 
 namespace gl {
 
-ScopedJavaSurface::ScopedJavaSurface() {
-}
+ScopedJavaSurface::ScopedJavaSurface() = default;
+ScopedJavaSurface::ScopedJavaSurface(std::nullptr_t) {}
 
 ScopedJavaSurface::ScopedJavaSurface(
-    const base::android::JavaRef<jobject>& surface) {
+    const base::android::JavaRef<jobject>& surface,
+    bool auto_release)
+    : auto_release_(auto_release), j_surface_(surface) {
   JNIEnv* env = base::android::AttachCurrentThread();
   DCHECK(env->IsInstanceOf(surface.obj(), android_view_Surface_clazz(env)));
-  j_surface_.Reset(surface);
 }
 
 ScopedJavaSurface::ScopedJavaSurface(const SurfaceTexture* surface_texture) {
@@ -45,6 +46,10 @@
   ReleaseSurfaceIfNeeded();
 }
 
+ScopedJavaSurface ScopedJavaSurface::CopyRetainOwnership() const {
+  return ScopedJavaSurface(j_surface_, /*auto_release=*/false);
+}
+
 void ScopedJavaSurface::ReleaseSurfaceIfNeeded() {
   if (auto_release_ && !j_surface_.is_null()) {
     JNIEnv* env = base::android::AttachCurrentThread();
@@ -56,7 +61,6 @@
   ReleaseSurfaceIfNeeded();
   j_surface_ = std::move(other.j_surface_);
   auto_release_ = other.auto_release_;
-  is_protected_ = other.is_protected_;
 }
 
 bool ScopedJavaSurface::IsEmpty() const {
@@ -68,13 +72,4 @@
   return !IsEmpty() && JNI_Surface::Java_Surface_isValidZ(env, j_surface_);
 }
 
-// static
-ScopedJavaSurface ScopedJavaSurface::AcquireExternalSurface(
-    const base::android::JavaRef<jobject>& surface) {
-  ScopedJavaSurface scoped_surface(surface);
-  scoped_surface.auto_release_ = false;
-  scoped_surface.is_protected_ = true;
-  return scoped_surface;
-}
-
 }  // namespace gl
diff --git a/ui/gl/android/scoped_java_surface.h b/ui/gl/android/scoped_java_surface.h
index b04b0a0..d11c73b5 100644
--- a/ui/gl/android/scoped_java_surface.h
+++ b/ui/gl/android/scoped_java_surface.h
@@ -6,6 +6,7 @@
 #define UI_GL_ANDROID_SCOPED_JAVA_SURFACE_H_
 
 #include <jni.h>
+#include <cstddef>
 
 #include "base/android/scoped_java_ref.h"
 #include "ui/gl/gl_export.h"
@@ -20,9 +21,11 @@
 class GL_EXPORT ScopedJavaSurface {
  public:
   ScopedJavaSurface();
+  ScopedJavaSurface(std::nullptr_t);
 
   // Wraps an existing Java Surface object in a ScopedJavaSurface.
-  explicit ScopedJavaSurface(const base::android::JavaRef<jobject>& surface);
+  ScopedJavaSurface(const base::android::JavaRef<jobject>& surface,
+                    bool auto_release);
 
   // Creates a Java Surface from a SurfaceTexture and wraps it in a
   // ScopedJavaSurface.
@@ -33,26 +36,21 @@
   ScopedJavaSurface(ScopedJavaSurface&& rvalue);
   ScopedJavaSurface& operator=(ScopedJavaSurface&& rhs);
 
-  // Creates a ScopedJavaSurface that is owned externally, i.e.,
-  // someone else is responsible to call Surface.release().
-  static ScopedJavaSurface AcquireExternalSurface(
-      const base::android::JavaRef<jobject>& surface);
-
   ScopedJavaSurface(const ScopedJavaSurface&) = delete;
   ScopedJavaSurface& operator=(const ScopedJavaSurface&) = delete;
 
   ~ScopedJavaSurface();
 
+  // Make a copy that does not retain ownership. Client is responsible for not
+  // using the copy after this is destroyed.
+  ScopedJavaSurface CopyRetainOwnership() const;
+
   // Checks whether the surface is an empty one.
   bool IsEmpty() const;
 
   // Checks whether this object references a valid surface.
   bool IsValid() const;
 
-  // Checks whether the surface is hardware protected so that no readback is
-  // possible.
-  bool is_protected() const { return is_protected_; }
-
   const base::android::JavaRef<jobject>& j_surface() const {
     return j_surface_;
   }
@@ -63,7 +61,6 @@
   void ReleaseSurfaceIfNeeded();
 
   bool auto_release_ = true;
-  bool is_protected_ = false;
 
   base::android::ScopedJavaGlobalRef<jobject> j_surface_;
 };
diff --git a/ui/gl/gl_image_egl.h b/ui/gl/gl_image_egl.h
index c5e6bcbc..4e4e1df 100644
--- a/ui/gl/gl_image_egl.h
+++ b/ui/gl/gl_image_egl.h
@@ -17,8 +17,6 @@
 // Abstract base class for EGL-based images.
 class GL_EXPORT GLImageEGL : public GLImage {
  public:
-  explicit GLImageEGL(const gfx::Size& size);
-
   GLImageEGL(const GLImageEGL&) = delete;
   GLImageEGL& operator=(const GLImageEGL&) = delete;
 
@@ -30,6 +28,7 @@
   void ReleaseTexImage(unsigned target) override {}
 
  protected:
+  explicit GLImageEGL(const gfx::Size& size);
   ~GLImageEGL() override;
 
   // Same semantic as specified for eglCreateImageKHR. There two main usages:
diff --git a/ui/gl/gl_image_native_pixmap.cc b/ui/gl/gl_image_native_pixmap.cc
index d492ad8f..68d0c7c 100644
--- a/ui/gl/gl_image_native_pixmap.cc
+++ b/ui/gl/gl_image_native_pixmap.cc
@@ -126,6 +126,39 @@
 
 }  // namespace
 
+scoped_refptr<GLImageNativePixmap> GLImageNativePixmap::Create(
+    const gfx::Size& size,
+    gfx::BufferFormat format,
+    scoped_refptr<gfx::NativePixmap> pixmap) {
+  return CreateForPlane(size, format, gfx::BufferPlane::DEFAULT,
+                        std::move(pixmap));
+}
+
+scoped_refptr<GLImageNativePixmap> GLImageNativePixmap::CreateForPlane(
+    const gfx::Size& size,
+    gfx::BufferFormat format,
+    gfx::BufferPlane plane,
+    scoped_refptr<gfx::NativePixmap> pixmap) {
+  auto image =
+      base::WrapRefCounted(new GLImageNativePixmap(size, format, plane));
+  if (!image->Initialize(std::move(pixmap))) {
+    return nullptr;
+  }
+  return image;
+}
+
+scoped_refptr<GLImageNativePixmap> GLImageNativePixmap::CreateFromTexture(
+    const gfx::Size& size,
+    gfx::BufferFormat format,
+    uint32_t texture_id) {
+  auto image = base::WrapRefCounted(
+      new GLImageNativePixmap(size, format, gfx::BufferPlane::DEFAULT));
+  if (!image->InitializeFromTexture(texture_id)) {
+    return nullptr;
+  }
+  return image;
+}
+
 GLImageNativePixmap::GLImageNativePixmap(const gfx::Size& size,
                                          gfx::BufferFormat format,
                                          gfx::BufferPlane plane)
@@ -238,7 +271,6 @@
                                 &attrs[0])) {
       return false;
     }
-    did_initialize_ = true;
   }
 
   pixmap_ = pixmap;
@@ -265,7 +297,6 @@
                               nullptr)) {
     return false;
   }
-  did_initialize_ = true;
   return true;
 }
 
@@ -361,12 +392,10 @@
 }
 
 bool GLImageNativePixmap::BindTexImage(unsigned target) {
-  DCHECK(did_initialize_);
   return GLImageEGL::BindTexImage(target);
 }
 
 bool GLImageNativePixmap::CopyTexImage(unsigned target) {
-  DCHECK(did_initialize_);
   if (egl_image_ != EGL_NO_IMAGE_KHR)
     return false;
 
diff --git a/ui/gl/gl_image_native_pixmap.h b/ui/gl/gl_image_native_pixmap.h
index 59bd313..c54afe9 100644
--- a/ui/gl/gl_image_native_pixmap.h
+++ b/ui/gl/gl_image_native_pixmap.h
@@ -17,14 +17,24 @@
 
 class GL_EXPORT GLImageNativePixmap : public gl::GLImageEGL {
  public:
-  GLImageNativePixmap(const gfx::Size& size,
-                      gfx::BufferFormat format,
-                      gfx::BufferPlane plane = gfx::BufferPlane::DEFAULT);
-
   // Create an EGLImage from a given NativePixmap.
-  bool Initialize(scoped_refptr<gfx::NativePixmap> pixmap);
+  static scoped_refptr<GLImageNativePixmap> Create(
+      const gfx::Size& size,
+      gfx::BufferFormat format,
+      scoped_refptr<gfx::NativePixmap> pixmap);
+
+  // Create an EGLImage from a given NativePixmap and plane.
+  static scoped_refptr<GLImageNativePixmap> CreateForPlane(
+      const gfx::Size& size,
+      gfx::BufferFormat format,
+      gfx::BufferPlane plane,
+      scoped_refptr<gfx::NativePixmap> pixmap);
   // Create an EGLImage from a given GL texture.
-  bool InitializeFromTexture(uint32_t texture_id);
+  static scoped_refptr<GLImageNativePixmap> CreateFromTexture(
+      const gfx::Size& size,
+      gfx::BufferFormat format,
+      uint32_t texture_id);
+
   // Export the wrapped EGLImage to dmabuf fds.
   gfx::NativePixmapHandle ExportHandle();
 
@@ -45,11 +55,18 @@
   ~GLImageNativePixmap() override;
 
  private:
+  GLImageNativePixmap(const gfx::Size& size,
+                      gfx::BufferFormat format,
+                      gfx::BufferPlane plane);
+  // Create an EGLImage from a given NativePixmap.
+  bool Initialize(scoped_refptr<gfx::NativePixmap> pixmap);
+  // Create an EGLImage from a given GL texture.
+  bool InitializeFromTexture(uint32_t texture_id);
+
   gfx::BufferFormat format_;
   scoped_refptr<gfx::NativePixmap> pixmap_;
   gfx::BufferPlane plane_;
   bool has_image_dma_buf_export_;
-  bool did_initialize_;
 };
 
 }  // namespace gl
diff --git a/ui/gl/gl_image_native_pixmap_unittest.cc b/ui/gl/gl_image_native_pixmap_unittest.cc
index dc65e52..aafe748 100644
--- a/ui/gl/gl_image_native_pixmap_unittest.cc
+++ b/ui/gl/gl_image_native_pixmap_unittest.cc
@@ -59,8 +59,9 @@
     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.width(), size.height(), 0,
                  GL_RGBA, GL_UNSIGNED_BYTE, pixels.get());
 
-    auto image = base::MakeRefCounted<gl::GLImageNativePixmap>(size, format);
-    EXPECT_TRUE(image->InitializeFromTexture(texture_id));
+    auto image =
+        gl::GLImageNativePixmap::CreateFromTexture(size, format, texture_id);
+    EXPECT_TRUE(image);
 
     glDeleteTextures(1, &texture_id);
     return image;
diff --git a/ui/ozone/common/native_pixmap_egl_binding.cc b/ui/ozone/common/native_pixmap_egl_binding.cc
index efb2090..d4c21a6 100644
--- a/ui/ozone/common/native_pixmap_egl_binding.cc
+++ b/ui/ozone/common/native_pixmap_egl_binding.cc
@@ -22,15 +22,16 @@
     const gfx::ColorSpace& color_space,
     GLenum target,
     GLuint texture_id) {
-  auto gl_image = base::MakeRefCounted<gl::GLImageNativePixmap>(
-      plane_size, plane_format, plane);
-  if (color_space.IsValid())
-    gl_image->SetColorSpace(color_space);
-  if (!gl_image->Initialize(std::move(pixmap))) {
+  auto gl_image = gl::GLImageNativePixmap::CreateForPlane(
+      plane_size, plane_format, plane, std::move(pixmap));
+  if (!gl_image) {
     LOG(ERROR) << "Unable to initialize GL image from pixmap";
     return nullptr;
   }
 
+  if (color_space.IsValid())
+    gl_image->SetColorSpace(color_space);
+
   auto binding = std::make_unique<NativePixmapEGLBinding>();
   if (!binding->BindTexture(std::move(gl_image), target, texture_id)) {
     return nullptr;
diff --git a/ui/ozone/demo/skia/skia_surfaceless_gl_renderer.cc b/ui/ozone/demo/skia/skia_surfaceless_gl_renderer.cc
index 8e1b4430b..6258cd0 100644
--- a/ui/ozone/demo/skia/skia_surfaceless_gl_renderer.cc
+++ b/ui/ozone/demo/skia/skia_surfaceless_gl_renderer.cc
@@ -122,12 +122,11 @@
           ->GetSurfaceFactoryOzone()
           ->CreateNativePixmap(widget, nullptr, size, format,
                                gfx::BufferUsage::SCANOUT);
-  auto image = base::MakeRefCounted<gl::GLImageNativePixmap>(size, format);
-  if (!image->Initialize(std::move(pixmap))) {
+  image_ = gl::GLImageNativePixmap::Create(size, format, std::move(pixmap));
+  if (!image_) {
     LOG(ERROR) << "Failed to create GLImage";
     return false;
   }
-  image_ = image;
 
   glBindTexture(GL_TEXTURE_2D, gl_tex_);
   image_->BindTexImage(GL_TEXTURE_2D);
diff --git a/ui/ozone/demo/surfaceless_gl_renderer.cc b/ui/ozone/demo/surfaceless_gl_renderer.cc
index f7baedf..d564ef0 100644
--- a/ui/ozone/demo/surfaceless_gl_renderer.cc
+++ b/ui/ozone/demo/surfaceless_gl_renderer.cc
@@ -93,12 +93,11 @@
           ->GetSurfaceFactoryOzone()
           ->CreateNativePixmap(widget, nullptr, size, format,
                                gfx::BufferUsage::SCANOUT);
-  auto image = base::MakeRefCounted<gl::GLImageNativePixmap>(size, format);
-  if (!image->Initialize(std::move(pixmap))) {
+  image_ = gl::GLImageNativePixmap::Create(size, format, std::move(pixmap));
+  if (!image_) {
     LOG(ERROR) << "Failed to create GLImage";
     return false;
   }
-  image_ = image;
 
   glBindFramebufferEXT(GL_FRAMEBUFFER, gl_fb_);
   glBindTexture(GL_TEXTURE_2D, gl_tex_);
diff --git a/ui/ozone/gl/gl_image_ozone_native_pixmap_unittest.cc b/ui/ozone/gl/gl_image_ozone_native_pixmap_unittest.cc
index 061e5507..425ea8fd 100644
--- a/ui/ozone/gl/gl_image_ozone_native_pixmap_unittest.cc
+++ b/ui/ozone/gl/gl_image_ozone_native_pixmap_unittest.cc
@@ -60,8 +60,9 @@
       client_pixmap->Unmap();
     }
 
-    auto image = base::MakeRefCounted<gl::GLImageNativePixmap>(size, format);
-    EXPECT_TRUE(image->Initialize(pixmap.get()));
+    auto image =
+        gl::GLImageNativePixmap::Create(size, format, std::move(pixmap));
+    EXPECT_TRUE(image);
     return image;
   }
 
diff --git a/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc b/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc
index 9dfb392f..38934c5e 100644
--- a/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc
@@ -128,8 +128,10 @@
   void OnDragDrop(std::unique_ptr<OSExchangeData> data,
                   int modifiers) override {
     MockOnDragDrop();
-    on_drop_closure_.Run();
-    on_drop_closure_.Reset();
+    if (on_drop_closure_) {
+      on_drop_closure_.Run();
+      on_drop_closure_.Reset();
+    }
   }
 
   int OnDragMotion(const gfx::PointF& point,
@@ -501,9 +503,21 @@
   SendMotionEvent(top_left);
 }
 
+// Emulating an incoming DnD session, ensures that data drag controller
+// fetches all the data for the mime-types offered by the source client.
 TEST_P(WaylandDataDragControllerTest, DropSeveralMimeTypes) {
-  EXPECT_CALL(*drop_handler_, MockOnDragEnter()).Times(1);
   const uint32_t surface_id = window_->root_surface()->get_surface_id();
+
+  // As data for each offered mime-type is asynchronously read (eg: using
+  // wl_display.sync callbacks, etc), a nested run loop is used to ensure
+  // it is reliably done. Furthermore, WmDropHandler::OnDragEnter() is expected
+  // to be called only once the data is fully fetched, so it's used here as
+  // condition to quit the loop and verify the expectations, otherwise some
+  // flakiness may be observed, see https://crbug.com/1395127.
+  base::RunLoop loop;
+  EXPECT_CALL(*drop_handler_, MockOnDragEnter()).WillOnce([&loop]() {
+    loop.Quit();
+  });
   PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
     auto* data_offer =
         server->data_device_manager()->data_device()->CreateAndSendDataOffer();
@@ -522,15 +536,13 @@
         1002, surface->resource(), wl_fixed_from_int(entered_point.x()),
         wl_fixed_from_int(entered_point.y()), data_offer);
   });
+  loop.Run();
+  Mock::VerifyAndClearExpectations(drop_handler_.get());
 
   EXPECT_CALL(*drop_handler_, MockOnDragDrop()).Times(1);
-  base::RunLoop loop;
-  drop_handler_->SetOnDropClosure(loop.QuitClosure());
   PostToServerAndWait([](wl::TestWaylandServerThread* server) {
     server->data_device_manager()->data_device()->OnDrop();
   });
-
-  loop.Run();
   Mock::VerifyAndClearExpectations(drop_handler_.get());
 
   ASSERT_NE(drop_handler_->dropped_data(), nullptr);
diff --git a/ui/strings/ui_strings.grd b/ui/strings/ui_strings.grd
index bc48ab46..2b0181d 100644
--- a/ui/strings/ui_strings.grd
+++ b/ui/strings/ui_strings.grd
@@ -605,7 +605,7 @@
           Emoji &amp;&amp; Symbols
         </message>
       </if>
-      <if expr="chromeos_ash">
+      <if expr="is_chromeos">
         <message name="IDS_APP_SHOW_CLIPBOARD_HISTORY" desc="The context menu item to show the clipboard history menu.">
           Clipboard
         </message>
diff --git a/ui/views/BUILD.gn b/ui/views/BUILD.gn
index c92b020..e683ccc1 100644
--- a/ui/views/BUILD.gn
+++ b/ui/views/BUILD.gn
@@ -596,11 +596,11 @@
   }
 
   if (is_chromeos) {
-    sources += [ "controls/menu/menu_config_chromeos.cc" ]
-    if (!is_chromeos_lacros) {
-      public += [ "controls/views_text_services_context_menu_chromeos.h" ]
-      sources += [ "controls/views_text_services_context_menu_chromeos.cc" ]
-    }
+    public += [ "controls/views_text_services_context_menu_chromeos.h" ]
+    sources += [
+      "controls/menu/menu_config_chromeos.cc",
+      "controls/views_text_services_context_menu_chromeos.cc",
+    ]
   }
 
   if (is_mac) {
diff --git a/ui/views/cocoa/immersive_mode_controller_unittest.mm b/ui/views/cocoa/immersive_mode_controller_unittest.mm
index 51aa430a..9579af9 100644
--- a/ui/views/cocoa/immersive_mode_controller_unittest.mm
+++ b/ui/views/cocoa/immersive_mode_controller_unittest.mm
@@ -236,11 +236,16 @@
   [fullscreen_window.get().contentView addSubview:titlebar_container_view];
   [fullscreen_window orderBack:nil];
 
+  auto immersive_mode_controller = std::make_unique<ImmersiveModeController>(
+      browser(), overlay(), base::DoNothing());
+  base::WeakPtrFactory<ImmersiveModeController> weak_ptr_factory(
+      immersive_mode_controller.get());
+
   // Create a titlebar observer. This is the class under test.
   base::scoped_nsobject<ImmersiveModeTitlebarObserver> titlebar_observer(
       [[ImmersiveModeTitlebarObserver alloc]
-          initWithOverlayWindow:overlay()
-                    overlayView:overlay_view]);
+          initWithController:weak_ptr_factory.GetWeakPtr()
+                 overlayView:overlay_view]);
 
   // Observer the fake fake titlebar container view.
   [titlebar_container_view addObserver:titlebar_observer
diff --git a/ui/views/controls/views_text_services_context_menu_base.cc b/ui/views/controls/views_text_services_context_menu_base.cc
index 2ec8faa..57d33de 100644
--- a/ui/views/controls/views_text_services_context_menu_base.cc
+++ b/ui/views/controls/views_text_services_context_menu_base.cc
@@ -88,7 +88,7 @@
   return command_id == IDS_CONTENT_CONTEXT_EMOJI;
 }
 
-#if !BUILDFLAG(IS_MAC) && !BUILDFLAG(IS_CHROMEOS_ASH)
+#if !BUILDFLAG(IS_MAC) && !BUILDFLAG(IS_CHROMEOS)
 // static
 std::unique_ptr<ViewsTextServicesContextMenu>
 ViewsTextServicesContextMenu::Create(ui::SimpleMenuModel* menu,
diff --git a/ui/views/controls/views_text_services_context_menu_base.h b/ui/views/controls/views_text_services_context_menu_base.h
index 1bd3aa3..7bdea6d 100644
--- a/ui/views/controls/views_text_services_context_menu_base.h
+++ b/ui/views/controls/views_text_services_context_menu_base.h
@@ -35,7 +35,7 @@
   bool SupportsCommand(int command_id) const override;
 
  protected:
-#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS)
   Textfield* client() { return client_; }
   const Textfield* client() const { return client_; }
 #endif
diff --git a/ui/views/controls/views_text_services_context_menu_chromeos.cc b/ui/views/controls/views_text_services_context_menu_chromeos.cc
index 63de8cf..86c09714 100644
--- a/ui/views/controls/views_text_services_context_menu_chromeos.cc
+++ b/ui/views/controls/views_text_services_context_menu_chromeos.cc
@@ -34,10 +34,11 @@
 
   // In unit tests, `impl_factory` may not be set. Use
   // `ViewTextServicesContextMenuBase` in that case.
-  if (impl_factory)
+  if (impl_factory) {
     impl_ = impl_factory.Run(menu, client);
-  else
+  } else {
     impl_ = std::make_unique<ViewsTextServicesContextMenuBase>(menu, client);
+  }
 }
 
 ViewsTextServicesContextMenuChromeos::~ViewsTextServicesContextMenuChromeos() =
@@ -66,7 +67,7 @@
 
 bool ViewsTextServicesContextMenuChromeos::SupportsCommand(
     int command_id) const {
-  return impl_->IsCommandIdEnabled(command_id);
+  return impl_->SupportsCommand(command_id);
 }
 
 // static
diff --git a/ui/views/controls/views_text_services_context_menu_chromeos.h b/ui/views/controls/views_text_services_context_menu_chromeos.h
index 9cdd0de..f583a26 100644
--- a/ui/views/controls/views_text_services_context_menu_chromeos.h
+++ b/ui/views/controls/views_text_services_context_menu_chromeos.h
@@ -12,8 +12,9 @@
 
 namespace views {
 
-// This class is used to add and handle text service items in the text context
-// menu under the CrOS environment.
+// This class is used to add and handle text service items in ChromeOS native UI
+// textfield context menus. The implementation is specific to the platform (Ash
+// or Lacros) where the textfield lives.
 class VIEWS_EXPORT ViewsTextServicesContextMenuChromeos
     : public ViewsTextServicesContextMenu {
  public:
@@ -40,9 +41,9 @@
   bool SupportsCommand(int command_id) const override;
 
  private:
-  // CrOS functionality must be provided by the embedder, so requests are
-  // forwarded to this concrete object, whose construction can be controlled by
-  // `SetImplFactory()`.
+  // ChromeOS functionality is provided by a platform-specific implementation.
+  // Function calls are forwarded to this instance, whose construction is
+  // controlled by `SetImplFactory()`.
   std::unique_ptr<ViewsTextServicesContextMenu> impl_;
 };
 
diff --git a/ui/webui/resources/cr_components/history_clusters/url_visit.html b/ui/webui/resources/cr_components/history_clusters/url_visit.html
index d552dbd..86e5985 100644
--- a/ui/webui/resources/cr_components/history_clusters/url_visit.html
+++ b/ui/webui/resources/cr_components/history_clusters/url_visit.html
@@ -47,7 +47,7 @@
     margin-inline-end: var(--cluster-padding-horizontal);
     min-width: 0;
     outline: none;
-    padding-inline-end: 2px; /* So focus outline does not intersect text */
+    padding-inline: 2px; /* So focus outline does not intersect text */
   }
 
   :host(:hover) #link-container {
diff --git a/ui/webui/resources/js/metrics_reporter/BUILD.gn b/ui/webui/resources/js/metrics_reporter/BUILD.gn
index 6fcb00b..487a50b 100644
--- a/ui/webui/resources/js/metrics_reporter/BUILD.gn
+++ b/ui/webui/resources/js/metrics_reporter/BUILD.gn
@@ -10,6 +10,7 @@
   sources = [ "metrics_reporter.mojom" ]
   webui_module_path = "chrome://resources/js/metrics_reporter"
   public_deps = [ "//mojo/public/mojom/base" ]
+  use_typescript_sources = true
 }
 
 # Output folder used to hold ts_library() output.
@@ -20,24 +21,24 @@
   root_dir = target_gen_dir
   out_dir = preprocess_folder
   composite = true
-  tsconfig_base = "tsconfig_base.json"
   in_files = [
     "metrics_reporter.ts",
     "browser_proxy.ts",
-    "metrics_reporter.mojom-webui.js",
+    "metrics_reporter.mojom-webui.ts",
   ]
   definitions = [ "//tools/typescript/definitions/chrome_timeticks.d.ts" ]
   deps = [
     "//ui/webui/resources:library",
     "//ui/webui/resources/mojo:library",
   ]
-  extra_deps = [ ":copy_src_and_mojom" ]
+  extra_deps = [
+    ":copy_src",
+    ":mojo_bindings_ts__generator",
+  ]
 }
 
-copy("copy_src_and_mojom") {
-  deps = [ ":mojo_bindings_js__generator" ]
+copy("copy_src") {
   sources = [
-    "$root_gen_dir/mojom-webui/ui/webui/resources/js/metrics_reporter/metrics_reporter.mojom-webui.js",
     "browser_proxy.ts",
     "metrics_reporter.ts",
   ]
diff --git a/ui/webui/resources/js/metrics_reporter/tsconfig_base.json b/ui/webui/resources/js/metrics_reporter/tsconfig_base.json
deleted file mode 100644
index 5502828..0000000
--- a/ui/webui/resources/js/metrics_reporter/tsconfig_base.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
-  "extends": "../../../../../tools/typescript/tsconfig_base.json",
-  "compilerOptions": {
-    "allowJs": true
-  }
-}