diff --git a/BUILD.gn b/BUILD.gn
index b0456ba..dca156f 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1585,12 +1585,9 @@
 
   script_test("blink_python_tests") {
     script = "//testing/scripts/run_isolated_script_test.py"
-    args = [
-      "@WrappedPath(" +
-          rebase_path("//third_party/blink/tools/run_blinkpy_tests.py",
-                      root_build_dir) + ")",
-      "--coverage",
-    ]
+    args = [ "@WrappedPath(" +
+             rebase_path("//third_party/blink/tools/run_blinkpy_tests.py",
+                         root_build_dir) + ")" ]
 
     data = [
       # These tests use //build/android/devil_chromium.py even when !is_android,
diff --git a/DEPS b/DEPS
index de18e02..2435834 100644
--- a/DEPS
+++ b/DEPS
@@ -279,7 +279,7 @@
   'dawn_standalone': False,
 
   # reclient CIPD package version
-  'reclient_version': 're_client_version:0.72.0.b874055-gomaip',
+  'reclient_version': 're_client_version:0.76.0.f4c4bc4-gomaip',
 
   # Enable fetching Rust-related packages.
   'use_rust': False,
@@ -297,15 +297,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': '7acdbfc5ae97776c6f2502cfd4949bef67feae42',
+  'skia_revision': 'f2ac3b9728f770efb53c34aeca7da6527d36f311',
   # 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': 'babfba7408534bbc5b214b0898aa5c19634d50e7',
+  'v8_revision': '13f3751b987e0fd310cd189123dd86a51212f4bf',
   # 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': '87ed2c9d1eaf918d38ceb6c4f210ce27f8efa9cb',
+  'angle_revision': 'c9360ccbd34aecc458cd13091e28243cf74f9c50',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -368,7 +368,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '268569634e0497164b911738502a4438bf1e669b',
+  'catapult_revision': 'b2cd6477b94b91a0b94b3430a50ba3bce650c5b6',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -440,7 +440,7 @@
   # 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': '30a82350cdda38c01fd6794d9addee89ced7d10f',
+  'nearby_revision': '76d45f20db7260c315b52396c740ae3e3f7bfd9f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling securemessage
   # and whatever else without interference from each other.
@@ -483,7 +483,7 @@
   'libcxx_revision':       '55e0c49d148aecc33568a1a61b916323004a2d18',
 
   # GN CIPD package version.
-  'gn_version': 'git_revision:c8c63300ac8ecb66d8126af5407257209ae59044',
+  'gn_version': 'git_revision:0bcd37bd2b83f1a9ee17088037ebdfe6eab6d31a',
 }
 
 # Only these hosts are allowed for dependencies in this DEPS file.
@@ -873,7 +873,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/mac-amd64',
-          'version': 'nv2m5tQGuaQEvcuOnvOK8U8qcOB1z6Atuu1nEF9oGV4C',
+          'version': 'WBTAImq5mN_jfX-oLpNn_P3VJ_MbJjXvN-VRik9nnMoC',
         },
       ],
       'dep_type': 'cipd',
@@ -1693,7 +1693,7 @@
       'dep_type': 'cipd',
   },
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@d53c356ad7d9a77f43cff070b7af4212fb3f739f',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@476824373c1963ca293213cddd13f6a839390491',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + 'ebe84bec02c041d28f902da0214bf442743fc907',
@@ -1762,7 +1762,7 @@
       'packages': [
         {
           'package': 'skia/tools/goldctl/linux-amd64',
-          'version': '9fyLI7UE2vwgyQweLyqyh193CzDMxbUr0xRuqtcCLAgC',
+          'version': 'ebDbf3X2jdAICDlXMXUr7yp4muhSvYoREDLdZZoJzuAC',
         },
       ],
       'dep_type': 'cipd',
@@ -1805,7 +1805,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@8057e5e76dec5b64f99c4c19f748d20c892822c7',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@cce874eebdb4e4f669f662c3f448102c6075ab87',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/browser/aw_permission_manager.cc b/android_webview/browser/aw_permission_manager.cc
index d0ddf34..fe625ae 100644
--- a/android_webview/browser/aw_permission_manager.cc
+++ b/android_webview/browser/aw_permission_manager.cc
@@ -16,6 +16,7 @@
 #include "components/permissions/permission_util.h"
 #include "content/public/browser/child_process_security_policy.h"
 #include "content/public/browser/permission_controller.h"
+#include "content/public/browser/permission_result.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/web_contents.h"
@@ -468,6 +469,17 @@
   return PermissionStatus::DENIED;
 }
 
+content::PermissionResult
+AwPermissionManager::GetPermissionResultForOriginWithoutContext(
+    blink::PermissionType permission,
+    const url::Origin& origin) {
+  blink::mojom::PermissionStatus status =
+      GetPermissionStatus(permission, origin.GetURL(), origin.GetURL());
+
+  return content::PermissionResult(
+      status, content::PermissionStatusSource::UNSPECIFIED);
+}
+
 PermissionStatus AwPermissionManager::GetPermissionStatusForCurrentDocument(
     PermissionType permission,
     content::RenderFrameHost* render_frame_host) {
diff --git a/android_webview/browser/aw_permission_manager.h b/android_webview/browser/aw_permission_manager.h
index 9713d7a..9276409 100644
--- a/android_webview/browser/aw_permission_manager.h
+++ b/android_webview/browser/aw_permission_manager.h
@@ -11,6 +11,7 @@
 #include "base/containers/id_map.h"
 #include "base/memory/weak_ptr.h"
 #include "content/public/browser/permission_controller_delegate.h"
+#include "content/public/browser/permission_result.h"
 
 namespace blink {
 enum class PermissionType;
@@ -60,6 +61,9 @@
       blink::PermissionType permission,
       const GURL& requesting_origin,
       const GURL& embedding_origin) override;
+  content::PermissionResult GetPermissionResultForOriginWithoutContext(
+      blink::PermissionType permission,
+      const url::Origin& origin) override;
   blink::mojom::PermissionStatus GetPermissionStatusForCurrentDocument(
       blink::PermissionType permission,
       content::RenderFrameHost* render_frame_host) override;
diff --git a/android_webview/browser/icon_helper.cc b/android_webview/browser/icon_helper.cc
index 8247e795..7e3b3b1 100644
--- a/android_webview/browser/icon_helper.cc
+++ b/android_webview/browser/icon_helper.cc
@@ -4,8 +4,6 @@
 
 #include "android_webview/browser/icon_helper.h"
 
-#include <vector>
-
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/check_op.h"
@@ -18,7 +16,6 @@
 #include "third_party/blink/public/mojom/favicon/favicon_url.mojom.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/gfx/geometry/size.h"
-#include "ui/gfx/image/image_util.h"
 
 using content::BrowserThread;
 using content::WebContents;
@@ -55,13 +52,7 @@
     return;
   }
 
-  std::vector<SkBitmap> filtered_images;
-  std::vector<gfx::Size> filtered_bitmap_sizes;
-  gfx::FilterAndResizeImagesForMaximalSize(
-      bitmaps, kLargestIconSize, filtered_images, filtered_bitmap_sizes);
-  DCHECK_EQ(filtered_images.size(), filtered_bitmap_sizes.size());
-
-  if (filtered_images.size() == 0) {
+  if (bitmaps.size() == 0) {
     return;
   }
 
@@ -71,13 +62,13 @@
 
   if (listener_) {
     std::vector<size_t> best_indices;
-    SelectFaviconFrameIndices(filtered_bitmap_sizes,
+    SelectFaviconFrameIndices(original_bitmap_sizes,
                               std::vector<int>(1U, kLargestIconSize),
                               &best_indices, nullptr);
 
     listener_->OnReceivedIcon(
         image_url,
-        filtered_images[best_indices.size() == 0 ? 0 : best_indices.front()]);
+        bitmaps[best_indices.size() == 0 ? 0 : best_indices.front()]);
   }
 }
 
@@ -98,10 +89,10 @@
         }
         web_contents()->DownloadImage(
             candidate->icon_url,
-            true,         // Is a favicon
-            gfx::Size(),  // No preferred size
-            0,            // Max bitmap size - 0 means unlimited
-            false,        // Normal cache policy
+            true,              // Is a favicon
+            gfx::Size(),       // No preferred size
+            kLargestIconSize,  // Max bitmap size
+            false,             // Normal cache policy
             base::BindOnce(&IconHelper::DownloadFaviconCallback,
                            base::Unretained(this)));
         break;
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 a3d7065..fc06e59 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
@@ -345,7 +345,11 @@
                     "Enable rendering starvation-prevention during threaded scrolling."
                             + " See https://crbug.com/1315279."),
             Flag.baseFeature(BaseFeatures.NO_WAKE_UPS_FOR_CANCELED_TASKS,
+                    "Controls whether wake ups are possible for canceled tasks."),
+            Flag.baseFeature(BaseFeatures.REMOVE_CANCELED_TASKS_IN_TASK_QUEUE,
                     "Controls whether or not canceled delayed tasks are removed from task queues."),
+            Flag.baseFeature(BlinkFeatures.THROTTLE_INTERSECTION_OBSERVER_UMA,
+                    "Reduces fine-grained UMA metrics for IntersectionObserver."),
             // Add new commandline switches and features above. The final entry should have a
             // trailing comma for cleaner diffs.
     };
diff --git a/android_webview/tools/generate_flag_labels.py b/android_webview/tools/generate_flag_labels.py
index da9d3f12..917270a 100755
--- a/android_webview/tools/generate_flag_labels.py
+++ b/android_webview/tools/generate_flag_labels.py
@@ -59,6 +59,8 @@
     'AutofillShadowDom': 'AutofillShadowDOM',
     'HtmlParamElementUrlSupport': 'HTMLParamElementUrlSupport',
     'webview-mp-arch-fenced-frames': 'webview-mparch-fenced-frames',
+    'ThrottleIntersectionObserverUma': 'ThrottleIntersectionObserverUMA',
+    'RemoveCanceledTasksInTaskQueue': 'RemoveCanceledTasksInTaskQueue2',
     # The final entry should have a trailing comma for cleaner diffs. You may
     # remove the entry from this dictionary when it's time to delete the
     # corresponding base::Feature or commandline switch.
diff --git a/ash/app_list/views/apps_container_view_unittest.cc b/ash/app_list/views/apps_container_view_unittest.cc
index 1a40b461ae..27ffe61f 100644
--- a/ash/app_list/views/apps_container_view_unittest.cc
+++ b/ash/app_list/views/apps_container_view_unittest.cc
@@ -7,6 +7,7 @@
 #include "ash/app_list/app_list_controller_impl.h"
 #include "ash/app_list/model/app_list_test_model.h"
 #include "ash/app_list/test/app_list_test_helper.h"
+#include "ash/app_list/views/apps_grid_view_test_api.h"
 #include "ash/app_list/views/continue_section_view.h"
 #include "ash/app_list/views/recent_apps_view.h"
 #include "ash/app_list/views/search_box_view.h"
@@ -25,6 +26,34 @@
 
 namespace ash {
 
+namespace {
+
+class TransitionWaiter : public PaginationModelObserver {
+ public:
+  explicit TransitionWaiter(PaginationModel* model) : model_(model) {
+    model_->AddObserver(this);
+  }
+
+  TransitionWaiter(const TransitionWaiter&) = delete;
+  TransitionWaiter& operator=(const TransitionWaiter&) = delete;
+
+  ~TransitionWaiter() override { model_->RemoveObserver(this); }
+
+  void Wait() {
+    ui_run_loop_ = std::make_unique<base::RunLoop>();
+    ui_run_loop_->Run();
+  }
+
+ private:
+  // PaginationModelObserver:
+  void TransitionEnded() override { ui_run_loop_->QuitWhenIdle(); }
+
+  std::unique_ptr<base::RunLoop> ui_run_loop_;
+  PaginationModel* model_ = nullptr;
+};
+
+}  // namespace
+
 class AppsContainerViewTest : public AshTestBase {
  public:
   AppsContainerViewTest() {
@@ -66,6 +95,21 @@
         ->selected_page();
   }
 
+  int GetTotalPages() {
+    return GetAppListTestHelper()
+        ->GetRootPagedAppsGridView()
+        ->pagination_model()
+        ->total_pages();
+  }
+
+  bool HasGradientMask() {
+    return GetAppListTestHelper()
+        ->GetAppsContainerView()
+        ->scrollable_container_for_test()
+        ->layer()
+        ->layer_mask_layer();
+  }
+
  private:
   base::test::ScopedFeatureList features_;
   std::unique_ptr<test::AppListTestModel> app_list_test_model_;
@@ -327,4 +371,45 @@
   EXPECT_EQ(GetSelectedPage(), 0);
 }
 
+// Test that the gradient mask is created when the page drag begins, and
+// destroyed once the page drag has been released and completes.
+TEST_F(AppsContainerViewTest, StartPageDragThenRelease) {
+  GetAppListTestHelper()->AddAppItems(23);
+  TabletMode::Get()->SetEnabledForTest(true);
+  auto* apps_grid_view = GetAppListTestHelper()->GetRootPagedAppsGridView();
+  test::AppsGridViewTestApi test_api(apps_grid_view);
+
+  EXPECT_FALSE(HasGradientMask());
+  EXPECT_EQ(0, GetSelectedPage());
+  EXPECT_EQ(2, GetTotalPages());
+
+  TransitionWaiter transition_waiter(apps_grid_view->pagination_model());
+  gfx::Point start_page_drag = test_api.GetViewAtIndex(GridIndex(0, 0))
+                                   ->GetIconBoundsInScreen()
+                                   .bottom_right();
+  start_page_drag.Offset(10, 0);
+
+  // Begin a touch and drag the page upward.
+  auto* generator = GetEventGenerator();
+  generator->set_current_screen_location(start_page_drag);
+  generator->PressTouch();
+  generator->MoveTouchBy(0, -20);
+
+  // Move the touch down a bit so it does not register as a fling to the next
+  // page.
+  generator->MoveTouchBy(0, 1);
+
+  // Gradient mask should exist during the page drag.
+  EXPECT_TRUE(HasGradientMask());
+
+  // End the page drag and wait for the page to animate back to the correct
+  // position.
+  generator->ReleaseTouch();
+  transition_waiter.Wait();
+
+  // The gradient mask should be removed after the end of the page animation.
+  EXPECT_FALSE(HasGradientMask());
+  EXPECT_EQ(0, GetSelectedPage());
+}
+
 }  // namespace ash
diff --git a/ash/components/arc/ime/arc_ime_service.cc b/ash/components/arc/ime/arc_ime_service.cc
index 6a477f5..c2a1cad 100644
--- a/ash/components/arc/ime/arc_ime_service.cc
+++ b/ash/components/arc/ime/arc_ime_service.cc
@@ -548,7 +548,7 @@
   return true;
 }
 
-bool ArcImeService::GetCompositionCharacterBounds(uint32_t index,
+bool ArcImeService::GetCompositionCharacterBounds(size_t index,
                                                   gfx::Rect* rect) const {
   return false;
 }
diff --git a/ash/components/arc/ime/arc_ime_service.h b/ash/components/arc/ime/arc_ime_service.h
index 03c0cf3..8106b5b 100644
--- a/ash/components/arc/ime/arc_ime_service.h
+++ b/ash/components/arc/ime/arc_ime_service.h
@@ -132,7 +132,7 @@
   base::i18n::TextDirection GetTextDirection() const override;
   int GetTextInputFlags() const override;
   bool CanComposeInline() const override;
-  bool GetCompositionCharacterBounds(uint32_t index,
+  bool GetCompositionCharacterBounds(size_t index,
                                      gfx::Rect* rect) const override;
   bool HasCompositionText() const override;
   FocusReason GetFocusReason() const override;
diff --git a/ash/public/cpp/pagination/pagination_model.cc b/ash/public/cpp/pagination/pagination_model.cc
index d5628ba..0975b61a 100644
--- a/ash/public/cpp/pagination/pagination_model.cc
+++ b/ash/public/cpp/pagination/pagination_model.cc
@@ -271,6 +271,7 @@
   if (!duration.is_zero())
     transition_animation_->SetSlideDuration(duration);
 
+  is_transition_started_ = true;
   NotifyTransitionStarted();
   transition_animation_->Show();
 }
@@ -288,9 +289,11 @@
 }
 
 void PaginationModel::AnimationEnded(const gfx::Animation* animation) {
-  // Do not notify transition end for the reverting animation.
-  if (!IsRevertingCurrentTransition())
+  // Ensure that each TransitionStarted() has only one TransitionEnded().
+  if (is_transition_started_) {
+    is_transition_started_ = false;
     NotifyTransitionEnded();
+  }
 
   // Save |pending_selected_page_| because SelectPage resets it.
   int next_target = pending_selected_page_;
diff --git a/ash/public/cpp/pagination/pagination_model.h b/ash/public/cpp/pagination/pagination_model.h
index 86533993..226b4d22 100644
--- a/ash/public/cpp/pagination/pagination_model.h
+++ b/ash/public/cpp/pagination/pagination_model.h
@@ -34,6 +34,12 @@
       return target_page == rhs.target_page && progress == rhs.progress;
     }
 
+    std::string ToString() const {
+      std::stringstream ss;
+      ss << "Target Page: " << target_page << ", Progess: " << progress;
+      return ss.str();
+    }
+
     // Target page for the transition or -1 if there is no target page. For
     // page switcher, this is the target selected page. For touch scroll,
     // this is usually the previous or next page (or -1 when there is no
@@ -139,6 +145,9 @@
 
   Transition transition_;
 
+  // Whether a transition has started, but not yet ended.
+  bool is_transition_started_ = false;
+
   // Pending selected page when SelectedPage is called during a transition. If
   // multiple SelectPage is called while a transition is in progress, only the
   // last target page is remembered here.
diff --git a/ash/public/cpp/pagination/pagination_model_unittest.cc b/ash/public/cpp/pagination/pagination_model_unittest.cc
index 3eed56c..48e6a41 100644
--- a/ash/public/cpp/pagination/pagination_model_unittest.cc
+++ b/ash/public/cpp/pagination/pagination_model_unittest.cc
@@ -519,5 +519,23 @@
   EXPECT_EQ(1, observer_.transition_end_call_count());
 }
 
+// Tests that a canceled scroll will call both TransitionStart and
+// TransitionEnd.
+TEST_F(PaginationModelTest, CancelAnimationHasOneTransitionEnd) {
+  const int kStartPage = 2;
+
+  // Scroll to the next page (negative delta) and cancel it.
+  SetStartPageAndExpects(kStartPage, 0, 1, 0);
+  pagination()->StartScroll();
+  pagination()->UpdateScroll(-0.1);
+  EXPECT_EQ(kStartPage + 1, pagination()->transition().target_page);
+  pagination()->EndScroll(true);  // Cancel transition
+  WaitForPagingAnimation();
+  EXPECT_EQ(0, observer_.selection_count());
+
+  EXPECT_EQ(1, observer_.transition_start_call_count());
+  EXPECT_EQ(1, observer_.transition_end_call_count());
+}
+
 }  // namespace test
 }  // namespace ash
diff --git a/ash/rgb_keyboard/rgb_keyboard_manager.cc b/ash/rgb_keyboard/rgb_keyboard_manager.cc
index 14c23aa9..a8f9245 100644
--- a/ash/rgb_keyboard/rgb_keyboard_manager.cc
+++ b/ash/rgb_keyboard/rgb_keyboard_manager.cc
@@ -146,11 +146,6 @@
 
     ime_controller_ptr_->AddObserver(this);
   }
-
-  // Set keyboard to the default color on startup
-  RgbkbdClient::Get()->SetStaticBackgroundColor(SkColorGetR(kDefaultColor),
-                                                SkColorGetG(kDefaultColor),
-                                                SkColorGetB(kDefaultColor));
 }
 
 bool RgbKeyboardManager::IsPerKeyKeyboard() const {
diff --git a/ash/rgb_keyboard/rgb_keyboard_manager_unittest.cc b/ash/rgb_keyboard/rgb_keyboard_manager_unittest.cc
index f1db670..f1d8cf4 100644
--- a/ash/rgb_keyboard/rgb_keyboard_manager_unittest.cc
+++ b/ash/rgb_keyboard/rgb_keyboard_manager_unittest.cc
@@ -235,16 +235,6 @@
   EXPECT_TRUE(client_->get_caps_lock_state());
 }
 
-TEST_F(RgbKeyboardManagerTest, DefaultState) {
-  const RgbColor& default_rgb_values = client_->recently_sent_rgb();
-
-  EXPECT_FALSE(client_->is_rainbow_mode_set());
-
-  EXPECT_EQ(SkColorGetR(kDefaultColor), std::get<0>(default_rgb_values));
-  EXPECT_EQ(SkColorGetG(kDefaultColor), std::get<1>(default_rgb_values));
-  EXPECT_EQ(SkColorGetB(kDefaultColor), std::get<2>(default_rgb_values));
-}
-
 // TODO(jimmyxgong): This is just a stub test, there is only one enum available
 // so just check num times the function has been called.
 TEST_F(RgbKeyboardManagerTest, SetAnimationMode) {
diff --git a/ash/system/accessibility/dictation_button_tray.cc b/ash/system/accessibility/dictation_button_tray.cc
index 9aafb1fb4..c8af5cc 100644
--- a/ash/system/accessibility/dictation_button_tray.cc
+++ b/ash/system/accessibility/dictation_button_tray.cc
@@ -19,6 +19,7 @@
 #include "ash/system/tray/tray_utils.h"
 #include "components/prefs/pref_service.h"
 #include "ui/accessibility/accessibility_features.h"
+#include "ui/base/ime/text_input_client.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/compositor/layer.h"
 #include "ui/gfx/geometry/transform_util.h"
@@ -40,11 +41,18 @@
 constexpr float kInProgressAnimationScaleFactor = 0.875f;
 
 // Helper function that creates an image for the dictation icon.
-gfx::ImageSkia GetIconImage(bool enabled) {
+// |active| means Dictation is actively listening for speech. The icon
+// changes to an "on" icon from "off" when Dictation is listening.
+// |enabled| indicates whether the tray button is enabled, i.e. clickable.
+// A secondary color is used to indicate the icon is not enabled.
+gfx::ImageSkia GetIconImage(bool active, bool enabled) {
   const SkColor color =
-      TrayIconColor(Shell::Get()->session_controller()->GetSessionState());
-  return enabled ? gfx::CreateVectorIcon(kDictationOnNewuiIcon, color)
-                 : gfx::CreateVectorIcon(kDictationOffNewuiIcon, color);
+      enabled
+          ? TrayIconColor(Shell::Get()->session_controller()->GetSessionState())
+          : AshColorProvider::Get()->GetContentLayerColor(
+                AshColorProvider::ContentLayerType::kIconColorSecondary);
+  return active ? gfx::CreateVectorIcon(kDictationOnNewuiIcon, color)
+                : gfx::CreateVectorIcon(kDictationOffNewuiIcon, color);
 }
 
 }  // namespace
@@ -53,7 +61,13 @@
     : TrayBackgroundView(shelf),
       icon_(new views::ImageView()),
       download_progress_(0) {
-  const gfx::ImageSkia icon_image = GetIconImage(/*enabled=*/false);
+  Shell* shell = Shell::Get();
+  ui::TextInputClient* client =
+      shell->window_tree_host_manager()->input_method()->GetTextInputClient();
+  in_text_input_ =
+      (client && client->GetTextInputType() != ui::TEXT_INPUT_TYPE_NONE);
+  const gfx::ImageSkia icon_image =
+      GetIconImage(/*active=*/false, /*enabled=*/in_text_input_);
   const int vertical_padding = (kTrayItemSize - icon_image.height()) / 2;
   const int horizontal_padding = (kTrayItemSize - icon_image.width()) / 2;
   icon_->SetBorder(views::CreateEmptyBorder(
@@ -61,15 +75,18 @@
   icon_->SetTooltipText(
       l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ACCESSIBILITY_DICTATION));
   tray_container()->AddChildView(icon_);
-  Shell::Get()->AddShellObserver(this);
-  Shell::Get()->accessibility_controller()->AddObserver(this);
-  Shell::Get()->session_controller()->AddObserver(this);
+  shell->AddShellObserver(this);
+  shell->accessibility_controller()->AddObserver(this);
+  shell->session_controller()->AddObserver(this);
+  shell->window_tree_host_manager()->input_method()->AddObserver(this);
 }
 
 DictationButtonTray::~DictationButtonTray() {
-  Shell::Get()->RemoveShellObserver(this);
-  Shell::Get()->accessibility_controller()->RemoveObserver(this);
-  Shell::Get()->session_controller()->RemoveObserver(this);
+  Shell* shell = Shell::Get();
+  shell->RemoveShellObserver(this);
+  shell->accessibility_controller()->RemoveObserver(this);
+  shell->session_controller()->RemoveObserver(this);
+  shell->window_tree_host_manager()->input_method()->RemoveObserver(this);
 }
 
 bool DictationButtonTray::PerformAction(const ui::Event& event) {
@@ -116,8 +133,9 @@
 
 void DictationButtonTray::OnThemeChanged() {
   TrayBackgroundView::OnThemeChanged();
-  icon_->SetImage(GetIconImage(
-      Shell::Get()->accessibility_controller()->dictation_active()));
+  icon_->SetImage(
+      GetIconImage(Shell::Get()->accessibility_controller()->dictation_active(),
+                   GetEnabled()));
   if (progress_indicator_)
     progress_indicator_->InvalidateLayer();
 }
@@ -137,7 +155,7 @@
 }
 
 void DictationButtonTray::UpdateIcon(bool dictation_active) {
-  icon_->SetImage(GetIconImage(dictation_active));
+  icon_->SetImage(GetIconImage(dictation_active, GetEnabled()));
   SetIsActive(dictation_active);
 }
 
@@ -221,13 +239,31 @@
   UpdateIcon(Shell::Get()->accessibility_controller()->dictation_active());
 }
 
+void DictationButtonTray::OnCaretBoundsChanged(
+    const ui::TextInputClient* client) {
+  TextInputChanged(client);
+}
+
+void DictationButtonTray::OnTextInputStateChanged(
+    const ui::TextInputClient* client) {
+  TextInputChanged(client);
+}
+
+void DictationButtonTray::TextInputChanged(const ui::TextInputClient* client) {
+  in_text_input_ =
+      client && client->GetTextInputType() != ui::TEXT_INPUT_TYPE_NONE;
+  SetEnabled((download_progress_ <= 0 || download_progress_ >= 100) &&
+             in_text_input_);
+  CheckDictationStatusAndUpdateIcon();
+}
+
 void DictationButtonTray::UpdateOnSpeechRecognitionDownloadChanged(
     int download_progress) {
   if (!visible_preferred())
     return;
 
   bool download_in_progress = download_progress > 0 && download_progress < 100;
-  SetEnabled(!download_in_progress);
+  SetEnabled(!download_in_progress && in_text_input_);
   icon_->SetTooltipText(l10n_util::GetStringUTF16(
       download_in_progress
           ? IDS_ASH_ACCESSIBILITY_DICTATION_BUTTON_TOOLTIP_SODA_DOWNLOADING
diff --git a/ash/system/accessibility/dictation_button_tray.h b/ash/system/accessibility/dictation_button_tray.h
index f496190..840162d 100644
--- a/ash/system/accessibility/dictation_button_tray.h
+++ b/ash/system/accessibility/dictation_button_tray.h
@@ -11,6 +11,7 @@
 #include "ash/public/cpp/session/session_observer.h"
 #include "ash/shell_observer.h"
 #include "ash/system/tray/tray_background_view.h"
+#include "ui/base/ime/input_method_observer.h"
 #include "ui/events/event_constants.h"
 
 namespace views {
@@ -28,7 +29,8 @@
 class ASH_EXPORT DictationButtonTray : public TrayBackgroundView,
                                        public ShellObserver,
                                        public AccessibilityObserver,
-                                       public SessionObserver {
+                                       public SessionObserver,
+                                       public ui::InputMethodObserver {
  public:
   explicit DictationButtonTray(Shelf* shelf);
 
@@ -62,6 +64,13 @@
   // views::View:
   const char* GetClassName() const override;
 
+  // ui::InputMethodObserver:
+  void OnFocus() override {}
+  void OnBlur() override {}
+  void OnCaretBoundsChanged(const ui::TextInputClient* client) override;
+  void OnTextInputStateChanged(const ui::TextInputClient* client) override;
+  void OnInputMethodDestroyed(const ui::InputMethod* input_method) override {}
+
   // Updates this button's state and progress indicator when speech recognition
   // file download state changes.
   void UpdateOnSpeechRecognitionDownloadChanged(int download_progress);
@@ -89,6 +98,10 @@
   // Actively looks up dictation status and calls UpdateIcon.
   void CheckDictationStatusAndUpdateIcon();
 
+  // Called when text input state changes to determine whether Dictation
+  // should still be enabled and update the icon.
+  void TextInputChanged(const ui::TextInputClient* client);
+
   // Weak pointer, will be parented by TrayContainer for its lifetime.
   views::ImageView* icon_;
 
@@ -100,6 +113,9 @@
   // to be notified of progress changed events.
   std::unique_ptr<ProgressIndicator> progress_indicator_;
   base::CallbackListSubscription progress_changed_subscription_;
+
+  // Whether the cursor and focus is currently in a text input field.
+  bool in_text_input_ = false;
 };
 
 }  // namespace ash
diff --git a/ash/system/accessibility/dictation_button_tray_unittest.cc b/ash/system/accessibility/dictation_button_tray_unittest.cc
index bbcd102..e320068e 100644
--- a/ash/system/accessibility/dictation_button_tray_unittest.cc
+++ b/ash/system/accessibility/dictation_button_tray_unittest.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "ash/system/accessibility/dictation_button_tray.h"
+#include <memory>
 
 #include "ash/accessibility/accessibility_controller_impl.h"
 #include "ash/accessibility/test_accessibility_controller_client.h"
@@ -35,6 +36,10 @@
 #include "ui/accessibility/accessibility_features.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/window.h"
+#include "ui/base/ime/ash/ime_bridge.h"
+#include "ui/base/ime/ash/input_method_ash.h"
+#include "ui/base/ime/fake_text_input_client.h"
+#include "ui/base/ime/text_input_type.h"
 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
 #include "ui/display/display_switches.h"
 #include "ui/display/manager/display_manager.h"
@@ -101,7 +106,15 @@
   DictationButtonTrayTest& operator=(const DictationButtonTrayTest&) = delete;
 
   // AshTestBase:
-  void SetUp() override { AshTestBase::SetUp(); }
+  void SetUp() override {
+    // Focus some input text so the Dictation button will be enabled.
+    fake_text_input_client_ =
+        std::make_unique<ui::FakeTextInputClient>(ui::TEXT_INPUT_TYPE_TEXT);
+    ui::InputMethodAsh ime(nullptr);
+    ui::IMEBridge::Get()->SetInputContextHandler(&ime);
+    AshTestBase::SetUp();
+    FocusTextInputClient();
+  }
 
  protected:
   views::ImageView* GetImageView(DictationButtonTray* tray) {
@@ -110,6 +123,20 @@
   void CheckDictationStatusAndUpdateIcon(DictationButtonTray* tray) {
     tray->CheckDictationStatusAndUpdateIcon();
   }
+  void FocusTextInputClient() {
+    Shell::Get()
+        ->window_tree_host_manager()
+        ->input_method()
+        ->SetFocusedTextInputClient(fake_text_input_client_.get());
+  }
+  void DetachTextInputClient() {
+    Shell::Get()
+        ->window_tree_host_manager()
+        ->input_method()
+        ->SetFocusedTextInputClient(nullptr);
+  }
+
+  std::unique_ptr<ui::FakeTextInputClient> fake_text_input_client_;
 };
 
 // Ensures that creation doesn't cause any crashes and adds the image icon.
@@ -159,6 +186,8 @@
 
   ASSERT_FALSE(controller->dictation_active());
   ASSERT_FALSE(GetTray()->is_active());
+  // In an input text area by default.
+  EXPECT_TRUE(GetTray()->GetEnabled());
 
   Shell::Get()->accelerator_controller()->PerformActionIfEnabled(
       AcceleratorAction::TOGGLE_DICTATION, {});
@@ -171,6 +200,28 @@
   EXPECT_FALSE(GetTray()->is_active());
 }
 
+TEST_F(DictationButtonTrayTest, DisabledWhenNoInputFocused) {
+  DetachTextInputClient();
+
+  AccessibilityControllerImpl* controller =
+      Shell::Get()->accessibility_controller();
+  controller->dictation().SetEnabled(true);
+  DictationButtonTray* tray = GetTray();
+  EXPECT_FALSE(tray->GetEnabled());
+
+  // Action doesn't work because disabled.
+  Shell::Get()->accelerator_controller()->PerformActionIfEnabled(
+      AcceleratorAction::TOGGLE_DICTATION, {});
+  EXPECT_FALSE(controller->dictation_active());
+  EXPECT_FALSE(tray->GetEnabled());
+
+  FocusTextInputClient();
+  EXPECT_TRUE(tray->GetEnabled());
+
+  DetachTextInputClient();
+  EXPECT_FALSE(tray->GetEnabled());
+}
+
 // Base class for SODA tests of the dictation button tray.
 class DictationButtonTraySodaTest : public DictationButtonTrayTest {
  public:
@@ -260,6 +311,12 @@
   EXPECT_FALSE(tray->GetEnabled());
   EXPECT_EQ(base::UTF8ToUTF16(kDisabledTooltip), image->GetTooltipText());
 
+  // Enabled state doesn't change even if text input is focused.
+  DetachTextInputClient();
+  EXPECT_FALSE(tray->GetEnabled());
+  FocusTextInputClient();
+  EXPECT_FALSE(tray->GetEnabled());
+
   // The tray icon should *not* be visible when the download is in progress.
   ProgressIndicatorWaiter().WaitForProgress(progress_indicator, 0.5f);
   EXPECT_TRUE(IsProgressIndicatorVisible());
diff --git a/ash/system/keyboard_brightness/keyboard_backlight_color_controller.cc b/ash/system/keyboard_brightness/keyboard_backlight_color_controller.cc
index ed8732a..654a594 100644
--- a/ash/system/keyboard_brightness/keyboard_backlight_color_controller.cc
+++ b/ash/system/keyboard_brightness/keyboard_backlight_color_controller.cc
@@ -86,22 +86,12 @@
 void KeyboardBacklightColorController::OnActiveUserPrefServiceChanged(
     PrefService* pref_service) {
   const auto backlight_color = GetBacklightColor(GetActiveAccountId());
-  // Wallpaper extracted color may not be available at this state. Instead of
-  // setting wallpaper color here, let |OnWallpaperColorsChanged| handles it
-  // when the color is available.
-  if (backlight_color == personalization_app::mojom::BacklightColor::kWallpaper)
-    return;
   DisplayBacklightColor(backlight_color);
 }
 
 void KeyboardBacklightColorController::OnUserSessionUpdated(
     const AccountId& account_id) {
   const auto backlight_color = GetBacklightColor(account_id);
-  // Wallpaper extracted color may not be available at this state. Instead of
-  // setting wallpaper color here, let |OnWallpaperColorsChanged| handles it
-  // when the color is available.
-  if (backlight_color == personalization_app::mojom::BacklightColor::kWallpaper)
-    return;
   DisplayBacklightColor(backlight_color);
 }
 
diff --git a/ash/system/keyboard_brightness/keyboard_backlight_color_controller_unittest.cc b/ash/system/keyboard_brightness/keyboard_backlight_color_controller_unittest.cc
index 940a84ea..ca65ee0 100644
--- a/ash/system/keyboard_brightness/keyboard_backlight_color_controller_unittest.cc
+++ b/ash/system/keyboard_brightness/keyboard_backlight_color_controller_unittest.cc
@@ -119,10 +119,11 @@
   SimulateUserLogin(account_id_1);
   EXPECT_EQ(personalization_app::mojom::BacklightColor::kWallpaper,
             controller_->GetBacklightColor(account_id_1));
-  // Expect the Wallpaper color to be not set.
+  // Expect the Wallpaper color to be set to the default as wallpaper color is
+  // not valid in this state.
   histogram_tester().ExpectBucketCount(
-      "Ash.Personalization.KeyboardBacklight.WallpaperColor.Valid", false, 0);
-  EXPECT_EQ(SK_ColorTRANSPARENT, displayed_color());
+      "Ash.Personalization.KeyboardBacklight.WallpaperColor.Valid", false, 1);
+  EXPECT_EQ(kDefaultColor, displayed_color());
 
   controller_->SetBacklightColor(
       personalization_app::mojom::BacklightColor::kBlue, account_id_1);
@@ -148,10 +149,11 @@
   SimulateUserLogin(account_id_1);
   EXPECT_EQ(personalization_app::mojom::BacklightColor::kWallpaper,
             controller_->GetBacklightColor(account_id_1));
-  // Expect the Wallpaper color to be not set.
+  // Expect the Wallpaper color to be set to the default as wallpaper color is
+  // not valid in this state.
   histogram_tester().ExpectBucketCount(
-      "Ash.Personalization.KeyboardBacklight.WallpaperColor.Valid", false, 0);
-  EXPECT_EQ(SK_ColorTRANSPARENT, displayed_color());
+      "Ash.Personalization.KeyboardBacklight.WallpaperColor.Valid", false, 1);
+  EXPECT_EQ(kDefaultColor, displayed_color());
 
   controller_->SetBacklightColor(
       personalization_app::mojom::BacklightColor::kBlue, account_id_1);
diff --git a/ash/webui/personalization_app/resources/js/ambient/ambient_weather_element.ts b/ash/webui/personalization_app/resources/js/ambient/ambient_weather_element.ts
index f56dd7d..b635b70a 100644
--- a/ash/webui/personalization_app/resources/js/ambient/ambient_weather_element.ts
+++ b/ash/webui/personalization_app/resources/js/ambient/ambient_weather_element.ts
@@ -9,7 +9,7 @@
 
 import '../../css/common.css.js';
 import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
-import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.js';
 import 'chrome://resources/cr_elements/shared_style_css.m.js';
 
 import {TemperatureUnit} from '../personalization_app.mojom-webui.js';
diff --git a/ash/webui/shimless_rma/resources/BUILD.gn b/ash/webui/shimless_rma/resources/BUILD.gn
index e9f99d85..f4335c94 100644
--- a/ash/webui/shimless_rma/resources/BUILD.gn
+++ b/ash/webui/shimless_rma/resources/BUILD.gn
@@ -232,7 +232,7 @@
     ":shimless_rma_types",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_elements/cr_radio_button:cr_radio_button.m",
-    "//ui/webui/resources/cr_elements/cr_radio_group:cr_radio_group.m",
+    "//ui/webui/resources/cr_elements/cr_radio_group:cr_radio_group",
     "//ui/webui/resources/js:i18n_behavior.m",
   ]
 }
@@ -388,7 +388,7 @@
     ":mojo_interface_provider",
     ":shimless_rma_types",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-    "//ui/webui/resources/cr_elements/cr_radio_group:cr_radio_group.m",
+    "//ui/webui/resources/cr_elements/cr_radio_group:cr_radio_group",
   ]
 }
 
diff --git a/ash/webui/shimless_rma/resources/onboarding_choose_destination_page.js b/ash/webui/shimless_rma/resources/onboarding_choose_destination_page.js
index 3abdcf77..0bef7e0 100644
--- a/ash/webui/shimless_rma/resources/onboarding_choose_destination_page.js
+++ b/ash/webui/shimless_rma/resources/onboarding_choose_destination_page.js
@@ -5,7 +5,7 @@
 import './shimless_rma_shared_css.js';
 import './base_page.js';
 import '//resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
-import '//resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import '//resources/cr_elements/cr_radio_group/cr_radio_group.js';
 
 import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/js/i18n_behavior.m.js';
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
diff --git a/ash/webui/shimless_rma/resources/onboarding_choose_wipe_device_page.js b/ash/webui/shimless_rma/resources/onboarding_choose_wipe_device_page.js
index baecd00..33a4db1 100644
--- a/ash/webui/shimless_rma/resources/onboarding_choose_wipe_device_page.js
+++ b/ash/webui/shimless_rma/resources/onboarding_choose_wipe_device_page.js
@@ -6,7 +6,7 @@
 import './shimless_rma_shared_css.js';
 
 import '//resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
-import '//resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import '//resources/cr_elements/cr_radio_group/cr_radio_group.js';
 
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/js/i18n_behavior.m.js';
diff --git a/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java
index 3819a80..873ca0d 100644
--- a/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java
+++ b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java
@@ -789,6 +789,7 @@
         boolean success;
         boolean usedFallback = sAlwaysFallback && mFallbackServiceName != null;
         if (useStrongBinding) {
+            mStrongBindingCount++;
             success = mStrongBinding.bindServiceConnection();
         } else {
             mModerateBindingCount++;
diff --git a/base/hash/md5_constexpr_internal.h b/base/hash/md5_constexpr_internal.h
index d68816a..1261722d 100644
--- a/base/hash/md5_constexpr_internal.h
+++ b/base/hash/md5_constexpr_internal.h
@@ -82,7 +82,7 @@
     DCHECK_EQ(m % 64, 0u);
     if (i < n) {
       // Emit the message itself...
-      return data[i];
+      return static_cast<uint8_t>(data[i]);
     } else if (i == n) {
       // ...followed by the end of message marker.
       return 0x80;
diff --git a/build/android/gyp/compile_java.py b/build/android/gyp/compile_java.py
index 358f345f..ed3c9be 100755
--- a/build/android/gyp/compile_java.py
+++ b/build/android/gyp/compile_java.py
@@ -471,7 +471,7 @@
 
   # Use jar_path's directory to ensure paths are relative (needed for goma).
   temp_dir = jar_path + '.staging'
-  shutil.rmtree(temp_dir, True)
+  build_utils.DeleteDirectory(temp_dir)
   os.makedirs(temp_dir)
   info_file_context = None
   try:
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index 759842b..0272bf8 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -1525,6 +1525,13 @@
         "-Wno-ignored-pragma-optimize",
       ]
 
+      if (llvm_force_head_revision && !is_nacl) {
+        cflags += [
+          # TODO(crbug.com/1352183) Evaluate and possibly enable.
+          "-Wno-bitfield-constant-conversion",
+        ]
+      }
+
       if (!is_nacl) {
         cflags += [
           # TODO(crbug.com/1343975) Evaluate and possibly enable.
diff --git a/cc/base/math_util.cc b/cc/base/math_util.cc
index 7c700f83..e7f0e37e 100644
--- a/cc/base/math_util.cc
+++ b/cc/base/math_util.cc
@@ -1017,7 +1017,7 @@
   res->AppendInteger(gradient.angle());
   res->AppendInteger(gradient.step_count());
   for (size_t i = 0; i < gradient.step_count(); i++) {
-    res->AppendDouble(gradient.steps()[i].percent);
+    res->AppendDouble(gradient.steps()[i].fraction);
     res->AppendInteger(gradient.steps()[i].alpha);
   }
   res->EndArray();
diff --git a/cc/trees/property_tree_builder_unittest.cc b/cc/trees/property_tree_builder_unittest.cc
index a5e1770..b3a537c 100644
--- a/cc/trees/property_tree_builder_unittest.cc
+++ b/cc/trees/property_tree_builder_unittest.cc
@@ -764,7 +764,7 @@
   child1->SetIsDrawable(true);
 
   gfx::LinearGradient gradient_mask(45);
-  gradient_mask.AddStep(50, 0x50);
+  gradient_mask.AddStep(.5, 0x50);
   child1->SetGradientMask(gradient_mask);
 
   // Without render surface.
@@ -915,12 +915,12 @@
   grand_child1->SetIsDrawable(true);
 
   gfx::LinearGradient gradient_mask1(30);
-  gradient_mask1.AddStep(50, 0x50);
+  gradient_mask1.AddStep(.5, 0x50);
   child1->SetGradientMask(gradient_mask1);
 
   gfx::LinearGradient gradient_mask2(45);
   gradient_mask2.AddStep(0, 0xFF);
-  gradient_mask2.AddStep(100, 0x0);
+  gradient_mask2.AddStep(1, 0x0);
   grand_child1->SetGradientMask(gradient_mask2);
 
   CommitAndActivate();
diff --git a/chrome/VERSION b/chrome/VERSION
index 3462af25..187da01 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=106
 MINOR=0
-BUILD=5234
+BUILD=5235
 PATCH=0
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
index 23bd9f7..7c71dff90 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
@@ -49,6 +49,7 @@
 import org.chromium.base.supplier.Supplier;
 import org.chromium.base.task.PostTask;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.browser_controls.BrowserStateBrowserControlsVisibilityDelegate;
 import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider.CloseButtonPosition;
 import org.chromium.chrome.browser.compositor.bottombar.ephemeraltab.EphemeralTabCoordinator;
 import org.chromium.chrome.browser.crash.ChromePureJavaExceptionReporter;
@@ -116,6 +117,7 @@
 
     private final CustomTabLocationBar mLocationBar = new CustomTabLocationBar();
     private LocationBarModel mLocationBarModel;
+    private BrowserStateBrowserControlsVisibilityDelegate mBrowserControlsVisibilityDelegate;
 
     /**
      * Whether to use the toolbar as handle to resize the Window height.
@@ -226,15 +228,20 @@
      * @param actionModeCallback Callback to handle changes in contextual action Modes.
      * @param modalDialogManagerSupplier Supplier of {@link ModalDialogManager}.
      * @param ephemeralTabCoordinatorSupplier Supplier of {@link EphemeralTabCoordinator}.
+     * @param controlsVisibilityDelegate {@link BrowserStateBrowserControlsVisibilityDelegate} to
+     *         show / hide the browser control. Used to ensure toolbar is shown for a certain
+     *         duration.
      * @return The LocationBar implementation for this CustomTabToolbar.
      */
     public LocationBar createLocationBar(LocationBarModel locationBarModel,
             ActionMode.Callback actionModeCallback,
             Supplier<ModalDialogManager> modalDialogManagerSupplier,
-            Supplier<EphemeralTabCoordinator> ephemeralTabCoordinatorSupplier) {
+            Supplier<EphemeralTabCoordinator> ephemeralTabCoordinatorSupplier,
+            BrowserStateBrowserControlsVisibilityDelegate controlsVisibilityDelegate) {
         mLocationBarModel = locationBarModel;
         mLocationBar.init(locationBarModel, modalDialogManagerSupplier,
                 ephemeralTabCoordinatorSupplier, actionModeCallback);
+        mBrowserControlsVisibilityDelegate = controlsVisibilityDelegate;
         return mLocationBar;
     }
 
@@ -630,6 +637,7 @@
                        View.OnLongClickListener, ToolbarBrandingDelegate {
         private static final int TITLE_ANIM_DELAY_MS = 800;
         private static final int BRANDING_DELAY_MS = 1800;
+        private static final int MIN_URL_BAR_VISIBLE_TIME_POST_BRANDING_MS = 3000;
 
         private static final int STATE_DOMAIN_ONLY = 0;
         private static final int STATE_TITLE_ONLY = 1;
@@ -741,6 +749,13 @@
             mCurrentlyShowingBranding = false;
             recoverFromRegularState();
             runAfterBrandingRunnables();
+
+            int token = mBrowserControlsVisibilityDelegate.showControlsPersistent();
+            PostTask.postDelayedTask(UiThreadTaskTraits.USER_VISIBLE,
+                    ()
+                            -> mBrowserControlsVisibilityDelegate.releasePersistentShowingToken(
+                                    token),
+                    MIN_URL_BAR_VISIBLE_TIME_POST_BRANDING_MS);
         }
 
         private void cacheRegularState() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryManager.java b/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryManager.java
index 45f71389..17268e6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryManager.java
@@ -261,7 +261,7 @@
 
             mHistoryClustersCoordinator = new HistoryClustersCoordinator(
                     Profile.getLastUsedRegularProfile(), activity, TemplateUrlServiceFactory.get(),
-                    historyClustersDelegate, ChromeAccessibilityUtil.get());
+                    historyClustersDelegate, ChromeAccessibilityUtil.get(), mSnackbarManager);
         }
 
         // 1. Create selectable components.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
index 206a2d04..13807af 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
@@ -606,7 +606,7 @@
             CustomTabToolbar customTabToolbar = ((CustomTabToolbar) toolbarLayout);
             mLocationBar = customTabToolbar.createLocationBar(mLocationBarModel,
                     mActionModeController.getActionModeCallback(), modalDialogManagerSupplier,
-                    mEphemeralTabCoordinatorSupplier);
+                    mEphemeralTabCoordinatorSupplier, mControlsVisibilityDelegate);
         } else {
             OverrideUrlLoadingDelegate overrideUrlLoadingDelegate =
                     (url, transition, postDataType, postData, incognito)
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
index f0caeb17..ede8596 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
@@ -851,7 +851,7 @@
 
             mHistoryClustersCoordinator = new HistoryClustersCoordinator(profile, mActivity,
                     TemplateUrlServiceFactory.get(), historyClustersDelegate,
-                    ChromeAccessibilityUtil.get());
+                    ChromeAccessibilityUtil.get(), mSnackbarManagerSupplier.get());
             mHistoryClustersCoordinatorSupplier.set(mHistoryClustersCoordinator);
         }
     }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarUnitTest.java
index c6fb8fca4..112cf73 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbarUnitTest.java
@@ -6,6 +6,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -28,6 +29,7 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 import org.robolectric.Robolectric;
+import org.robolectric.Shadows;
 import org.robolectric.annotation.Config;
 import org.robolectric.annotation.LooperMode;
 import org.robolectric.annotation.LooperMode.Mode;
@@ -38,6 +40,7 @@
 import org.chromium.base.task.test.ShadowPostTask.TestImpl;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.browser_controls.BrowserStateBrowserControlsVisibilityDelegate;
 import org.chromium.chrome.browser.customtabs.features.toolbar.CustomTabToolbar.CustomTabLocationBar;
 import org.chromium.chrome.browser.flags.CachedFeatureFlags;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
@@ -45,6 +48,8 @@
 import org.chromium.ui.base.TestActivity;
 import org.chromium.url.JUnitTestGURLs;
 
+import java.util.concurrent.TimeUnit;
+
 /**
  * Tests AMP url handling in the CustomTab Toolbar.
  */
@@ -61,6 +66,8 @@
     ActionMode.Callback mActionModeCallback;
     @Mock
     CustomTabToolbarAnimationDelegate mAnimationDelegate;
+    @Mock
+    BrowserStateBrowserControlsVisibilityDelegate mControlsVisibleDelegate;
 
     private Activity mActivity;
     private CustomTabLocationBar mLocationBar;
@@ -85,8 +92,8 @@
         mActivity = Robolectric.buildActivity(TestActivity.class).get();
         CustomTabToolbar toolbar = (CustomTabToolbar) LayoutInflater.from(mActivity).inflate(
                 R.layout.custom_tabs_toolbar, null, false);
-        mLocationBar = (CustomTabLocationBar) toolbar.createLocationBar(
-                mLocationBarModel, mActionModeCallback, () -> null, () -> null);
+        mLocationBar = (CustomTabLocationBar) toolbar.createLocationBar(mLocationBarModel,
+                mActionModeCallback, () -> null, () -> null, mControlsVisibleDelegate);
         mUrlBar = toolbar.findViewById(R.id.url_bar);
         mTitleBar = toolbar.findViewById(R.id.title_bar);
         mLocationBar.setAnimDelegateForTesting(mAnimationDelegate);
@@ -177,6 +184,7 @@
         assertUrlAndTitleVisible(/*titleVisible=*/false, /*urlVisible=*/true);
         assertEquals("Runnables queue should be empty after reset to regular toolbar", 0,
                 postBrandingRunnableCounts());
+        verifyBrowserControlVisibleForRequiredDuration();
     }
 
     @Test
@@ -205,6 +213,7 @@
         assertUrlAndTitleVisible(/*titleVisible=*/false, /*urlVisible=*/true);
         assertEquals("Runnables queue should be empty after reset to regular toolbar", 0,
                 postBrandingRunnableCounts());
+        verifyBrowserControlVisibleForRequiredDuration();
     }
 
     private int postBrandingRunnableCounts() {
@@ -226,4 +235,14 @@
                 "Title visibility is off.", expectedTitleVisibility, mTitleBar.getVisibility());
         assertEquals("URL bar visibility is off.", expectedUrlVisibility, mUrlBar.getVisibility());
     }
+
+    private void verifyBrowserControlVisibleForRequiredDuration() {
+        // Verify browser control is visible for required duration (3000ms).
+        ShadowLooper looper = Shadows.shadowOf(Looper.getMainLooper());
+        verify(mControlsVisibleDelegate).showControlsPersistent();
+        looper.idleFor(2999, TimeUnit.MILLISECONDS);
+        verify(mControlsVisibleDelegate, never()).releasePersistentShowingToken(anyInt());
+        looper.idleFor(1, TimeUnit.MILLISECONDS);
+        verify(mControlsVisibleDelegate).releasePersistentShowingToken(anyInt());
+    }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/history_clusters/HistoryClustersCoordinatorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/history_clusters/HistoryClustersCoordinatorTest.java
index bba917d..86ff4fe 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/history_clusters/HistoryClustersCoordinatorTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/history_clusters/HistoryClustersCoordinatorTest.java
@@ -16,6 +16,9 @@
 import static org.robolectric.Shadows.shadowOf;
 
 import android.app.Activity;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
 import android.content.Intent;
 import android.view.MenuItem;
 import android.view.View;
@@ -53,11 +56,14 @@
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.TabCreator;
+import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.components.browser_ui.widget.selectable_list.SelectableListLayout;
 import org.chromium.components.browser_ui.widget.selectable_list.SelectionDelegate;
 import org.chromium.components.favicon.LargeIconBridge;
 import org.chromium.components.favicon.LargeIconBridgeJni;
 import org.chromium.components.search_engines.TemplateUrlService;
+import org.chromium.ui.base.Clipboard;
+import org.chromium.ui.base.ClipboardImpl;
 import org.chromium.ui.display.DisplayAndroidManager;
 import org.chromium.ui.util.AccessibilityUtil;
 import org.chromium.url.GURL;
@@ -181,6 +187,8 @@
     private HistoryClustersMetricsLogger mMetricsLogger;
     @Mock
     private AccessibilityUtil mAccessibilityUtil;
+    @Mock
+    private SnackbarManager mSnackbarManager;
 
     private ActivityScenario<ChromeTabbedActivity> mActivityScenario;
     private HistoryClustersCoordinator mHistoryClustersCoordinator;
@@ -219,7 +227,7 @@
                     mActivity = activity;
                     mHistoryClustersCoordinator = new HistoryClustersCoordinator(mProfile, activity,
                             mTemplateUrlService, mHistoryClustersDelegate, mMetricsLogger,
-                            mSelectionDelegate, mAccessibilityUtil);
+                            mSelectionDelegate, mAccessibilityUtil, mSnackbarManager);
                 });
     }
 
@@ -364,6 +372,26 @@
     }
 
     @Test
+    public void testCopyMenuItem() {
+        final ClipboardManager clipboardManager =
+                (ClipboardManager) mActivity.getSystemService(Context.CLIPBOARD_SERVICE);
+        assertNotNull(clipboardManager);
+        ((ClipboardImpl) Clipboard.getInstance())
+                .overrideClipboardManagerForTesting(clipboardManager);
+        clipboardManager.setPrimaryClip(ClipData.newPlainText(null, "dummy_val"));
+        doReturn("http://spec1.com").when(mGurl1).getSpec();
+
+        HistoryClustersToolbar toolbar = mHistoryClustersCoordinator.getActivityContentView()
+                                                 .findViewById(R.id.selectable_list)
+                                                 .findViewById(R.id.action_bar);
+
+        mSelectionDelegate.setSelectedItems(new HashSet<>(Arrays.asList(mVisit1)));
+        mHistoryClustersCoordinator.onMenuItemClick(
+                toolbar.getMenu().findItem(R.id.selection_mode_copy_link));
+        assertEquals(mVisit1.getNormalizedUrl().getSpec(), clipboardManager.getText());
+    }
+
+    @Test
     public void testSetQueryState() {
         mHistoryClustersCoordinator.inflateActivityView();
         mHistoryClustersCoordinator.setInitialQuery(QueryState.forQuery("dogs", ""));
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 5223c1c..da934f52 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -7083,6 +7083,9 @@
       <message name="IDS_READ_ANYTHING_INCREASE_FONT_SIZE_BUTTON_LABEL" desc="Accessibility label for the increase font size button of the Read Anything feature." translateable="false">
         Increase font-size
       </message>
+      <message name="IDS_READ_ANYTHING_COLORS_COMBOBOX_LABEL" desc="Accessibility label for the colors combobox of the Read Anything feature." translateable="false">
+        Theme
+      </message>
 
       <!-- User note strings -->
       <message name="IDS_USER_NOTE_TITLE" desc="Title of the User note feature, which gives users access to private user notes.">
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 0d9e9d2..09cf0e58 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -4029,6 +4029,8 @@
       "performance_manager/mechanisms/page_discarder.h",
       "performance_manager/mechanisms/page_loader.cc",
       "performance_manager/mechanisms/page_loader.h",
+      "performance_manager/metrics/metrics_provider.cc",
+      "performance_manager/metrics/metrics_provider.h",
       "performance_manager/persistence/site_data/site_data_cache_facade.cc",
       "performance_manager/persistence/site_data/site_data_cache_facade.h",
       "performance_manager/persistence/site_data/site_data_cache_facade_factory.cc",
diff --git a/chrome/browser/DEPS b/chrome/browser/DEPS
index 0d9d09b..60ff85eb 100644
--- a/chrome/browser/DEPS
+++ b/chrome/browser/DEPS
@@ -500,6 +500,7 @@
   "+chrome/browser/performance_manager/test_support",
   "+chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.h",
   "+chrome/browser/performance_manager/chrome_content_browser_client_performance_manager_part.h",
+  "+chrome/browser/performance_manager/metrics/metrics_provider.h",
   "+chrome/browser/performance_manager/policies/policy_features.h",
 
   # Explicitly disallow using SyncMessageFilter to prevent browser from
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 697f743..0da4658 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -9067,6 +9067,13 @@
          password_manager::features::kBiometricAuthenticationForFilling)},
 #endif
 
+#if BUILDFLAG(IS_ANDROID)
+    {"reduce-gpu-priority-on-background",
+     flag_descriptions::kReduceGpuPriorityOnBackgroundName,
+     flag_descriptions::kReduceGpuPriorityOnBackgroundDescription, kOsAndroid,
+     FEATURE_VALUE_TYPE(::features::kReduceGpuPriorityOnBackground)},
+#endif
+
     // NOTE: Adding a new flag requires adding a corresponding entry to enum
     // "LoginCustomFlags" in tools/metrics/histograms/enums.xml. See "Flag
     // Histograms" in tools/metrics/histograms/README.md (run the
diff --git a/chrome/browser/accessibility/accessibility_labels_service_factory.cc b/chrome/browser/accessibility/accessibility_labels_service_factory.cc
index 8b5fa19..dec36626 100644
--- a/chrome/browser/accessibility/accessibility_labels_service_factory.cc
+++ b/chrome/browser/accessibility/accessibility_labels_service_factory.cc
@@ -36,7 +36,7 @@
 AccessibilityLabelsServiceFactory::AccessibilityLabelsServiceFactory()
     : ProfileKeyedServiceFactory(
           "AccessibilityLabelsService",
-          ProfileSelections::BuildServicesRedirectedToOriginal()) {}
+          ProfileSelections::BuildRedirectedInIncognito()) {}
 
 AccessibilityLabelsServiceFactory::~AccessibilityLabelsServiceFactory() {}
 
diff --git a/chrome/browser/accessibility/live_caption_controller_factory.cc b/chrome/browser/accessibility/live_caption_controller_factory.cc
index a210cbf..2e0ec31 100644
--- a/chrome/browser/accessibility/live_caption_controller_factory.cc
+++ b/chrome/browser/accessibility/live_caption_controller_factory.cc
@@ -34,7 +34,7 @@
 LiveCaptionControllerFactory::LiveCaptionControllerFactory()
     : ProfileKeyedServiceFactory(
           "LiveCaptionController",
-          ProfileSelections::BuildServicesRedirectedToOriginal()) {}
+          ProfileSelections::BuildRedirectedInIncognito()) {}
 
 LiveCaptionControllerFactory::~LiveCaptionControllerFactory() = default;
 
diff --git a/chrome/browser/android/omnibox/autocomplete_controller_android.cc b/chrome/browser/android/omnibox/autocomplete_controller_android.cc
index 7127216c..7aef586 100644
--- a/chrome/browser/android/omnibox/autocomplete_controller_android.cc
+++ b/chrome/browser/android/omnibox/autocomplete_controller_android.cc
@@ -481,7 +481,7 @@
 AutocompleteControllerAndroid::Factory::Factory()
     : ProfileKeyedServiceFactory(
           "AutocompleteControllerAndroid",
-          ProfileSelections::BuildServicesForAllProfiles()) {
+          ProfileSelections::BuildForRegularAndIncognito()) {
   DependsOn(TemplateURLServiceFactory::GetInstance());
   DependsOn(ShortcutsBackendFactory::GetInstance());
 }
diff --git a/chrome/browser/android/reading_list/reading_list_manager_factory.cc b/chrome/browser/android/reading_list/reading_list_manager_factory.cc
index 44935da..6e95e7c 100644
--- a/chrome/browser/android/reading_list/reading_list_manager_factory.cc
+++ b/chrome/browser/android/reading_list/reading_list_manager_factory.cc
@@ -26,7 +26,7 @@
 ReadingListManagerFactory::ReadingListManagerFactory()
     : ProfileKeyedServiceFactory(
           "ReadingListManager",
-          ProfileSelections::BuildServicesRedirectedToOriginal()) {
+          ProfileSelections::BuildRedirectedInIncognito()) {
   DependsOn(ReadingListModelFactory::GetInstance());
 }
 
diff --git a/chrome/browser/android/reading_list/reading_list_notification_service_factory.cc b/chrome/browser/android/reading_list/reading_list_notification_service_factory.cc
index d3d0d99..003a3ab 100644
--- a/chrome/browser/android/reading_list/reading_list_notification_service_factory.cc
+++ b/chrome/browser/android/reading_list/reading_list_notification_service_factory.cc
@@ -33,7 +33,7 @@
 ReadingListNotificationServiceFactory::ReadingListNotificationServiceFactory()
     : ProfileKeyedServiceFactory(
           "ReadingListNotificationService",
-          ProfileSelections::BuildServicesRedirectedToOriginal()) {
+          ProfileSelections::BuildRedirectedInIncognito()) {
   DependsOn(ReadingListModelFactory::GetInstance());
   DependsOn(NotificationScheduleServiceFactory::GetInstance());
 }
diff --git a/chrome/browser/android/webapk/webapk_install_service_factory.cc b/chrome/browser/android/webapk/webapk_install_service_factory.cc
index 32ba69e..9c0d9b0 100644
--- a/chrome/browser/android/webapk/webapk_install_service_factory.cc
+++ b/chrome/browser/android/webapk/webapk_install_service_factory.cc
@@ -21,7 +21,7 @@
 WebApkInstallServiceFactory::WebApkInstallServiceFactory()
     : ProfileKeyedServiceFactory(
           "WebApkInstallService",
-          ProfileSelections::BuildServicesRedirectedToOriginal()) {}
+          ProfileSelections::BuildRedirectedInIncognito()) {}
 
 WebApkInstallServiceFactory::~WebApkInstallServiceFactory() {}
 
diff --git a/chrome/browser/apps/platform_apps/app_load_service_factory.cc b/chrome/browser/apps/platform_apps/app_load_service_factory.cc
index d2bb0c9e..a3cb3f3 100644
--- a/chrome/browser/apps/platform_apps/app_load_service_factory.cc
+++ b/chrome/browser/apps/platform_apps/app_load_service_factory.cc
@@ -29,7 +29,7 @@
 AppLoadServiceFactory::AppLoadServiceFactory()
     : ProfileKeyedServiceFactory(
           "AppLoadService",
-          ProfileSelections::BuildServicesRedirectedToOriginal()) {
+          ProfileSelections::BuildRedirectedInIncognito()) {
   DependsOn(extensions::AppWindowRegistry::Factory::GetInstance());
   DependsOn(extensions::ExtensionPrefsFactory::GetInstance());
   DependsOn(extensions::ExtensionRegistryFactory::GetInstance());
diff --git a/chrome/browser/apps/platform_apps/app_termination_observer.cc b/chrome/browser/apps/platform_apps/app_termination_observer.cc
index 1cf307e..171e967 100644
--- a/chrome/browser/apps/platform_apps/app_termination_observer.cc
+++ b/chrome/browser/apps/platform_apps/app_termination_observer.cc
@@ -32,7 +32,7 @@
 AppTerminationObserverFactory::AppTerminationObserverFactory()
     : ProfileKeyedServiceFactory(
           "AppTerminationObserver",
-          ProfileSelections::BuildServicesRedirectedToOriginal()) {}
+          ProfileSelections::BuildRedirectedInIncognito()) {}
 
 KeyedService* AppTerminationObserverFactory::BuildServiceInstanceFor(
     content::BrowserContext* browser_context) const {
diff --git a/chrome/browser/ash/apps/apk_web_app_service_factory.cc b/chrome/browser/ash/apps/apk_web_app_service_factory.cc
index 9e5d533..3dbc440 100644
--- a/chrome/browser/ash/apps/apk_web_app_service_factory.cc
+++ b/chrome/browser/ash/apps/apk_web_app_service_factory.cc
@@ -31,7 +31,7 @@
 ApkWebAppServiceFactory::ApkWebAppServiceFactory()
     : ProfileKeyedServiceFactory(
           "ApkWebAppService",
-          ProfileSelections::BuildServicesRedirectedToOriginal()) {
+          ProfileSelections::BuildRedirectedInIncognito()) {
   DependsOn(ArcAppListPrefsFactory::GetInstance());
   DependsOn(web_app::WebAppProviderFactory::GetInstance());
 }
diff --git a/chrome/browser/ash/arc/enterprise/cert_store/cert_store_service.cc b/chrome/browser/ash/arc/enterprise/cert_store/cert_store_service.cc
index 5fe3843..4ba2e993 100644
--- a/chrome/browser/ash/arc/enterprise/cert_store/cert_store_service.cc
+++ b/chrome/browser/ash/arc/enterprise/cert_store/cert_store_service.cc
@@ -70,7 +70,7 @@
   CertStoreServiceFactory()
       : ProfileKeyedServiceFactory(
             "CertStoreService",
-            ProfileSelections::BuildServicesForAllProfiles()) {
+            ProfileSelections::BuildForRegularAndIncognito()) {
     DependsOn(NssServiceFactory::GetInstance());
   }
 
diff --git a/chrome/browser/ash/arc/fileapi/arc_documents_provider_root_map_factory.cc b/chrome/browser/ash/arc/fileapi/arc_documents_provider_root_map_factory.cc
index 0be4d4b..baf5307 100644
--- a/chrome/browser/ash/arc/fileapi/arc_documents_provider_root_map_factory.cc
+++ b/chrome/browser/ash/arc/fileapi/arc_documents_provider_root_map_factory.cc
@@ -22,7 +22,7 @@
 ArcDocumentsProviderRootMapFactory::ArcDocumentsProviderRootMapFactory()
     : ProfileKeyedServiceFactory(
           "ArcDocumentsProviderRootMap",
-          ProfileSelections::BuildServicesRedirectedToOriginal()) {
+          ProfileSelections::BuildRedirectedInIncognito()) {
   DependsOn(ArcFileSystemOperationRunner::GetFactory());
 }
 
diff --git a/chrome/browser/ash/drive/drive_integration_service.cc b/chrome/browser/ash/drive/drive_integration_service.cc
index 9969c14..d65d903 100644
--- a/chrome/browser/ash/drive/drive_integration_service.cc
+++ b/chrome/browser/ash/drive/drive_integration_service.cc
@@ -1434,7 +1434,7 @@
 DriveIntegrationServiceFactory::DriveIntegrationServiceFactory()
     : ProfileKeyedServiceFactory(
           "DriveIntegrationService",
-          ProfileSelections::BuildServicesRedirectedToOriginal()) {
+          ProfileSelections::BuildRedirectedInIncognito()) {
   DependsOn(IdentityManagerFactory::GetInstance());
   DependsOn(DriveNotificationManagerFactory::GetInstance());
   DependsOn(DownloadCoreServiceFactory::GetInstance());
diff --git a/chrome/browser/ash/file_manager/volume_manager_factory.cc b/chrome/browser/ash/file_manager/volume_manager_factory.cc
index b5687be..d12be46 100644
--- a/chrome/browser/ash/file_manager/volume_manager_factory.cc
+++ b/chrome/browser/ash/file_manager/volume_manager_factory.cc
@@ -51,7 +51,7 @@
     : ProfileKeyedServiceFactory(
           "VolumeManagerFactory",
           // Explicitly allow this manager in guest login mode.
-          ProfileSelections::BuildServicesForAllProfiles()) {
+          ProfileSelections::BuildForRegularAndIncognito()) {
   DependsOn(drive::DriveIntegrationServiceFactory::GetInstance());
   DependsOn(ash::file_system_provider::ServiceFactory::GetInstance());
 }
diff --git a/chrome/browser/ash/file_system_provider/service_factory.cc b/chrome/browser/ash/file_system_provider/service_factory.cc
index 555e5ef4..51b57a7f 100644
--- a/chrome/browser/ash/file_system_provider/service_factory.cc
+++ b/chrome/browser/ash/file_system_provider/service_factory.cc
@@ -33,7 +33,7 @@
 ServiceFactory::ServiceFactory()
     : ProfileKeyedServiceFactory(
           "Service",
-          ProfileSelections::BuildServicesRedirectedToOriginal()) {
+          ProfileSelections::BuildRedirectedInIncognito()) {
   DependsOn(extensions::ExtensionRegistryFactory::GetInstance());
   DependsOn(extensions::ExtensionSystemFactory::GetInstance());
   DependsOn(NotificationDisplayServiceFactory::GetInstance());
diff --git a/chrome/browser/ash/login/demo_mode/demo_setup_browsertest.cc b/chrome/browser/ash/login/demo_mode/demo_setup_browsertest.cc
index 8602cf4..e82ba577 100644
--- a/chrome/browser/ash/login/demo_mode/demo_setup_browsertest.cc
+++ b/chrome/browser/ash/login/demo_mode/demo_setup_browsertest.cc
@@ -112,7 +112,7 @@
 const test::UIPath kArcTosBackButton = {kArcTosId, "arcTosBackButton"};
 const test::UIPath kArcTosNextButton = {kArcTosId, "arcTosNextButton"};
 
-const test::UIPath kCCLoadedDialog = {kConsolidatedConsentId, "loadedDialog"};
+const test::UIPath kCCAcceptButton = {kConsolidatedConsentId, "acceptButton"};
 const test::UIPath kCCArcTosLink = {kConsolidatedConsentId, "arcTosLink"};
 const test::UIPath kCCBackButton = {kConsolidatedConsentId, "backButton"};
 
@@ -316,11 +316,12 @@
   }
 
   void WaitForConsolidatedConsentScreen() {
-    OobeScreenWaiter(ConsolidatedConsentScreenView::kScreenId).Wait();
-    test::OobeJS().CreateVisibilityWaiter(true, kCCLoadedDialog)->Wait();
+    test::WaitForConsolidatedConsentScreen();
 
     // Make sure that ARC ToS link is visible.
     test::OobeJS().ExpectVisiblePath(kCCArcTosLink);
+    test::OobeJS().CreateVisibilityWaiter(true, kCCAcceptButton)->Wait();
+    test::OobeJS().ExpectVisiblePath(kCCAcceptButton);
   }
 
   void AcceptArcTos() {
@@ -488,9 +489,14 @@
   IsConfirmationDialogHidden();
 }
 
-// TODO(crbug.com/1341234): flaky.
+// TODO(crbug.com/1150349): Flaky on ChromeOS ASAN.
+#if defined(ADDRESS_SANITIZER)
+#define MAYBE_OnlineSetupFlowSuccess DISABLED_OnlineSetupFlowSuccess
+#else
+#define MAYBE_OnlineSetupFlowSuccess OnlineSetupFlowSuccess
+#endif
 IN_PROC_BROWSER_TEST_F(DemoSetupArcSupportedTest,
-                       DISABLED_OnlineSetupFlowSuccess) {
+                       MAYBE_OnlineSetupFlowSuccess) {
   // Simulate successful online setup.
   enrollment_helper_.ExpectEnrollmentMode(
       policy::EnrollmentConfig::MODE_ATTESTATION);
diff --git a/chrome/browser/ash/login/easy_unlock/easy_unlock_tpm_key_manager_factory.cc b/chrome/browser/ash/login/easy_unlock/easy_unlock_tpm_key_manager_factory.cc
index 30a56c6..8c38752 100644
--- a/chrome/browser/ash/login/easy_unlock/easy_unlock_tpm_key_manager_factory.cc
+++ b/chrome/browser/ash/login/easy_unlock/easy_unlock_tpm_key_manager_factory.cc
@@ -53,7 +53,7 @@
 EasyUnlockTpmKeyManagerFactory::EasyUnlockTpmKeyManagerFactory()
     : ProfileKeyedServiceFactory(
           "EasyUnlockTpmKeyManager",
-          ProfileSelections::BuildServicesRedirectedToOriginal()) {}
+          ProfileSelections::BuildRedirectedInIncognito()) {}
 
 EasyUnlockTpmKeyManagerFactory::~EasyUnlockTpmKeyManagerFactory() {}
 
diff --git a/chrome/browser/ash/login/security_token_session_controller_factory.cc b/chrome/browser/ash/login/security_token_session_controller_factory.cc
index d4d12c1e..695a2af 100644
--- a/chrome/browser/ash/login/security_token_session_controller_factory.cc
+++ b/chrome/browser/ash/login/security_token_session_controller_factory.cc
@@ -20,7 +20,7 @@
 SecurityTokenSessionControllerFactory::SecurityTokenSessionControllerFactory()
     : ProfileKeyedServiceFactory(
           "SecurityTokenSessionController",
-          ProfileSelections::BuildServicesRedirectedToOriginal()) {
+          ProfileSelections::BuildRedirectedInIncognito()) {
   DependsOn(chromeos::CertificateProviderServiceFactory::GetInstance());
 }
 
diff --git a/chrome/browser/ash/login/signin_partition_manager.cc b/chrome/browser/ash/login/signin_partition_manager.cc
index c87d69a..96a1c18 100644
--- a/chrome/browser/ash/login/signin_partition_manager.cc
+++ b/chrome/browser/ash/login/signin_partition_manager.cc
@@ -186,7 +186,7 @@
 SigninPartitionManager::Factory::Factory()
     : ProfileKeyedServiceFactory(
           "SigninPartitionManager",
-          ProfileSelections::BuildServicesForAllProfiles()) {}
+          ProfileSelections::BuildForRegularAndIncognito()) {}
 
 SigninPartitionManager::Factory::~Factory() = default;
 
diff --git a/chrome/browser/ash/platform_keys/platform_keys_service_factory.cc b/chrome/browser/ash/platform_keys/platform_keys_service_factory.cc
index f93f887..b63b2b9 100644
--- a/chrome/browser/ash/platform_keys/platform_keys_service_factory.cc
+++ b/chrome/browser/ash/platform_keys/platform_keys_service_factory.cc
@@ -171,7 +171,7 @@
 PlatformKeysServiceFactory::PlatformKeysServiceFactory()
     : ProfileKeyedServiceFactory(
           "PlatformKeysService",
-          ProfileSelections::BuildServicesRedirectedToOriginal()) {
+          ProfileSelections::BuildRedirectedInIncognito()) {
   DependsOn(NssServiceFactory::GetInstance());
 }
 
diff --git a/chrome/browser/ash/plugin_vm/plugin_vm_installer_factory.cc b/chrome/browser/ash/plugin_vm/plugin_vm_installer_factory.cc
index ec4df1ec..b4eb7771 100644
--- a/chrome/browser/ash/plugin_vm/plugin_vm_installer_factory.cc
+++ b/chrome/browser/ash/plugin_vm/plugin_vm_installer_factory.cc
@@ -24,7 +24,7 @@
 PluginVmInstallerFactory::PluginVmInstallerFactory()
     : ProfileKeyedServiceFactory(
           "PluginVmInstaller",
-          ProfileSelections::BuildServicesRedirectedToOriginal()) {
+          ProfileSelections::BuildRedirectedInIncognito()) {
   DependsOn(BackgroundDownloadServiceFactory::GetInstance());
 }
 
diff --git a/chrome/browser/ash/policy/core/device_policy_decoder.cc b/chrome/browser/ash/policy/core/device_policy_decoder.cc
index 61298b6..47bda04 100644
--- a/chrome/browser/ash/policy/core/device_policy_decoder.cc
+++ b/chrome/browser/ash/policy/core/device_policy_decoder.cc
@@ -366,6 +366,10 @@
                     POLICY_SCOPE_MACHINE, POLICY_SOURCE_CLOUD,
                     base::Value(container.transfer_saml_cookies()), nullptr);
     }
+  }
+
+  if (policy.has_saml_username()) {
+    const em::SAMLUsernameProto& container(policy.saml_username());
     if (container.has_url_parameter_to_autofill_saml_username()) {
       policies->Set(
           key::kDeviceAutofillSAMLUsername, POLICY_LEVEL_MANDATORY,
diff --git a/chrome/browser/ash/policy/core/device_policy_decoder_unittest.cc b/chrome/browser/ash/policy/core/device_policy_decoder_unittest.cc
index a9dea9d..3d537514 100644
--- a/chrome/browser/ash/policy/core/device_policy_decoder_unittest.cc
+++ b/chrome/browser/ash/policy/core/device_policy_decoder_unittest.cc
@@ -416,7 +416,7 @@
                                     key::kDeviceAutofillSAMLUsername);
 
   base::Value autofill_saml_username_value("login_hint");
-  device_policy.mutable_saml_settings()
+  device_policy.mutable_saml_username()
       ->set_url_parameter_to_autofill_saml_username(
           autofill_saml_username_value.GetString());
 
diff --git a/chrome/browser/ash/printing/cups_print_job_manager_factory.cc b/chrome/browser/ash/printing/cups_print_job_manager_factory.cc
index a694398..90889b9 100644
--- a/chrome/browser/ash/printing/cups_print_job_manager_factory.cc
+++ b/chrome/browser/ash/printing/cups_print_job_manager_factory.cc
@@ -32,7 +32,7 @@
 CupsPrintJobManagerFactory::CupsPrintJobManagerFactory()
     : ProfileKeyedServiceFactory(
           "CupsPrintJobManagerFactory",
-          ProfileSelections::BuildServicesRedirectedToOriginal()) {
+          ProfileSelections::BuildRedirectedInIncognito()) {
   DependsOn(SyncedPrintersManagerFactory::GetInstance());
   DependsOn(CupsPrintersManagerFactory::GetInstance());
 }
diff --git a/chrome/browser/ash/printing/oauth2/authorization_zones_manager_factory.cc b/chrome/browser/ash/printing/oauth2/authorization_zones_manager_factory.cc
index 7b9a84f6..a43dd31 100644
--- a/chrome/browser/ash/printing/oauth2/authorization_zones_manager_factory.cc
+++ b/chrome/browser/ash/printing/oauth2/authorization_zones_manager_factory.cc
@@ -30,7 +30,7 @@
 AuthorizationZonesManagerFactory::AuthorizationZonesManagerFactory()
     : ProfileKeyedServiceFactory(
           "AuthorizationZonesManagerFactory",
-          ProfileSelections::BuildServicesRedirectedToOriginal()) {}
+          ProfileSelections::BuildRedirectedInIncognito()) {}
 
 AuthorizationZonesManagerFactory::~AuthorizationZonesManagerFactory() = default;
 
diff --git a/chrome/browser/ash/printing/print_management/printing_manager_factory.cc b/chrome/browser/ash/printing/print_management/printing_manager_factory.cc
index 59d3d1e..54b7cc1 100644
--- a/chrome/browser/ash/printing/print_management/printing_manager_factory.cc
+++ b/chrome/browser/ash/printing/print_management/printing_manager_factory.cc
@@ -32,7 +32,7 @@
 PrintingManagerFactory::PrintingManagerFactory()
     : ProfileKeyedServiceFactory(
           "PrintingManager",
-          ProfileSelections::BuildServicesRedirectedToOriginal()) {
+          ProfileSelections::BuildRedirectedInIncognito()) {
   DependsOn(PrintJobHistoryServiceFactory::GetInstance());
   DependsOn(HistoryServiceFactory::GetInstance());
   DependsOn(CupsPrintJobManagerFactory::GetInstance());
diff --git a/chrome/browser/ash/printing/printer_event_tracker_factory.cc b/chrome/browser/ash/printing/printer_event_tracker_factory.cc
index 38f612f4..812c10d 100644
--- a/chrome/browser/ash/printing/printer_event_tracker_factory.cc
+++ b/chrome/browser/ash/printing/printer_event_tracker_factory.cc
@@ -31,7 +31,7 @@
 PrinterEventTrackerFactory::PrinterEventTrackerFactory()
     : ProfileKeyedServiceFactory(
           "PrinterEventTracker",
-          ProfileSelections::BuildServicesForAllProfiles()) {}
+          ProfileSelections::BuildForRegularAndIncognito()) {}
 PrinterEventTrackerFactory::~PrinterEventTrackerFactory() = default;
 
 void PrinterEventTrackerFactory::SetLogging(bool enabled) {
diff --git a/chrome/browser/ash/printing/synced_printers_manager_factory.cc b/chrome/browser/ash/printing/synced_printers_manager_factory.cc
index 110c31e..a76551f 100644
--- a/chrome/browser/ash/printing/synced_printers_manager_factory.cc
+++ b/chrome/browser/ash/printing/synced_printers_manager_factory.cc
@@ -42,7 +42,7 @@
 SyncedPrintersManagerFactory::SyncedPrintersManagerFactory()
     : ProfileKeyedServiceFactory(
           "SyncedPrintersManager",
-          ProfileSelections::BuildServicesRedirectedToOriginal()) {
+          ProfileSelections::BuildRedirectedInIncognito()) {
   DependsOn(ModelTypeStoreServiceFactory::GetInstance());
 }
 
diff --git a/chrome/browser/ash/scanning/scan_service_factory.cc b/chrome/browser/ash/scanning/scan_service_factory.cc
index 1dc3069..17e8c87 100644
--- a/chrome/browser/ash/scanning/scan_service_factory.cc
+++ b/chrome/browser/ash/scanning/scan_service_factory.cc
@@ -54,7 +54,7 @@
 ScanServiceFactory::ScanServiceFactory()
     : ProfileKeyedServiceFactory(
           "ScanService",
-          ProfileSelections::BuildServicesRedirectedToOriginal()) {
+          ProfileSelections::BuildRedirectedInIncognito()) {
   DependsOn(LorgnetteScannerManagerFactory::GetInstance());
   DependsOn(HoldingSpaceKeyedServiceFactory::GetInstance());
 }
diff --git a/chrome/browser/ash/smb_client/smb_service_factory.cc b/chrome/browser/ash/smb_client/smb_service_factory.cc
index 3a6571dc..2a4a322 100644
--- a/chrome/browser/ash/smb_client/smb_service_factory.cc
+++ b/chrome/browser/ash/smb_client/smb_service_factory.cc
@@ -48,7 +48,7 @@
 SmbServiceFactory::SmbServiceFactory()
     : ProfileKeyedServiceFactory(
           /*name=*/"SmbService",
-          ProfileSelections::BuildServicesRedirectedToOriginal()) {
+          ProfileSelections::BuildRedirectedInIncognito()) {
   DependsOn(file_system_provider::ServiceFactory::GetInstance());
   DependsOn(AuthPolicyCredentialsManagerFactory::GetInstance());
   DependsOn(KerberosCredentialsManagerFactory::GetInstance());
diff --git a/chrome/browser/assist_ranker/assist_ranker_service_factory.cc b/chrome/browser/assist_ranker/assist_ranker_service_factory.cc
index 698853e..2d5a580f 100644
--- a/chrome/browser/assist_ranker/assist_ranker_service_factory.cc
+++ b/chrome/browser/assist_ranker/assist_ranker_service_factory.cc
@@ -6,9 +6,7 @@
 
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/net/system_network_context_manager.h"
-#include "chrome/browser/profiles/incognito_helpers.h"
 #include "components/assist_ranker/assist_ranker_service_impl.h"
-#include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "content/public/browser/browser_context.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
@@ -28,9 +26,9 @@
 }
 
 AssistRankerServiceFactory::AssistRankerServiceFactory()
-    : BrowserContextKeyedServiceFactory(
+    : ProfileKeyedServiceFactory(
           "AssistRankerService",
-          BrowserContextDependencyManager::GetInstance()) {}
+          ProfileSelections::BuildRedirectedInIncognito()) {}
 
 AssistRankerServiceFactory::~AssistRankerServiceFactory() {}
 
@@ -42,9 +40,4 @@
           ->GetSharedURLLoaderFactory());
 }
 
-content::BrowserContext* AssistRankerServiceFactory::GetBrowserContextToUse(
-    content::BrowserContext* context) const {
-  return chrome::GetBrowserContextRedirectedInIncognito(context);
-}
-
 }  // namespace assist_ranker
diff --git a/chrome/browser/assist_ranker/assist_ranker_service_factory.h b/chrome/browser/assist_ranker/assist_ranker_service_factory.h
index 97bd0dd5..f59a340 100644
--- a/chrome/browser/assist_ranker/assist_ranker_service_factory.h
+++ b/chrome/browser/assist_ranker/assist_ranker_service_factory.h
@@ -6,7 +6,7 @@
 #define CHROME_BROWSER_ASSIST_RANKER_ASSIST_RANKER_SERVICE_FACTORY_H_
 
 #include "base/memory/singleton.h"
-#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "chrome/browser/profiles/profile_keyed_service_factory.h"
 
 namespace content {
 class BrowserContext;
@@ -18,7 +18,7 @@
 
 namespace assist_ranker {
 
-class AssistRankerServiceFactory : public BrowserContextKeyedServiceFactory {
+class AssistRankerServiceFactory : public ProfileKeyedServiceFactory {
  public:
   static AssistRankerServiceFactory* GetInstance();
   static AssistRankerService* GetForBrowserContext(
@@ -37,8 +37,6 @@
   // BrowserContextKeyedServiceFactory:
   KeyedService* BuildServiceInstanceFor(
       content::BrowserContext* context) const override;
-  content::BrowserContext* GetBrowserContextToUse(
-      content::BrowserContext* context) const override;
 };
 
 }  // namespace assist_ranker
diff --git a/chrome/browser/autocomplete/autocomplete_classifier_factory.cc b/chrome/browser/autocomplete/autocomplete_classifier_factory.cc
index 16ccca1..fd72d04 100644
--- a/chrome/browser/autocomplete/autocomplete_classifier_factory.cc
+++ b/chrome/browser/autocomplete/autocomplete_classifier_factory.cc
@@ -11,10 +11,8 @@
 #include "chrome/browser/autocomplete/in_memory_url_index_factory.h"
 #include "chrome/browser/autocomplete/remote_suggestions_service_factory.h"
 #include "chrome/browser/autocomplete/shortcuts_backend_factory.h"
-#include "chrome/browser/profiles/incognito_helpers.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
-#include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "components/omnibox/browser/autocomplete_classifier.h"
 #include "components/omnibox/browser/autocomplete_controller.h"
 #include "extensions/buildflags/buildflags.h"
@@ -48,9 +46,9 @@
 }
 
 AutocompleteClassifierFactory::AutocompleteClassifierFactory()
-    : BrowserContextKeyedServiceFactory(
-        "AutocompleteClassifier",
-        BrowserContextDependencyManager::GetInstance()) {
+    : ProfileKeyedServiceFactory(
+          "AutocompleteClassifier",
+          ProfileSelections::BuildRedirectedInIncognito()) {
 #if BUILDFLAG(ENABLE_EXTENSIONS)
   DependsOn(
       extensions::ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
@@ -64,11 +62,6 @@
 AutocompleteClassifierFactory::~AutocompleteClassifierFactory() {
 }
 
-content::BrowserContext* AutocompleteClassifierFactory::GetBrowserContextToUse(
-    content::BrowserContext* context) const {
-  return chrome::GetBrowserContextRedirectedInIncognito(context);
-}
-
 bool AutocompleteClassifierFactory::ServiceIsNULLWhileTesting() const {
   return true;
 }
diff --git a/chrome/browser/autocomplete/autocomplete_classifier_factory.h b/chrome/browser/autocomplete/autocomplete_classifier_factory.h
index 7e8a3f32..6b05499f 100644
--- a/chrome/browser/autocomplete/autocomplete_classifier_factory.h
+++ b/chrome/browser/autocomplete/autocomplete_classifier_factory.h
@@ -8,14 +8,14 @@
 #include <memory>
 
 #include "base/memory/singleton.h"
-#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "chrome/browser/profiles/profile_keyed_service_factory.h"
 
 class AutocompleteClassifier;
 class Profile;
 
 // Singleton that owns all AutocompleteClassifiers and associates them with
 // Profiles.
-class AutocompleteClassifierFactory : public BrowserContextKeyedServiceFactory {
+class AutocompleteClassifierFactory : public ProfileKeyedServiceFactory {
  public:
   // Returns the AutocompleteClassifier for |profile|.
   static AutocompleteClassifier* GetForProfile(Profile* profile);
@@ -36,8 +36,6 @@
   ~AutocompleteClassifierFactory() override;
 
   // BrowserContextKeyedServiceFactory:
-  content::BrowserContext* GetBrowserContextToUse(
-      content::BrowserContext* context) const override;
   bool ServiceIsNULLWhileTesting() const override;
   KeyedService* BuildServiceInstanceFor(
       content::BrowserContext* profile) const override;
diff --git a/chrome/browser/autocomplete/document_suggestions_service_factory.cc b/chrome/browser/autocomplete/document_suggestions_service_factory.cc
index be19b43..b7422bc 100644
--- a/chrome/browser/autocomplete/document_suggestions_service_factory.cc
+++ b/chrome/browser/autocomplete/document_suggestions_service_factory.cc
@@ -7,7 +7,6 @@
 #include "base/memory/singleton.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
-#include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "components/omnibox/browser/document_suggestions_service.h"
 #include "content/public/browser/storage_partition.h"
 
@@ -37,9 +36,7 @@
 }
 
 DocumentSuggestionsServiceFactory::DocumentSuggestionsServiceFactory()
-    : BrowserContextKeyedServiceFactory(
-          "DocumentSuggestionsService",
-          BrowserContextDependencyManager::GetInstance()) {
+    : ProfileKeyedServiceFactory("DocumentSuggestionsService") {
   DependsOn(IdentityManagerFactory::GetInstance());
 }
 
diff --git a/chrome/browser/autocomplete/document_suggestions_service_factory.h b/chrome/browser/autocomplete/document_suggestions_service_factory.h
index d496d3e..7823b8f 100644
--- a/chrome/browser/autocomplete/document_suggestions_service_factory.h
+++ b/chrome/browser/autocomplete/document_suggestions_service_factory.h
@@ -6,13 +6,12 @@
 #define CHROME_BROWSER_AUTOCOMPLETE_DOCUMENT_SUGGESTIONS_SERVICE_FACTORY_H_
 
 #include "base/memory/singleton.h"
-#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "chrome/browser/profiles/profile_keyed_service_factory.h"
 
 class DocumentSuggestionsService;
 class Profile;
 
-class DocumentSuggestionsServiceFactory
-    : public BrowserContextKeyedServiceFactory {
+class DocumentSuggestionsServiceFactory : public ProfileKeyedServiceFactory {
  public:
   static DocumentSuggestionsService* GetForProfile(Profile* profile,
                                                    bool create_if_necessary);
diff --git a/chrome/browser/autocomplete/in_memory_url_index_factory.cc b/chrome/browser/autocomplete/in_memory_url_index_factory.cc
index 781cdaa..69828c7b 100644
--- a/chrome/browser/autocomplete/in_memory_url_index_factory.cc
+++ b/chrome/browser/autocomplete/in_memory_url_index_factory.cc
@@ -7,10 +7,8 @@
 #include "base/memory/singleton.h"
 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
 #include "chrome/browser/history/history_service_factory.h"
-#include "chrome/browser/profiles/incognito_helpers.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
-#include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "components/keyed_service/core/service_access_type.h"
 #include "components/omnibox/browser/in_memory_url_index.h"
 #include "content/public/common/url_constants.h"
@@ -27,9 +25,9 @@
 }
 
 InMemoryURLIndexFactory::InMemoryURLIndexFactory()
-    : BrowserContextKeyedServiceFactory(
+    : ProfileKeyedServiceFactory(
           "InMemoryURLIndex",
-          BrowserContextDependencyManager::GetInstance()) {
+          ProfileSelections::BuildRedirectedInIncognito()) {
   DependsOn(BookmarkModelFactory::GetInstance());
   DependsOn(HistoryServiceFactory::GetInstance());
   DependsOn(TemplateURLServiceFactory::GetInstance());
@@ -54,11 +52,6 @@
   return in_memory_url_index;
 }
 
-content::BrowserContext* InMemoryURLIndexFactory::GetBrowserContextToUse(
-    content::BrowserContext* context) const {
-  return chrome::GetBrowserContextRedirectedInIncognito(context);
-}
-
 bool InMemoryURLIndexFactory::ServiceIsNULLWhileTesting() const {
   return true;
 }
diff --git a/chrome/browser/autocomplete/in_memory_url_index_factory.h b/chrome/browser/autocomplete/in_memory_url_index_factory.h
index 4f925e8..c99da8d 100644
--- a/chrome/browser/autocomplete/in_memory_url_index_factory.h
+++ b/chrome/browser/autocomplete/in_memory_url_index_factory.h
@@ -5,7 +5,7 @@
 #ifndef CHROME_BROWSER_AUTOCOMPLETE_IN_MEMORY_URL_INDEX_FACTORY_H_
 #define CHROME_BROWSER_AUTOCOMPLETE_IN_MEMORY_URL_INDEX_FACTORY_H_
 
-#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "chrome/browser/profiles/profile_keyed_service_factory.h"
 
 namespace base {
 template <typename T> struct DefaultSingletonTraits;
@@ -14,7 +14,7 @@
 class InMemoryURLIndex;
 class Profile;
 
-class InMemoryURLIndexFactory : public BrowserContextKeyedServiceFactory {
+class InMemoryURLIndexFactory : public ProfileKeyedServiceFactory {
  public:
   static InMemoryURLIndex* GetForProfile(Profile* profile);
   static InMemoryURLIndexFactory* GetInstance();
@@ -28,8 +28,6 @@
   // BrowserContextKeyedServiceFactory:
   KeyedService* BuildServiceInstanceFor(
       content::BrowserContext* context) const override;
-  content::BrowserContext* GetBrowserContextToUse(
-      content::BrowserContext* context) const override;
   bool ServiceIsNULLWhileTesting() const override;
 };
 
diff --git a/chrome/browser/autocomplete/remote_suggestions_service_factory.cc b/chrome/browser/autocomplete/remote_suggestions_service_factory.cc
index d9fa615..13180fa 100644
--- a/chrome/browser/autocomplete/remote_suggestions_service_factory.cc
+++ b/chrome/browser/autocomplete/remote_suggestions_service_factory.cc
@@ -6,7 +6,6 @@
 
 #include "base/memory/singleton.h"
 #include "chrome/browser/profiles/profile.h"
-#include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "components/omnibox/browser/remote_suggestions_service.h"
 #include "content/public/browser/storage_partition.h"
 
@@ -33,9 +32,6 @@
 }
 
 RemoteSuggestionsServiceFactory::RemoteSuggestionsServiceFactory()
-    : BrowserContextKeyedServiceFactory(
-          "RemoteSuggestionsService",
-          BrowserContextDependencyManager::GetInstance()) {
-}
+    : ProfileKeyedServiceFactory("RemoteSuggestionsService") {}
 
 RemoteSuggestionsServiceFactory::~RemoteSuggestionsServiceFactory() {}
diff --git a/chrome/browser/autocomplete/remote_suggestions_service_factory.h b/chrome/browser/autocomplete/remote_suggestions_service_factory.h
index 8419b5e..42884738 100644
--- a/chrome/browser/autocomplete/remote_suggestions_service_factory.h
+++ b/chrome/browser/autocomplete/remote_suggestions_service_factory.h
@@ -6,13 +6,12 @@
 #define CHROME_BROWSER_AUTOCOMPLETE_REMOTE_SUGGESTIONS_SERVICE_FACTORY_H_
 
 #include "base/memory/singleton.h"
-#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "chrome/browser/profiles/profile_keyed_service_factory.h"
 
 class RemoteSuggestionsService;
 class Profile;
 
-class RemoteSuggestionsServiceFactory
-    : public BrowserContextKeyedServiceFactory {
+class RemoteSuggestionsServiceFactory : public ProfileKeyedServiceFactory {
  public:
   static RemoteSuggestionsService* GetForProfile(Profile* profile,
                                                  bool create_if_necessary);
diff --git a/chrome/browser/autofill/autocomplete_history_manager_factory.cc b/chrome/browser/autofill/autocomplete_history_manager_factory.cc
index 62f6173c..11e916178 100644
--- a/chrome/browser/autofill/autocomplete_history_manager_factory.cc
+++ b/chrome/browser/autofill/autocomplete_history_manager_factory.cc
@@ -5,12 +5,10 @@
 #include "chrome/browser/autofill/autocomplete_history_manager_factory.h"
 
 #include "base/memory/singleton.h"
-#include "chrome/browser/profiles/incognito_helpers.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/web_data_service_factory.h"
 #include "components/autofill/core/browser/autocomplete_history_manager.h"
 #include "components/autofill/core/browser/webdata/autofill_webdata_service.h"
-#include "components/keyed_service/content/browser_context_dependency_manager.h"
 
 namespace autofill {
 
@@ -28,9 +26,9 @@
 }
 
 AutocompleteHistoryManagerFactory::AutocompleteHistoryManagerFactory()
-    : BrowserContextKeyedServiceFactory(
+    : ProfileKeyedServiceFactory(
           "AutocompleteHistoryManager",
-          BrowserContextDependencyManager::GetInstance()) {
+          ProfileSelections::BuildForRegularAndIncognito()) {
   DependsOn(WebDataServiceFactory::GetInstance());
 }
 
@@ -49,10 +47,4 @@
   return service;
 }
 
-content::BrowserContext*
-AutocompleteHistoryManagerFactory::GetBrowserContextToUse(
-    content::BrowserContext* context) const {
-  return chrome::GetBrowserContextOwnInstanceInIncognito(context);
-}
-
 }  // namespace autofill
diff --git a/chrome/browser/autofill/autocomplete_history_manager_factory.h b/chrome/browser/autofill/autocomplete_history_manager_factory.h
index 29f6475b..dbd57a673 100644
--- a/chrome/browser/autofill/autocomplete_history_manager_factory.h
+++ b/chrome/browser/autofill/autocomplete_history_manager_factory.h
@@ -5,7 +5,7 @@
 #ifndef CHROME_BROWSER_AUTOFILL_AUTOCOMPLETE_HISTORY_MANAGER_FACTORY_H_
 #define CHROME_BROWSER_AUTOFILL_AUTOCOMPLETE_HISTORY_MANAGER_FACTORY_H_
 
-#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "chrome/browser/profiles/profile_keyed_service_factory.h"
 #include "components/keyed_service/core/keyed_service.h"
 
 namespace base {
@@ -23,8 +23,7 @@
 // Profiles.
 // Listens for the Profile's destruction notification and cleans up the
 // associated AutocompleteHistoryManager.
-class AutocompleteHistoryManagerFactory
-    : public BrowserContextKeyedServiceFactory {
+class AutocompleteHistoryManagerFactory : public ProfileKeyedServiceFactory {
  public:
   // Returns the AutocompleteHistoryManager for |profile|, creating it if it is
   // not yet created.
@@ -41,8 +40,6 @@
   // BrowserContextKeyedServiceFactory:
   KeyedService* BuildServiceInstanceFor(
       content::BrowserContext* profile) const override;
-  content::BrowserContext* GetBrowserContextToUse(
-      content::BrowserContext* context) const override;
 };
 
 }  // namespace autofill
diff --git a/chrome/browser/autofill/autofill_image_fetcher_factory.cc b/chrome/browser/autofill/autofill_image_fetcher_factory.cc
index 50f7ebcb..6871f96f5 100644
--- a/chrome/browser/autofill/autofill_image_fetcher_factory.cc
+++ b/chrome/browser/autofill/autofill_image_fetcher_factory.cc
@@ -7,10 +7,8 @@
 #include "base/no_destructor.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/image_fetcher/image_decoder_impl.h"
-#include "chrome/browser/profiles/incognito_helpers.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/autofill/core/browser/ui/autofill_image_fetcher.h"
-#include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "content/public/browser/storage_partition.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 
@@ -30,9 +28,9 @@
 }
 
 AutofillImageFetcherFactory::AutofillImageFetcherFactory()
-    : BrowserContextKeyedServiceFactory(
+    : ProfileKeyedServiceFactory(
           "AutofillImageFetcher",
-          BrowserContextDependencyManager::GetInstance()) {}
+          ProfileSelections::BuildRedirectedInIncognito()) {}
 
 AutofillImageFetcherFactory::~AutofillImageFetcherFactory() = default;
 
@@ -51,10 +49,4 @@
   return BuildAutofillImageFetcher(context);
 }
 
-content::BrowserContext* AutofillImageFetcherFactory::GetBrowserContextToUse(
-    content::BrowserContext* context) const {
-  // Use the image fetcher from the original browser context.
-  return chrome::GetBrowserContextRedirectedInIncognito(context);
-}
-
 }  // namespace autofill
diff --git a/chrome/browser/autofill/autofill_image_fetcher_factory.h b/chrome/browser/autofill/autofill_image_fetcher_factory.h
index 21bb0461..0dc1b96 100644
--- a/chrome/browser/autofill/autofill_image_fetcher_factory.h
+++ b/chrome/browser/autofill/autofill_image_fetcher_factory.h
@@ -6,16 +6,16 @@
 #define CHROME_BROWSER_AUTOFILL_AUTOFILL_IMAGE_FETCHER_FACTORY_H_
 
 #include "base/no_destructor.h"
-#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
-#include "components/keyed_service/core/keyed_service.h"
+#include "chrome/browser/profiles/profile_keyed_service_factory.h"
 
+class KeyedService;
 class Profile;
 
 namespace autofill {
 
 class AutofillImageFetcher;
 
-class AutofillImageFetcherFactory : public BrowserContextKeyedServiceFactory {
+class AutofillImageFetcherFactory : public ProfileKeyedServiceFactory {
  public:
   // Returns the AutofillImageFetcher for |profile|, creating it if it is not
   // yet created.
@@ -35,8 +35,6 @@
   // BrowserContextKeyedServiceFactory:
   KeyedService* BuildServiceInstanceFor(
       content::BrowserContext* profile) const override;
-  content::BrowserContext* GetBrowserContextToUse(
-      content::BrowserContext* context) const override;
 };
 
 }  // namespace autofill
diff --git a/chrome/browser/autofill/autofill_offer_manager_factory.cc b/chrome/browser/autofill/autofill_offer_manager_factory.cc
index 0ce1d47b..859ec1a6 100644
--- a/chrome/browser/autofill/autofill_offer_manager_factory.cc
+++ b/chrome/browser/autofill/autofill_offer_manager_factory.cc
@@ -8,7 +8,6 @@
 #include "build/build_config.h"
 #include "chrome/browser/autofill/personal_data_manager_factory.h"
 #include "components/autofill/core/browser/payments/autofill_offer_manager.h"
-#include "components/keyed_service/content/browser_context_dependency_manager.h"
 #if !BUILDFLAG(IS_ANDROID)
 #include "chrome/browser/commerce/coupons/coupon_service.h"
 #include "chrome/browser/commerce/coupons/coupon_service_factory.h"
@@ -30,9 +29,7 @@
 }
 
 AutofillOfferManagerFactory::AutofillOfferManagerFactory()
-    : BrowserContextKeyedServiceFactory(
-          "AutofillOfferManager",
-          BrowserContextDependencyManager::GetInstance()) {
+    : ProfileKeyedServiceFactory("AutofillOfferManager") {
   DependsOn(PersonalDataManagerFactory::GetInstance());
 #if !BUILDFLAG(IS_ANDROID)
   DependsOn(CouponServiceFactory::GetInstance());
diff --git a/chrome/browser/autofill/autofill_offer_manager_factory.h b/chrome/browser/autofill/autofill_offer_manager_factory.h
index 1af610b..1a85f66 100644
--- a/chrome/browser/autofill/autofill_offer_manager_factory.h
+++ b/chrome/browser/autofill/autofill_offer_manager_factory.h
@@ -5,7 +5,7 @@
 #ifndef CHROME_BROWSER_AUTOFILL_AUTOFILL_OFFER_MANAGER_FACTORY_H_
 #define CHROME_BROWSER_AUTOFILL_AUTOFILL_OFFER_MANAGER_FACTORY_H_
 
-#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "chrome/browser/profiles/profile_keyed_service_factory.h"
 #include "components/keyed_service/core/keyed_service.h"
 
 namespace base {
@@ -19,7 +19,7 @@
 
 // Singleton that owns all AutofillOfferManager and associates them with
 // Profiles.
-class AutofillOfferManagerFactory : public BrowserContextKeyedServiceFactory {
+class AutofillOfferManagerFactory : public ProfileKeyedServiceFactory {
  public:
   AutofillOfferManagerFactory(const AutofillOfferManagerFactory&) = delete;
   AutofillOfferManagerFactory& operator=(const AutofillOfferManagerFactory&) =
diff --git a/chrome/browser/autofill/form_structure_browsertest.cc b/chrome/browser/autofill/form_structure_browsertest.cc
index a6a57bd..9c385fb 100644
--- a/chrome/browser/autofill/form_structure_browsertest.cc
+++ b/chrome/browser/autofill/form_structure_browsertest.cc
@@ -219,14 +219,16 @@
        // TODO(crbug.com/1150895) Remove once launched.
        features::kAutofillParsingPatternProvider,
        features::kAutofillPageLanguageDetection,
-       // TODO(crbug/1165780): Remove once shared labels are launched.
+       // TODO(crbug.com/1165780): Remove once shared labels are launched.
        features::kAutofillEnableSupportForParsingWithSharedLabels,
        // TODO(crbug.com/1277480): Remove once launched.
        features::kAutofillEnableNameSurenameParsing,
        // TODO(crbug.com/1190334): Remove once launched.
        features::kAutofillParseMerchantPromoCodeFields,
        // TODO(crbug.com/1113970): Remove once launched.
-       features::kAutofillSectionUponRedundantNameInfo},
+       features::kAutofillSectionUponRedundantNameInfo,
+       // TODO(crbug.com/1335549): Remove once launched.
+       features::kAutofillParseIBANFields},
       // Disabled
       {});
 }
diff --git a/chrome/browser/autofill/merchant_promo_code_manager_factory.cc b/chrome/browser/autofill/merchant_promo_code_manager_factory.cc
index 388ec511..4e758ad 100644
--- a/chrome/browser/autofill/merchant_promo_code_manager_factory.cc
+++ b/chrome/browser/autofill/merchant_promo_code_manager_factory.cc
@@ -6,10 +6,8 @@
 
 #include "base/memory/singleton.h"
 #include "chrome/browser/autofill/personal_data_manager_factory.h"
-#include "chrome/browser/profiles/incognito_helpers.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/autofill/core/browser/merchant_promo_code_manager.h"
-#include "components/keyed_service/content/browser_context_dependency_manager.h"
 
 namespace autofill {
 
@@ -27,9 +25,9 @@
 }
 
 MerchantPromoCodeManagerFactory::MerchantPromoCodeManagerFactory()
-    : BrowserContextKeyedServiceFactory(
+    : ProfileKeyedServiceFactory(
           "MerchantPromoCodeManager",
-          BrowserContextDependencyManager::GetInstance()) {
+          ProfileSelections::BuildForRegularAndIncognito()) {
   DependsOn(PersonalDataManagerFactory::GetInstance());
 }
 
@@ -44,10 +42,4 @@
   return service;
 }
 
-content::BrowserContext*
-MerchantPromoCodeManagerFactory::GetBrowserContextToUse(
-    content::BrowserContext* context) const {
-  return chrome::GetBrowserContextOwnInstanceInIncognito(context);
-}
-
 }  // namespace autofill
diff --git a/chrome/browser/autofill/merchant_promo_code_manager_factory.h b/chrome/browser/autofill/merchant_promo_code_manager_factory.h
index 7eaa0831..e95ada0f 100644
--- a/chrome/browser/autofill/merchant_promo_code_manager_factory.h
+++ b/chrome/browser/autofill/merchant_promo_code_manager_factory.h
@@ -5,8 +5,7 @@
 #ifndef CHROME_BROWSER_AUTOFILL_MERCHANT_PROMO_CODE_MANAGER_FACTORY_H_
 #define CHROME_BROWSER_AUTOFILL_MERCHANT_PROMO_CODE_MANAGER_FACTORY_H_
 
-#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
-#include "components/keyed_service/core/keyed_service.h"
+#include "chrome/browser/profiles/profile_keyed_service_factory.h"
 
 namespace base {
 template <typename T>
@@ -22,8 +21,7 @@
 // Singleton that owns all MerchantPromoCodeManagers and associates
 // them with Profiles. Listens for the Profile's destruction notification and
 // cleans up the associated MerchantPromoCodeManager.
-class MerchantPromoCodeManagerFactory
-    : public BrowserContextKeyedServiceFactory {
+class MerchantPromoCodeManagerFactory : public ProfileKeyedServiceFactory {
  public:
   // Returns the MerchantPromoCodeManager for |profile|, creating it
   // if it is not yet created.
@@ -40,8 +38,6 @@
   // BrowserContextKeyedServiceFactory:
   KeyedService* BuildServiceInstanceFor(
       content::BrowserContext* profile) const override;
-  content::BrowserContext* GetBrowserContextToUse(
-      content::BrowserContext* context) const override;
 };
 
 }  // namespace autofill
diff --git a/chrome/browser/autofill_assistant/annotate_dom_model_service_factory.cc b/chrome/browser/autofill_assistant/annotate_dom_model_service_factory.cc
index 2c6e41e..6851a32d 100644
--- a/chrome/browser/autofill_assistant/annotate_dom_model_service_factory.cc
+++ b/chrome/browser/autofill_assistant/annotate_dom_model_service_factory.cc
@@ -17,12 +17,9 @@
 #include "components/autofill_assistant/browser/features.h"
 #include "components/autofill_assistant/browser/switches.h"
 #include "components/autofill_assistant/content/browser/annotate_dom_model_service.h"
-#include "components/keyed_service/content/browser_context_dependency_manager.h"
 
 AnnotateDomModelServiceFactory::AnnotateDomModelServiceFactory()
-    : BrowserContextKeyedServiceFactory(
-          "AnnotateDomModelService",
-          BrowserContextDependencyManager::GetInstance()) {}
+    : ProfileKeyedServiceFactory("AnnotateDomModelService") {}
 
 AnnotateDomModelServiceFactory::~AnnotateDomModelServiceFactory() = default;
 
diff --git a/chrome/browser/autofill_assistant/annotate_dom_model_service_factory.h b/chrome/browser/autofill_assistant/annotate_dom_model_service_factory.h
index 8603902..0213b2d5 100644
--- a/chrome/browser/autofill_assistant/annotate_dom_model_service_factory.h
+++ b/chrome/browser/autofill_assistant/annotate_dom_model_service_factory.h
@@ -5,7 +5,7 @@
 #ifndef CHROME_BROWSER_AUTOFILL_ASSISTANT_ANNOTATE_DOM_MODEL_SERVICE_FACTORY_H_
 #define CHROME_BROWSER_AUTOFILL_ASSISTANT_ANNOTATE_DOM_MODEL_SERVICE_FACTORY_H_
 
-#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "chrome/browser/profiles/profile_keyed_service_factory.h"
 
 namespace content {
 class BrowserContext;
@@ -16,8 +16,7 @@
 }  // namespace autofill_assistant
 
 // Creates instances of |AnnotateDomMOdelService| per |BrowserContext|.
-class AnnotateDomModelServiceFactory
-    : public BrowserContextKeyedServiceFactory {
+class AnnotateDomModelServiceFactory : public ProfileKeyedServiceFactory {
  public:
   AnnotateDomModelServiceFactory();
   ~AnnotateDomModelServiceFactory() override;
diff --git a/chrome/browser/chrome_browser_interface_binders.cc b/chrome/browser/chrome_browser_interface_binders.cc
index 1c17560..293386b 100644
--- a/chrome/browser/chrome_browser_interface_binders.cc
+++ b/chrome/browser/chrome_browser_interface_binders.cc
@@ -79,6 +79,7 @@
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/web_ui_browser_interface_broker_registry.h"
+#include "content/public/browser/web_ui_controller_interface_binder.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/url_constants.h"
 #include "extensions/buildflags/buildflags.h"
@@ -355,6 +356,8 @@
 namespace chrome {
 namespace internal {
 
+using content::RegisterWebUIControllerInterfaceBinder;
+
 #if BUILDFLAG(ENABLE_UNHANDLED_TAP)
 void BindUnhandledTapWebContentsObserver(
     content::RenderFrameHost* const host,
diff --git a/chrome/browser/chrome_browser_interface_binders.h b/chrome/browser/chrome_browser_interface_binders.h
index 7e8f7c3..464012c 100644
--- a/chrome/browser/chrome_browser_interface_binders.h
+++ b/chrome/browser/chrome_browser_interface_binders.h
@@ -5,16 +5,11 @@
 #ifndef CHROME_BROWSER_CHROME_BROWSER_INTERFACE_BINDERS_H_
 #define CHROME_BROWSER_CHROME_BROWSER_INTERFACE_BINDERS_H_
 
-#include "chrome/browser/bad_message.h"
-#include "content/public/browser/render_frame_host.h"
-#include "content/public/browser/web_contents.h"
-#include "content/public/browser/web_ui.h"
-#include "content/public/browser/web_ui_controller.h"
 #include "mojo/public/cpp/bindings/binder_map.h"
 
 namespace content {
-
 class RenderFrameHost;
+class WebUIBrowserInterfaceBrokerRegistry;
 }  // namespace content
 
 namespace chrome {
@@ -44,83 +39,6 @@
 void PopulateChromeWebUIFrameInterfaceBrokers(
     content::WebUIBrowserInterfaceBrokerRegistry& registry);
 
-template <typename Interface, int N, typename... Subclasses>
-struct BinderHelper;
-
-template <typename Interface, typename WebUIControllerSubclass>
-bool SafeDownCastAndBindInterface(content::WebUI* web_ui,
-                                  mojo::PendingReceiver<Interface>& receiver) {
-  // Performs a safe downcast to the concrete WebUIController subclass.
-  WebUIControllerSubclass* concrete_controller =
-      web_ui ? web_ui->GetController()->GetAs<WebUIControllerSubclass>()
-             : nullptr;
-
-  if (!concrete_controller)
-    return false;
-
-  // Fails to compile if |Subclass| does not implement the appropriate overload
-  // for |Interface|.
-  concrete_controller->BindInterface(std::move(receiver));
-  return true;
-}
-
-template <typename Interface, int N, typename Subclass, typename... Subclasses>
-struct BinderHelper<Interface, N, std::tuple<Subclass, Subclasses...>> {
-  static bool BindInterface(content::WebUI* web_ui,
-                            mojo::PendingReceiver<Interface> receiver) {
-    // Try a different subclass if the current one is not the right
-    // WebUIController for the current WebUI page, and only fail if none of the
-    // passed subclasses match.
-    if (!SafeDownCastAndBindInterface<Interface, Subclass>(web_ui, receiver)) {
-      return BinderHelper<Interface, N - 1, std::tuple<Subclasses...>>::
-          BindInterface(web_ui, std::move(receiver));
-    }
-    return true;
-  }
-};
-
-template <typename Interface, typename Subclass, typename... Subclasses>
-struct BinderHelper<Interface, 0, std::tuple<Subclass, Subclasses...>> {
-  static bool BindInterface(content::WebUI* web_ui,
-                            mojo::PendingReceiver<Interface> receiver) {
-    return SafeDownCastAndBindInterface<Interface, Subclass>(web_ui, receiver);
-  }
-};
-
-// Registers a binder in |map| that binds |Interface| iff the RenderFrameHost
-// has a WebUIController among type |WebUIControllerSubclasses|.
-template <typename Interface, typename... WebUIControllerSubclasses>
-void RegisterWebUIControllerInterfaceBinder(
-    mojo::BinderMapWithContext<content::RenderFrameHost*>* map) {
-  DCHECK(!map->Contains<Interface>())
-      << "A binder for " << Interface::Name_ << " has already been registered.";
-  map->Add<Interface>(
-      base::BindRepeating([](content::RenderFrameHost* host,
-                             mojo::PendingReceiver<Interface> receiver) {
-        // This is expected to be called only for outermost main frames.
-        if (host->GetParentOrOuterDocument()) {
-          ReceivedBadMessage(
-              host->GetProcess(),
-              bad_message::BadMessageReason::RFH_INVALID_WEB_UI_CONTROLLER);
-          return;
-        }
-
-        const int size = sizeof...(WebUIControllerSubclasses);
-        bool is_bound = BinderHelper<Interface, size - 1,
-                                     std::tuple<WebUIControllerSubclasses...>>::
-            BindInterface(host->GetWebUI(), std::move(receiver));
-
-        // This is expected to be called only for the right WebUI pages matching
-        // the same WebUI associated to the RenderFrameHost.
-        if (!is_bound) {
-          ReceivedBadMessage(
-              host->GetProcess(),
-              bad_message::BadMessageReason::RFH_INVALID_WEB_UI_CONTROLLER);
-          return;
-        }
-      }));
-}
-
 }  // namespace internal
 }  // namespace chrome
 
diff --git a/chrome/browser/content_settings/page_specific_content_settings_delegate.h b/chrome/browser/content_settings/page_specific_content_settings_delegate.h
index 91de10e..c7a09f4a 100644
--- a/chrome/browser/content_settings/page_specific_content_settings_delegate.h
+++ b/chrome/browser/content_settings/page_specific_content_settings_delegate.h
@@ -9,6 +9,7 @@
 #include "chrome/browser/browsing_data/access_context_audit_service.h"
 #include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/custom_handlers/protocol_handler.h"
+#include "content/public/browser/web_contents_observer.h"
 
 namespace chrome {
 
diff --git a/chrome/browser/content_settings/page_specific_content_settings_unittest.cc b/chrome/browser/content_settings/page_specific_content_settings_unittest.cc
index 66fc55a..fd2dc0b 100644
--- a/chrome/browser/content_settings/page_specific_content_settings_unittest.cc
+++ b/chrome/browser/content_settings/page_specific_content_settings_unittest.cc
@@ -9,6 +9,7 @@
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/content_settings/page_specific_content_settings_delegate.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
+#include "content/public/browser/web_contents.h"
 #include "testing/gmock/include/gmock/gmock-matchers.h"
 
 namespace content_settings {
diff --git a/chrome/browser/dips/dips_helper.cc b/chrome/browser/dips/dips_helper.cc
index b4b4936..6e587ed 100644
--- a/chrome/browser/dips/dips_helper.cc
+++ b/chrome/browser/dips/dips_helper.cc
@@ -6,9 +6,11 @@
 
 #include <utility>
 
+#include "base/metrics/histogram_functions.h"
+#include "base/strings/strcat.h"
 #include "base/time/default_clock.h"
 #include "chrome/browser/dips/dips_service.h"
-#include "chrome/browser/dips/dips_utils.h"
+#include "chrome/browser/dips/dips_storage.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/cookie_access_details.h"
 #include "content/public/browser/navigation_handle.h"
@@ -16,6 +18,26 @@
 
 namespace {
 
+inline void UmaHistogramTimeToInteraction(base::TimeDelta sample,
+                                          DIPSCookieMode mode) {
+  const std::string name = base::StrCat(
+      {"Privacy.DIPS.TimeFromStorageToInteraction", GetHistogramSuffix(mode)});
+
+  base::UmaHistogramCustomTimes(name, sample,
+                                /*min=*/base::TimeDelta(),
+                                /*max=*/base::Days(7), 100);
+}
+
+inline void UmaHistogramTimeToStorage(base::TimeDelta sample,
+                                      DIPSCookieMode mode) {
+  const std::string name = base::StrCat(
+      {"Privacy.DIPS.TimeFromInteractionToStorage", GetHistogramSuffix(mode)});
+
+  base::UmaHistogramCustomTimes(name, sample,
+                                /*min=*/base::TimeDelta(),
+                                /*max=*/base::Days(7), 100);
+}
+
 // The Clock that a new DIPSTabHelper will use internally. Exposed as a global
 // so that browser tests (which don't call the DIPSTabHelper constructor
 // directly) can inject a fake clock.
@@ -38,18 +60,8 @@
       service_->ShouldBlockThirdPartyCookies());
 }
 
-void DIPSTabHelper::FlushForTesting(base::OnceClosure flushed) {
-  service_->storage()
-      ->AsyncCall(&DIPSStorage::DoNothing)
-      .Then(std::move(flushed));
-}
-
-void DIPSTabHelper::StateForURLForTesting(const GURL& url,
-                                          StateForURLCallback callback) {
-  service_->storage()
-      ->AsyncCall(&DIPSStorage::Read)
-      .WithArgs(url)
-      .Then(std::move(callback));
+DIPSState DIPSTabHelper::StateForURL(const GURL& url) {
+  return service_->storage()->Read(url);
 }
 
 /* static */
@@ -57,29 +69,29 @@
   return std::exchange(g_clock, clock);
 }
 
-void DIPSTabHelper::RecordStorage(const GURL& url) {
+void DIPSTabHelper::MaybeRecordStorage(const GURL& url) {
+  DIPSState state = StateForURL(url);
+  if (state.site_storage_time()) {
+    // We want the time that storage was first written, so don't overwrite the
+    // existing timestamp.
+    return;
+  }
+
   base::Time now = clock_->Now();
-  DIPSCookieMode mode = GetCookieMode();
+  if (state.user_interaction_time()) {
+    // First storage, but previous interaction.
+    UmaHistogramTimeToStorage(now - state.user_interaction_time().value(),
+                              GetCookieMode());
+  }
 
-  service_->storage()
-      ->AsyncCall(&DIPSStorage::RecordStorage)
-      .WithArgs(url, now, mode);
-}
-
-void DIPSTabHelper::RecordInteraction(const GURL& url) {
-  base::Time now = clock_->Now();
-  DIPSCookieMode mode = GetCookieMode();
-
-  service_->storage()
-      ->AsyncCall(&DIPSStorage::RecordInteraction)
-      .WithArgs(url, now, mode);
+  state.set_site_storage_time(now);
 }
 
 void DIPSTabHelper::OnCookiesAccessed(
     content::RenderFrameHost* render_frame_host,
     const content::CookieAccessDetails& details) {
   if (details.type == content::CookieAccessDetails::Type::kChange) {
-    RecordStorage(details.url);
+    MaybeRecordStorage(details.url);
   }
 }
 
@@ -87,7 +99,7 @@
     content::NavigationHandle* handle,
     const content::CookieAccessDetails& details) {
   if (details.type == content::CookieAccessDetails::Type::kChange) {
-    RecordStorage(details.url);
+    MaybeRecordStorage(details.url);
   }
 }
 
@@ -101,4 +113,24 @@
   RecordInteraction(url);
 }
 
+void DIPSTabHelper::RecordInteraction(const GURL& url) {
+  DIPSState state = StateForURL(url);
+
+  base::Time now = clock_->Now();
+  if (!state.user_interaction_time()) {
+    // First interaction on site.
+    if (state.site_storage_time()) {
+      // Site previously wrote to storage. Record metric for the time delay
+      // between storage and interaction.
+      UmaHistogramTimeToInteraction(now - state.site_storage_time().value(),
+                                    GetCookieMode());
+    }
+  }
+
+  // Unlike for storage, we want to know the time of the most recent user
+  // interaction, so overwrite any existing timestamp. (If interaction happened
+  // a long time ago, it may no longer be relevant.)
+  state.set_user_interaction_time(now);
+}
+
 WEB_CONTENTS_USER_DATA_KEY_IMPL(DIPSTabHelper);
diff --git a/chrome/browser/dips/dips_helper.h b/chrome/browser/dips/dips_helper.h
index 8250849b..42a54772 100644
--- a/chrome/browser/dips/dips_helper.h
+++ b/chrome/browser/dips/dips_helper.h
@@ -26,14 +26,12 @@
 
   // Record that |url| wrote to storage, if it was the first such time (we
   // currently don't care about later writes to storage.)
-  void RecordStorage(const GURL& url);
+  void MaybeRecordStorage(const GURL& url);
   // Record that the user interacted on |url| .
   void RecordInteraction(const GURL& url);
 
-  void FlushForTesting(base::OnceClosure flushed);
+  DIPSState StateForURL(const GURL& url);
 
-  using StateForURLCallback = base::OnceCallback<void(DIPSState)>;
-  void StateForURLForTesting(const GURL& url, StateForURLCallback callback);
   static base::Clock* SetClockForTesting(base::Clock* clock);
 
  private:
diff --git a/chrome/browser/dips/dips_helper_browsertest.cc b/chrome/browser/dips/dips_helper_browsertest.cc
index 341738c3..0011ab9f 100644
--- a/chrome/browser/dips/dips_helper_browsertest.cc
+++ b/chrome/browser/dips/dips_helper_browsertest.cc
@@ -5,7 +5,6 @@
 #include "chrome/browser/dips/dips_helper.h"
 
 #include "base/memory/raw_ptr.h"
-#include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/simple_test_clock.h"
 #include "base/time/time.h"
@@ -103,32 +102,8 @@
 
   DIPSTabHelper* dips_helper() { return helper_; }
 
-  void BlockUntilHelperProcessesPendingRequests() {
-    base::RunLoop run_loop;
-    helper_->FlushForTesting(run_loop.QuitClosure());
-    run_loop.Run();
-  }
-
   void SetDIPSTime(base::Time time) { test_clock_.SetNow(time); }
 
-  void CopyDIPSState(DIPSState* a, DIPSState* b) {
-    a->set_site_storage_time_on_load(b->site_storage_time());
-    a->set_user_interaction_time_on_load(b->user_interaction_time());
-    a->set_was_loaded_for_testing(b->was_loaded());
-  }
-
-  DIPSState GetDIPSState(const GURL& url) {
-    DIPSState state;
-
-    helper_->StateForURLForTesting(
-        url, base::BindLambdaForTesting([&](DIPSState loaded_state) {
-          CopyDIPSState(&state, &loaded_state);
-        }));
-    BlockUntilHelperProcessesPendingRequests();
-
-    return state;
-  }
-
  private:
   base::SimpleTestClock test_clock_;
   raw_ptr<DIPSTabHelper> helper_ = nullptr;
@@ -153,8 +128,8 @@
   content::WaitForHitTestData(iframe);
 
   // Before clicking, no DIPS state for either site.
-  EXPECT_FALSE(GetDIPSState(url_a).was_loaded());
-  EXPECT_FALSE(GetDIPSState(url_b).was_loaded());
+  EXPECT_FALSE(dips_helper()->StateForURL(url_a).was_loaded());
+  EXPECT_FALSE(dips_helper()->StateForURL(url_b).was_loaded());
 
   // Click on the b.test iframe.
   SetDIPSTime(time);
@@ -163,12 +138,12 @@
   observer.Wait();
 
   // User interaction is recorded for a.test (the top-level frame).
-  DIPSState state_a = GetDIPSState(url_a);
+  DIPSState state_a = dips_helper()->StateForURL(url_a);
   EXPECT_TRUE(state_a.was_loaded());
   EXPECT_FALSE(state_a.site_storage_time().has_value());
   EXPECT_EQ(time, state_a.user_interaction_time().value());
   // User interaction is also recorded for b.test (the iframe).
-  DIPSState state_b = GetDIPSState(url_b);
+  DIPSState state_b = dips_helper()->StateForURL(url_b);
   EXPECT_TRUE(state_b.was_loaded());
   EXPECT_FALSE(state_b.site_storage_time().has_value());
   EXPECT_EQ(time, state_b.user_interaction_time().value());
@@ -197,8 +172,8 @@
       base::BindRepeating(&content::FrameIsChildOfMainFrame));
 
   // Initially, no DIPS state for either site.
-  EXPECT_FALSE(GetDIPSState(url_a).was_loaded());
-  EXPECT_FALSE(GetDIPSState(url_b).was_loaded());
+  EXPECT_FALSE(dips_helper()->StateForURL(url_a).was_loaded());
+  EXPECT_FALSE(dips_helper()->StateForURL(url_b).was_loaded());
 
   // Write a cookie in the b.test iframe.
   SetDIPSTime(time);
@@ -209,10 +184,10 @@
   observer.Wait();
 
   // Nothing recorded for a.test (the top-level frame).
-  DIPSState state_a = GetDIPSState(url_a);
+  DIPSState state_a = dips_helper()->StateForURL(url_a);
   EXPECT_FALSE(state_a.was_loaded());
   // Site storage was recorded for b.test (the iframe).
-  DIPSState state_b = GetDIPSState(url_b);
+  DIPSState state_b = dips_helper()->StateForURL(url_b);
   EXPECT_TRUE(state_b.was_loaded());
   EXPECT_EQ(time, state_b.site_storage_time().value());
   EXPECT_FALSE(state_b.user_interaction_time().has_value());
diff --git a/chrome/browser/dips/dips_service.cc b/chrome/browser/dips/dips_service.cc
index 50f7b15..68af45328 100644
--- a/chrome/browser/dips/dips_service.cc
+++ b/chrome/browser/dips/dips_service.cc
@@ -4,7 +4,6 @@
 
 #include "chrome/browser/dips/dips_service.h"
 
-#include "base/task/thread_pool.h"
 #include "chrome/browser/content_settings/cookie_settings_factory.h"
 #include "chrome/browser/dips/dips_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
@@ -13,8 +12,7 @@
 DIPSService::DIPSService(content::BrowserContext* context)
     : browser_context_(context),
       cookie_settings_(CookieSettingsFactory::GetForProfile(
-          Profile::FromBrowserContext(context))),
-      storage_(base::SequenceBound<DIPSStorage>(CreateTaskRunner())) {}
+          Profile::FromBrowserContext(context))) {}
 
 DIPSService::~DIPSService() = default;
 
@@ -27,12 +25,6 @@
   cookie_settings_.reset();
 }
 
-scoped_refptr<base::SequencedTaskRunner> DIPSService::CreateTaskRunner() {
-  return base::ThreadPool::CreateSequencedTaskRunner(
-      {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
-       base::ThreadPolicy::PREFER_BACKGROUND});
-}
-
 bool DIPSService::ShouldBlockThirdPartyCookies() const {
   return cookie_settings_->ShouldBlockThirdPartyCookies();
 }
diff --git a/chrome/browser/dips/dips_service.h b/chrome/browser/dips/dips_service.h
index 046d971..45fa818e 100644
--- a/chrome/browser/dips/dips_service.h
+++ b/chrome/browser/dips/dips_service.h
@@ -7,7 +7,6 @@
 
 #include "base/memory/raw_ptr.h"
 #include "base/memory/scoped_refptr.h"
-#include "base/threading/sequence_bound.h"
 #include "chrome/browser/dips/dips_storage.h"
 #include "components/keyed_service/core/keyed_service.h"
 
@@ -25,8 +24,7 @@
 
   static DIPSService* Get(content::BrowserContext* context);
 
-  base::SequenceBound<DIPSStorage>* storage() { return &storage_; }
-
+  DIPSStorage* storage() { return &storage_; }
   bool ShouldBlockThirdPartyCookies() const;
 
  private:
@@ -35,11 +33,9 @@
   explicit DIPSService(content::BrowserContext* context);
   void Shutdown() override;
 
-  scoped_refptr<base::SequencedTaskRunner> CreateTaskRunner();
-
   raw_ptr<content::BrowserContext> browser_context_;
   scoped_refptr<content_settings::CookieSettings> cookie_settings_;
-  base::SequenceBound<DIPSStorage> storage_;
+  DIPSStorage storage_;
 };
 
 #endif  // CHROME_BROWSER_DIPS_DIPS_SERVICE_H_
diff --git a/chrome/browser/dips/dips_state.cc b/chrome/browser/dips/dips_state.cc
index 3c6257db..5546154 100644
--- a/chrome/browser/dips/dips_state.cc
+++ b/chrome/browser/dips/dips_state.cc
@@ -28,10 +28,6 @@
   dirty_ = true;
 }
 
-void DIPSState::set_site_storage_time_on_load(absl::optional<base::Time> time) {
-  site_storage_time_ = time;
-}
-
 void DIPSState::set_user_interaction_time(absl::optional<base::Time> time) {
   if (time == user_interaction_time_) {
     return;
@@ -40,12 +36,3 @@
   user_interaction_time_ = time;
   dirty_ = true;
 }
-
-void DIPSState::set_user_interaction_time_on_load(
-    absl::optional<base::Time> time) {
-  user_interaction_time_ = time;
-}
-
-void DIPSState::set_was_loaded_for_testing(bool loaded) {
-  was_loaded_ = loaded;
-}
diff --git a/chrome/browser/dips/dips_state.h b/chrome/browser/dips/dips_state.h
index ae11d1b2..b138357 100644
--- a/chrome/browser/dips/dips_state.h
+++ b/chrome/browser/dips/dips_state.h
@@ -34,7 +34,6 @@
 // DIPSState represents the state recorded by DIPSService itself.
 class DIPSState {
  public:
-  DIPSState() = default;
   DIPSState(DIPSStorage* storage, std::string site, bool was_loaded);
   DIPSState(DIPSState&&);
   // Flushes changes to storage_.
@@ -44,22 +43,16 @@
   // True iff this DIPSState was loaded from DIPSStorage (as opposed to being
   // default-initialized for a new site).
   bool was_loaded() const { return was_loaded_; }
-  // For testing only.
-  void set_was_loaded_for_testing(bool loaded);
 
   absl::optional<base::Time> site_storage_time() const {
     return site_storage_time_;
   }
   void set_site_storage_time(absl::optional<base::Time> time);
-  // For loading/copying DIPSState objects only.
-  void set_site_storage_time_on_load(absl::optional<base::Time> time);
 
   absl::optional<base::Time> user_interaction_time() const {
     return user_interaction_time_;
   }
   void set_user_interaction_time(absl::optional<base::Time> time);
-  // For loading/copying DIPSState objects only.
-  void set_user_interaction_time_on_load(absl::optional<base::Time> time);
 
  private:
   raw_ptr<DIPSStorage> storage_;
diff --git a/chrome/browser/dips/dips_storage.cc b/chrome/browser/dips/dips_storage.cc
index 23ca184..364e053 100644
--- a/chrome/browser/dips/dips_storage.cc
+++ b/chrome/browser/dips/dips_storage.cc
@@ -4,42 +4,14 @@
 
 #include "chrome/browser/dips/dips_storage.h"
 
-#include "base/metrics/histogram_functions.h"
-#include "base/strings/strcat.h"
 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
 #include "url/gurl.h"
 
-namespace {
-
-inline void UmaHistogramTimeToInteraction(base::TimeDelta sample,
-                                          DIPSCookieMode mode) {
-  const std::string name = base::StrCat(
-      {"Privacy.DIPS.TimeFromStorageToInteraction", GetHistogramSuffix(mode)});
-
-  base::UmaHistogramCustomTimes(name, sample,
-                                /*min=*/base::TimeDelta(),
-                                /*max=*/base::Days(7), 100);
-}
-
-inline void UmaHistogramTimeToStorage(base::TimeDelta sample,
-                                      DIPSCookieMode mode) {
-  const std::string name = base::StrCat(
-      {"Privacy.DIPS.TimeFromInteractionToStorage", GetHistogramSuffix(mode)});
-
-  base::UmaHistogramCustomTimes(name, sample,
-                                /*min=*/base::TimeDelta(),
-                                /*max=*/base::Days(7), 100);
-}
-
-}  // namespace
-
 DIPSStorage::DIPSStorage() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DETACH_FROM_SEQUENCE(sequence_checker_);
 }
 
-DIPSStorage::~DIPSStorage() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-}
+DIPSStorage::~DIPSStorage() = default;
 
 DIPSState DIPSStorage::Read(const GURL& url) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -50,8 +22,8 @@
   }
   const StateValue& value = iter->second;
   DIPSState state(this, std::move(site), /*was_loaded=*/true);
-  state.set_site_storage_time_on_load(value.site_storage_time);
-  state.set_user_interaction_time_on_load(value.user_interaction_time);
+  state.set_site_storage_time(value.site_storage_time);
+  state.set_user_interaction_time(value.user_interaction_time);
   return state;
 }
 
@@ -62,47 +34,6 @@
   value.user_interaction_time = state.user_interaction_time();
 }
 
-void DIPSStorage::RecordStorage(const GURL& url,
-                                base::Time time,
-                                DIPSCookieMode mode) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DIPSState state = Read(url);
-  if (state.site_storage_time()) {
-    // We want the time that storage was first written, so don't overwrite the
-    // existing timestamp.
-    return;
-  }
-
-  if (state.user_interaction_time()) {
-    // First storage, but previous interaction.
-    UmaHistogramTimeToStorage(time - state.user_interaction_time().value(),
-                              mode);
-  }
-
-  state.set_site_storage_time(time);
-}
-
-void DIPSStorage::RecordInteraction(const GURL& url,
-                                    base::Time time,
-                                    DIPSCookieMode mode) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DIPSState state = Read(url);
-  if (!state.user_interaction_time()) {
-    // First interaction on site.
-    if (state.site_storage_time()) {
-      // Site previously wrote to storage. Record metric for the time delay
-      // between storage and interaction.
-      UmaHistogramTimeToInteraction(time - state.site_storage_time().value(),
-                                    mode);
-    }
-  }
-
-  // Unlike for storage, we want to know the time of the most recent user
-  // interaction, so overwrite any existing timestamp. (If interaction happened
-  // a long time ago, it may no longer be relevant.)
-  state.set_user_interaction_time(time);
-}
-
 /* static */
 std::string DIPSStorage::GetSite(const GURL& url) {
   // TODO(crbug.com/1306935): use privacy boundary instead of eTLD+1
diff --git a/chrome/browser/dips/dips_storage.h b/chrome/browser/dips/dips_storage.h
index b26f5a6..ccf53a2 100644
--- a/chrome/browser/dips/dips_storage.h
+++ b/chrome/browser/dips/dips_storage.h
@@ -11,7 +11,6 @@
 #include "base/sequence_checker.h"
 #include "base/time/time.h"
 #include "chrome/browser/dips/dips_state.h"
-#include "chrome/browser/dips/dips_utils.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 class GURL;
@@ -27,23 +26,11 @@
 
   DIPSState Read(const GURL& url);
 
-  // DIPS Helper Method Impls --------------------------------------------------
-
-  // Record that |url| wrote to storage, if it was the first such time (we
-  // currently don't care about later writes to storage.)
-  void RecordStorage(const GURL& url, base::Time time, DIPSCookieMode mode);
-  // Record that the user interacted on |url|.
-  void RecordInteraction(const GURL& url, base::Time time, DIPSCookieMode mode);
-
-  /* static */
   // Returns an opaque value representing the "privacy boundary" that the URL
   // belongs to. Currently returns eTLD+1, but this is an implementation detail
   // and will change (e.g. after adding support for First-Party Sets).
   static std::string GetSite(const GURL& url);
 
-  // Empty method intended for testing use only.
-  void DoNothing() {}
-
  private:
   friend class DIPSState;
   void Write(const DIPSState& state);
diff --git a/chrome/browser/dips/dips_storage_unittest.cc b/chrome/browser/dips/dips_storage_unittest.cc
index 2c6a824..7bc25a60 100644
--- a/chrome/browser/dips/dips_storage_unittest.cc
+++ b/chrome/browser/dips/dips_storage_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/dips/dips_storage.h"
 
+#include "base/test/simple_test_clock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
diff --git a/chrome/browser/download/download_item_model_unittest.cc b/chrome/browser/download/download_item_model_unittest.cc
index d30021f42..5646432 100644
--- a/chrome/browser/download/download_item_model_unittest.cc
+++ b/chrome/browser/download/download_item_model_unittest.cc
@@ -740,6 +740,86 @@
 
 #if !BUILDFLAG(IS_ANDROID)
 
+TEST_F(DownloadItemModelTest, InProgressOrCompletedBubbleUIInfo_V2On) {
+  SetupDownloadItemDefaults();
+
+  SetupCompletedDownloadItem(base::Hours(1));
+  DownloadUIModel::BubbleUIInfo bubble_ui_info =
+      model().GetBubbleUIInfo(/*is_download_bubble_v2=*/true);
+  std::vector<DownloadCommands::Command> quick_action_commands;
+  for (auto quick_action : bubble_ui_info.quick_actions) {
+    quick_action_commands.push_back(quick_action.command);
+  }
+  EXPECT_EQ(quick_action_commands,
+            std::vector({DownloadCommands::Command::OPEN_WHEN_COMPLETE,
+                         DownloadCommands::Command::SHOW_IN_FOLDER}));
+  EXPECT_FALSE(bubble_ui_info.primary_button_command.has_value());
+
+  Mock::VerifyAndClearExpectations(&item());
+  Mock::VerifyAndClearExpectations(&model());
+
+  ON_CALL(item(), GetState())
+      .WillByDefault(Return(download::DownloadItem::IN_PROGRESS));
+  EXPECT_CALL(item(), IsPaused()).WillRepeatedly(Return(true));
+  bubble_ui_info = model().GetBubbleUIInfo(/*is_download_bubble_v2=*/true);
+  quick_action_commands = {};
+  for (auto quick_action : bubble_ui_info.quick_actions) {
+    quick_action_commands.push_back(quick_action.command);
+  }
+  EXPECT_EQ(quick_action_commands,
+            std::vector({DownloadCommands::Command::RESUME,
+                         DownloadCommands::Command::CANCEL}));
+  EXPECT_FALSE(bubble_ui_info.primary_button_command.has_value());
+
+  Mock::VerifyAndClearExpectations(&item());
+  Mock::VerifyAndClearExpectations(&model());
+
+  EXPECT_CALL(item(), IsPaused()).WillRepeatedly(Return(false));
+  bubble_ui_info = model().GetBubbleUIInfo(/*is_download_bubble_v2=*/true);
+  quick_action_commands = {};
+  for (auto quick_action : bubble_ui_info.quick_actions) {
+    quick_action_commands.push_back(quick_action.command);
+  }
+  EXPECT_EQ(quick_action_commands,
+            std::vector({DownloadCommands::Command::PAUSE,
+                         DownloadCommands::Command::CANCEL}));
+  EXPECT_FALSE(bubble_ui_info.primary_button_command.has_value());
+}
+
+TEST_F(DownloadItemModelTest, InProgressOrCompletedBubbleUIInfo_V2Off) {
+  SetupDownloadItemDefaults();
+
+  SetupCompletedDownloadItem(base::Hours(1));
+  DownloadUIModel::BubbleUIInfo bubble_ui_info =
+      model().GetBubbleUIInfo(/*is_download_bubble_v2=*/false);
+  std::vector<DownloadCommands::Command> quick_action_commands;
+  for (auto quick_action : bubble_ui_info.quick_actions) {
+    quick_action_commands.push_back(quick_action.command);
+  }
+  EXPECT_EQ(quick_action_commands, std::vector<DownloadCommands::Command>());
+  EXPECT_FALSE(bubble_ui_info.primary_button_command.has_value());
+
+  Mock::VerifyAndClearExpectations(&item());
+  Mock::VerifyAndClearExpectations(&model());
+
+  ON_CALL(item(), GetState())
+      .WillByDefault(Return(download::DownloadItem::IN_PROGRESS));
+  EXPECT_CALL(item(), IsPaused()).WillRepeatedly(Return(true));
+  bubble_ui_info = model().GetBubbleUIInfo(/*is_download_bubble_v2=*/false);
+  EXPECT_TRUE(bubble_ui_info.quick_actions.empty());
+  EXPECT_EQ(bubble_ui_info.primary_button_command.value(),
+            DownloadCommands::Command::RESUME);
+
+  Mock::VerifyAndClearExpectations(&item());
+  Mock::VerifyAndClearExpectations(&model());
+
+  EXPECT_CALL(item(), IsPaused()).WillRepeatedly(Return(false));
+  bubble_ui_info = model().GetBubbleUIInfo(/*is_download_bubble_v2=*/false);
+  EXPECT_TRUE(bubble_ui_info.quick_actions.empty());
+  EXPECT_EQ(bubble_ui_info.primary_button_command.value(),
+            DownloadCommands::Command::CANCEL);
+}
+
 TEST_F(DownloadItemModelTest, DangerousWarningBubbleUIInfo_V2On) {
   SetupCompletedDownloadItem(base::Hours(1));
   const struct DangerTypeTestCase {
diff --git a/chrome/browser/download/download_ui_model.cc b/chrome/browser/download/download_ui_model.cc
index 8601272..25ce5d54 100644
--- a/chrome/browser/download/download_ui_model.cc
+++ b/chrome/browser/download/download_ui_model.cc
@@ -1136,36 +1136,51 @@
     case download::DOWNLOAD_DANGER_TYPE_MAX:
       break;
   }
+
+  // Add primary button/quick actions for in-progress (paused or active), and
+  // completed downloads
   bool has_progress_bar = GetState() == DownloadItem::IN_PROGRESS;
   BubbleUIInfo bubble_ui_info = DownloadUIModel::BubbleUIInfo(has_progress_bar);
   if (has_progress_bar) {
     if (IsPaused()) {
-      bubble_ui_info.AddPrimaryButton(DownloadCommands::Command::RESUME);
-      bubble_ui_info.AddQuickAction(
-          DownloadCommands::Command::RESUME,
-          l10n_util::GetStringUTF16(IDS_DOWNLOAD_BUBBLE_RESUME_QUICK_ACTION),
-          &vector_icons::kPlayArrowIcon);
+      if (is_download_bubble_v2) {
+        bubble_ui_info.AddQuickAction(
+            DownloadCommands::Command::RESUME,
+            l10n_util::GetStringUTF16(IDS_DOWNLOAD_BUBBLE_RESUME_QUICK_ACTION),
+            &vector_icons::kPlayArrowIcon);
+        bubble_ui_info.AddQuickAction(
+            DownloadCommands::Command::CANCEL,
+            l10n_util::GetStringUTF16(IDS_DOWNLOAD_BUBBLE_CANCEL_QUICK_ACTION),
+            &vector_icons::kCloseIcon);
+      } else {
+        bubble_ui_info.AddPrimaryButton(DownloadCommands::Command::RESUME);
+      }
     } else {
-      bubble_ui_info.AddPrimaryButton(DownloadCommands::Command::CANCEL);
-      bubble_ui_info.AddQuickAction(
-          DownloadCommands::Command::PAUSE,
-          l10n_util::GetStringUTF16(IDS_DOWNLOAD_BUBBLE_PAUSE_QUICK_ACTION),
-          &vector_icons::kPauseIcon);
+      if (is_download_bubble_v2) {
+        bubble_ui_info.AddQuickAction(
+            DownloadCommands::Command::PAUSE,
+            l10n_util::GetStringUTF16(IDS_DOWNLOAD_BUBBLE_PAUSE_QUICK_ACTION),
+            &vector_icons::kPauseIcon);
+        bubble_ui_info.AddQuickAction(
+            DownloadCommands::Command::CANCEL,
+            l10n_util::GetStringUTF16(IDS_DOWNLOAD_BUBBLE_CANCEL_QUICK_ACTION),
+            &vector_icons::kCloseIcon);
+      } else {
+        bubble_ui_info.AddPrimaryButton(DownloadCommands::Command::CANCEL);
+      }
     }
-    bubble_ui_info.AddQuickAction(
-        DownloadCommands::Command::CANCEL,
-        l10n_util::GetStringUTF16(IDS_DOWNLOAD_BUBBLE_CANCEL_QUICK_ACTION),
-        &vector_icons::kCloseIcon);
   } else {
-    bubble_ui_info.AddQuickAction(
-        DownloadCommands::Command::OPEN_WHEN_COMPLETE,
-        l10n_util::GetStringUTF16(IDS_DOWNLOAD_BUBBLE_OPEN_QUICK_ACTION),
-        &vector_icons::kOpenInNewIcon);
-    bubble_ui_info.AddQuickAction(
-        DownloadCommands::Command::SHOW_IN_FOLDER,
-        l10n_util::GetStringUTF16(
-            IDS_DOWNLOAD_BUBBLE_SHOW_IN_FOLDER_QUICK_ACTION),
-        &vector_icons::kFolderIcon);
+    if (is_download_bubble_v2) {
+      bubble_ui_info.AddQuickAction(
+          DownloadCommands::Command::OPEN_WHEN_COMPLETE,
+          l10n_util::GetStringUTF16(IDS_DOWNLOAD_BUBBLE_OPEN_QUICK_ACTION),
+          &vector_icons::kOpenInNewIcon);
+      bubble_ui_info.AddQuickAction(
+          DownloadCommands::Command::SHOW_IN_FOLDER,
+          l10n_util::GetStringUTF16(
+              IDS_DOWNLOAD_BUBBLE_SHOW_IN_FOLDER_QUICK_ACTION),
+          &vector_icons::kFolderIcon);
+    }
   }
   return bubble_ui_info;
 }
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/mock_secure_enclave_client.h b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/mock_secure_enclave_client.h
index cd77c6c5..f4d10ba 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/mock_secure_enclave_client.h
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/mock_secure_enclave_client.h
@@ -25,14 +25,14 @@
   ~MockSecureEnclaveClient() override;
 
   MOCK_METHOD(base::ScopedCFTypeRef<SecKeyRef>,
-              CreateTemporaryKey,
+              CreatePermanentKey,
               (),
               (override));
   MOCK_METHOD(base::ScopedCFTypeRef<SecKeyRef>,
               CopyStoredKey,
               (KeyType),
               (override));
-  MOCK_METHOD(bool, MoveTemporaryKeyToPermanent, (), (override));
+  MOCK_METHOD(bool, UpdateStoredKeyLabel, (KeyType, KeyType), (override));
   MOCK_METHOD(bool, DeleteKey, (KeyType), (override));
   MOCK_METHOD(bool,
               GetStoredKeyLabel,
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client.h b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client.h
index dbaa1f4..ec28ba7d 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client.h
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client.h
@@ -36,16 +36,18 @@
 
   static std::unique_ptr<SecureEnclaveClient> Create();
 
-  // Creates a new Secure Enclave private key with a temporary key label.
-  virtual base::ScopedCFTypeRef<SecKeyRef> CreateTemporaryKey() = 0;
+  // Creates a new Secure Enclave private key with a permanent key label.
+  virtual base::ScopedCFTypeRef<SecKeyRef> CreatePermanentKey() = 0;
 
   // Queries for the secure key using its label determined by the key `type`.
   // Returns the secure key reference or a nullptr if no key was found.
   virtual base::ScopedCFTypeRef<SecKeyRef> CopyStoredKey(KeyType type) = 0;
 
-  // Updates the private key label from the temporary key label to the
-  // non-temporary label.
-  virtual bool MoveTemporaryKeyToPermanent() = 0;
+  // Deletes any key stored in `new_key_type` and updates the private key
+  // storage in `current_key_type` to `new_key_type` and modifies the key label
+  // to reflect this change.
+  virtual bool UpdateStoredKeyLabel(KeyType current_key_type,
+                                    KeyType new_key_type) = 0;
 
   // Queries for the secure key using its label determined by the key `type`
   // and deletes it from the secure enclave.
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_impl.h b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_impl.h
index 2f4e5e5..1d544e4 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_impl.h
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_impl.h
@@ -27,9 +27,10 @@
   ~SecureEnclaveClientImpl() override;
 
   // SecureEnclaveClient:
-  base::ScopedCFTypeRef<SecKeyRef> CreateTemporaryKey() override;
+  base::ScopedCFTypeRef<SecKeyRef> CreatePermanentKey() override;
   base::ScopedCFTypeRef<SecKeyRef> CopyStoredKey(KeyType type) override;
-  bool MoveTemporaryKeyToPermanent() override;
+  bool UpdateStoredKeyLabel(KeyType current_key_type,
+                            KeyType new_key_type) override;
   bool DeleteKey(KeyType type) override;
   bool GetStoredKeyLabel(KeyType type, std::vector<uint8_t>& output) override;
   bool ExportPublicKey(SecKeyRef key, std::vector<uint8_t>& output) override;
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_impl.mm b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_impl.mm
index 367af64..9ee02daf 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_impl.mm
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_impl.mm
@@ -69,7 +69,8 @@
 #pragma clang diagnostic pop
 
 // Creates and returns the secure enclave private key attributes used
-// for key creation.
+// for key creation. These key attributes represent the key created in
+// the permanent key location.
 base::ScopedCFTypeRef<CFMutableDictionaryRef> CreateAttributesForKey() {
   auto access_ref = CreateACL();
   if (!access_ref)
@@ -84,9 +85,9 @@
   CFDictionarySetValue(attributes, kSecAttrKeyType,
                        kSecAttrKeyTypeECSECPrimeRandom);
   CFDictionarySetValue(attributes, kSecAttrKeySizeInBits, @256);
-  CFDictionarySetValue(attributes, kSecAttrLabel,
-                       base::SysUTF8ToCFStringRef(
-                           constants::kTemporaryDeviceTrustSigningKeyLabel));
+  CFDictionarySetValue(
+      attributes, kSecAttrLabel,
+      base::SysUTF8ToCFStringRef(constants::kDeviceTrustSigningKeyLabel));
 
   base::ScopedCFTypeRef<CFMutableDictionaryRef> private_key_params(
       CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
@@ -123,14 +124,14 @@
 
 SecureEnclaveClientImpl::~SecureEnclaveClientImpl() = default;
 
-base::ScopedCFTypeRef<SecKeyRef> SecureEnclaveClientImpl::CreateTemporaryKey() {
+base::ScopedCFTypeRef<SecKeyRef> SecureEnclaveClientImpl::CreatePermanentKey() {
   auto attributes = CreateAttributesForKey();
   if (!attributes)
     return base::ScopedCFTypeRef<SecKeyRef>();
 
-  // Deletes a temporary Secure Enclave key if it exists from a previous
+  // Deletes a permanent Secure Enclave key if it exists from a previous
   // key rotation.
-  DeleteKey(KeyType::kTemporary);
+  DeleteKey(KeyType::kPermanent);
   return helper_->CreateSecureKey(attributes);
 }
 
@@ -139,19 +140,23 @@
   return helper_->CopyKey(CreateQueryForKey(type));
 }
 
-bool SecureEnclaveClientImpl::MoveTemporaryKeyToPermanent() {
-  // Deletes an old Secure Enclave key if it exists.
-  DeleteKey(SecureEnclaveClient::KeyType::kPermanent);
+bool SecureEnclaveClientImpl::UpdateStoredKeyLabel(KeyType current_key_type,
+                                                   KeyType new_key_type) {
+  // Deletes the `new_key_type` label if it exists in the keychain.
+  DeleteKey(new_key_type);
 
   base::ScopedCFTypeRef<CFMutableDictionaryRef> attributes_to_update(
       CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
                                 &kCFTypeDictionaryKeyCallBacks,
                                 &kCFTypeDictionaryValueCallBacks));
-  CFDictionarySetValue(
-      attributes_to_update, kSecAttrLabel,
-      base::SysUTF8ToCFStringRef(constants::kDeviceTrustSigningKeyLabel));
+  auto label = GetLabelFromKeyType(new_key_type);
+  if (label.empty())
+    return false;
 
-  return helper_->Update(CreateQueryForKey(KeyType::kTemporary),
+  CFDictionarySetValue(attributes_to_update, kSecAttrLabel,
+                       base::SysUTF8ToCFStringRef(label));
+
+  return helper_->Update(CreateQueryForKey(current_key_type),
                          attributes_to_update);
 }
 
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_unittest.mm b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_unittest.mm
index 4453395..4f88769 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_unittest.mm
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_client_unittest.mm
@@ -36,7 +36,7 @@
     CreateAndSetTestKey();
   }
 
-  // Creates a temporary key.
+  // Creates a test key.
   void CreateAndSetTestKey() {
     base::ScopedCFTypeRef<CFMutableDictionaryRef> test_attributes(
         CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
@@ -71,27 +71,25 @@
   base::ScopedCFTypeRef<SecKeyRef> test_key_;
 };
 
-// Tests that the CreateTemporaryKey method invokes both the SE helper's
+// Tests that the CreatePermanentKey method invokes both the SE helper's
 // Delete and CreateSecureKey method and that the key attributes are set
 // correctly.
 TEST_F(SecureEnclaveClientTest, CreateKey) {
   EXPECT_CALL(*mock_secure_enclave_helper_, Delete(_))
       .Times(1)
       .WillOnce([this](CFDictionaryRef query) {
-        VerifyQuery(query,
-                    base::SysUTF8ToCFStringRef(
-                        constants::kTemporaryDeviceTrustSigningKeyLabel));
+        VerifyQuery(query, base::SysUTF8ToCFStringRef(
+                               constants::kDeviceTrustSigningKeyLabel));
         return true;
       });
 
   EXPECT_CALL(*mock_secure_enclave_helper_, CreateSecureKey(_))
       .Times(1)
       .WillOnce([this](CFDictionaryRef attributes) {
-        EXPECT_TRUE(
-            CFEqual(base::SysUTF8ToCFStringRef(
-                        constants::kTemporaryDeviceTrustSigningKeyLabel),
-                    base::mac::GetValueFromDictionary<CFStringRef>(
-                        attributes, kSecAttrLabel)));
+        EXPECT_TRUE(CFEqual(
+            base::SysUTF8ToCFStringRef(constants::kDeviceTrustSigningKeyLabel),
+            base::mac::GetValueFromDictionary<CFStringRef>(attributes,
+                                                           kSecAttrLabel)));
         EXPECT_TRUE(CFEqual(kSecAttrKeyTypeECSECPrimeRandom,
                             base::mac::GetValueFromDictionary<CFStringRef>(
                                 attributes, kSecAttrKeyType)));
@@ -109,7 +107,7 @@
                                 private_key_attributes, kSecAttrTokenID)));
         return test_key_;
       });
-  EXPECT_EQ(secure_enclave_client_->CreateTemporaryKey(), test_key_);
+  EXPECT_EQ(secure_enclave_client_->CreatePermanentKey(), test_key_);
 }
 
 // Tests when the CopyStoredKey method invokes the SE helper's CopyKey method
@@ -140,9 +138,32 @@
       SecureEnclaveClient::KeyType::kTemporary));
 }
 
-// Tests that the MoveTemporaryKeyToPermanent method invokes the SE helper's
-// Update method and that the key attributes and query are set correctly.
-TEST_F(SecureEnclaveClientTest, MoveTemporaryKeyToPermanent) {
+// Tests that the UpdateStoredKeyLabel method invokes the SE helper's
+// Update method and that the key attributes and query are set correctly for
+// the permanent key label being updated to the temporary key label.
+TEST_F(SecureEnclaveClientTest, UpdateStoredKeyLabel_PermanentToTemporary) {
+  EXPECT_CALL(*mock_secure_enclave_helper_, Update(_, _))
+      .Times(1)
+      .WillOnce(
+          [this](CFDictionaryRef query, CFDictionaryRef attribute_to_update) {
+            EXPECT_TRUE(
+                CFEqual(base::SysUTF8ToCFStringRef(
+                            constants::kTemporaryDeviceTrustSigningKeyLabel),
+                        base::mac::GetValueFromDictionary<CFStringRef>(
+                            attribute_to_update, kSecAttrLabel)));
+            VerifyQuery(query, base::SysUTF8ToCFStringRef(
+                                   constants::kDeviceTrustSigningKeyLabel));
+            return true;
+          });
+  EXPECT_TRUE(secure_enclave_client_->UpdateStoredKeyLabel(
+      SecureEnclaveClient::KeyType::kPermanent,
+      SecureEnclaveClient::KeyType::kTemporary));
+}
+
+// Tests that the UpdateStoredKeyLabel method invokes the SE helper's
+// Update method and that the key attributes and query are set correctly for
+// the temporary key label being updated to the permanent key label.
+TEST_F(SecureEnclaveClientTest, UpdateStoredKeyLabel_TemporaryToPermanent) {
   EXPECT_CALL(*mock_secure_enclave_helper_, Update(_, _))
       .Times(1)
       .WillOnce([this](CFDictionaryRef query,
@@ -156,7 +177,9 @@
                         constants::kTemporaryDeviceTrustSigningKeyLabel));
         return true;
       });
-  EXPECT_TRUE(secure_enclave_client_->MoveTemporaryKeyToPermanent());
+  EXPECT_TRUE(secure_enclave_client_->UpdateStoredKeyLabel(
+      SecureEnclaveClient::KeyType::kTemporary,
+      SecureEnclaveClient::KeyType::kPermanent));
 }
 
 // Tests that the DeleteKey method invokes the SE helper's Delete method
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_signing_key.cc b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_signing_key.cc
index fe73436b..aca89f38 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_signing_key.cc
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_signing_key.cc
@@ -129,7 +129,7 @@
 SecureEnclaveSigningKeyProvider::GenerateSigningKeySlowly(
     base::span<const crypto::SignatureVerifier::SignatureAlgorithm>
         acceptable_algorithms) {
-  if (provider_key_type_ != SecureEnclaveClient::KeyType::kTemporary)
+  if (provider_key_type_ != SecureEnclaveClient::KeyType::kPermanent)
     return nullptr;
 
   auto algo = SelectAlgorithm(acceptable_algorithms);
@@ -139,7 +139,7 @@
   DCHECK_EQ(crypto::SignatureVerifier::ECDSA_SHA256, *algo);
 
   auto client = SecureEnclaveClient::Create();
-  auto key = client->CreateTemporaryKey();
+  auto key = client->CreatePermanentKey();
   if (!key)
     return nullptr;
 
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_signing_key_unittest.mm b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_signing_key_unittest.mm
index ac0bd94..bf8b529 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_signing_key_unittest.mm
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/mac/secure_enclave_signing_key_unittest.mm
@@ -40,7 +40,7 @@
   }
 
  protected:
-  // Creates a temporary key.
+  // Creates a test key.
   void CreateTestKey() {
     base::ScopedCFTypeRef<CFMutableDictionaryRef> test_attributes(
         CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
@@ -65,8 +65,8 @@
   // Sets the unexportable key using the test key.
   void SetUnexportableKey() {
     auto provider = SecureEnclaveSigningKeyProvider(
-        SecureEnclaveClient::KeyType::kTemporary);
-    EXPECT_CALL(*mock_secure_enclave_client_, CreateTemporaryKey())
+        SecureEnclaveClient::KeyType::kPermanent);
+    EXPECT_CALL(*mock_secure_enclave_client_, CreatePermanentKey())
         .Times(1)
         .WillOnce([this]() { return test_key_; });
     auto acceptable_algorithms = {crypto::SignatureVerifier::ECDSA_SHA256};
@@ -79,15 +79,16 @@
 };
 
 // Tests that the GenerateSigningKeySlowly method invokes the SE client's
-// CreateTemporaryKey method only when the provider is a temporary key provider.
+// CreatePermanentKey method only when the provider is a permanent key provider.
 TEST_F(SecureEnclaveSigningKeyTest, GenerateSigningKeySlowly) {
   auto acceptable_algorithms = {crypto::SignatureVerifier::ECDSA_SHA256};
 
   InSequence s;
 
   auto provider =
-      SecureEnclaveSigningKeyProvider(SecureEnclaveClient::KeyType::kTemporary);
-  EXPECT_CALL(*mock_secure_enclave_client_, CreateTemporaryKey())
+      SecureEnclaveSigningKeyProvider(SecureEnclaveClient::KeyType::kPermanent);
+
+  EXPECT_CALL(*mock_secure_enclave_client_, CreatePermanentKey())
       .Times(1)
       .WillOnce([this]() { return test_key_; });
   auto unexportable_key =
@@ -97,8 +98,8 @@
             unexportable_key->Algorithm());
 
   provider =
-      SecureEnclaveSigningKeyProvider(SecureEnclaveClient::KeyType::kPermanent);
-  EXPECT_CALL(*mock_secure_enclave_client_, CreateTemporaryKey()).Times(0);
+      SecureEnclaveSigningKeyProvider(SecureEnclaveClient::KeyType::kTemporary);
+  EXPECT_CALL(*mock_secure_enclave_client_, CreatePermanentKey()).Times(0);
   unexportable_key = provider.GenerateSigningKeySlowly(acceptable_algorithms);
   EXPECT_FALSE(unexportable_key);
 }
@@ -186,12 +187,12 @@
 }
 
 // Tests that the GetWrappedKey method invokes the SE client's
-// GetStoredKeyLabel method and that the wrapped key label is the temporary
-// key label since the SecureEnclaveSigningKey is currently a temporary key.
+// GetStoredKeyLabel method and that the wrapped key label is the permanent
+// key label since the SecureEnclaveSigningKey is currently a permanent key.
 TEST_F(SecureEnclaveSigningKeyTest, GetWrappedKey) {
   SetUnexportableKey();
   EXPECT_CALL(*mock_secure_enclave_client_,
-              GetStoredKeyLabel(SecureEnclaveClient::KeyType::kTemporary, _))
+              GetStoredKeyLabel(SecureEnclaveClient::KeyType::kPermanent, _))
       .WillOnce(
           [](SecureEnclaveClient::KeyType type, std::vector<uint8_t>& output) {
             std::string label =
@@ -202,7 +203,7 @@
             return true;
           });
   auto wrapped = key_->GetWrappedKey();
-  EXPECT_EQ(constants::kTemporaryDeviceTrustSigningKeyLabel,
+  EXPECT_EQ(constants::kDeviceTrustSigningKeyLabel,
             std::string(wrapped.begin(), wrapped.end()));
 }
 
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/key_persistence_delegate.h b/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/key_persistence_delegate.h
index 997b583..4ceda4f 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/key_persistence_delegate.h
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/key_persistence_delegate.h
@@ -46,6 +46,12 @@
   // BPKUR::CHROME_BROWSER_OS_KEY is created if available. If neither are
   // available, a nullptr is returned.
   virtual std::unique_ptr<SigningKeyPair> CreateKeyPair() = 0;
+
+  // Deletes the signing key in the temporary key storage after a successful
+  // key rotation. This method is only overridden in Mac platforms since signing
+  // key rollback is handled in the StoreKeyPair method in Linux and Windows
+  // platforms.
+  virtual void CleanupTemporaryKeyData() {}
 };
 
 }  // namespace enterprise_connectors
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/mac_key_persistence_delegate.cc b/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/mac_key_persistence_delegate.cc
index d5d0517..2923b17 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/mac_key_persistence_delegate.cc
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/mac_key_persistence_delegate.cc
@@ -35,4 +35,8 @@
   return nullptr;
 }
 
+void MacKeyPersistenceDelegate::CleanupTemporaryKeyData() {
+  NOTIMPLEMENTED();
+}
+
 }  // namespace enterprise_connectors
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/mac_key_persistence_delegate.h b/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/mac_key_persistence_delegate.h
index 1ada540..c093e1fb 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/mac_key_persistence_delegate.h
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/mac_key_persistence_delegate.h
@@ -25,6 +25,7 @@
                     std::vector<uint8_t> wrapped) override;
   std::unique_ptr<SigningKeyPair> LoadKeyPair() override;
   std::unique_ptr<SigningKeyPair> CreateKeyPair() override;
+  void CleanupTemporaryKeyData() override;
 };
 
 }  // namespace enterprise_connectors
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/mock_key_persistence_delegate.h b/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/mock_key_persistence_delegate.h
index f8f76d2..840eb1f 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/mock_key_persistence_delegate.h
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/persistence/mock_key_persistence_delegate.h
@@ -32,6 +32,7 @@
               CreateKeyPair,
               (),
               (override));
+  MOCK_METHOD(void, CleanupTemporaryKeyData, (), (override));
 };
 
 }  // namespace test
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/installer/key_rotation_manager_impl.cc b/chrome/browser/enterprise/connectors/device_trust/key_management/installer/key_rotation_manager_impl.cc
index 189c710..89d9f33 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/installer/key_rotation_manager_impl.cc
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/installer/key_rotation_manager_impl.cc
@@ -181,6 +181,7 @@
     std::move(result_callback).Run(false);
     return;
   }
+  persistence_delegate_->CleanupTemporaryKeyData();
   key_pair_ = std::move(new_key_pair);
   RecordRotationStatus(nonce, RotationStatus::SUCCESS);
   std::move(result_callback).Run(true);
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/installer/key_rotation_manager_unittest.cc b/chrome/browser/enterprise/connectors/device_trust/key_management/installer/key_rotation_manager_unittest.cc
index ec43a00..20961f04 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/installer/key_rotation_manager_unittest.cc
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/installer/key_rotation_manager_unittest.cc
@@ -116,6 +116,7 @@
             captured_body = body;
             std::move(callback).Run(kSuccessCode);
           }));
+  EXPECT_CALL(*mock_persistence_delegate, CleanupTemporaryKeyData());
 
   auto manager = KeyRotationManager::CreateForTesting(
       std::move(mock_network_delegate), std::move(mock_persistence_delegate));
@@ -171,6 +172,7 @@
                           base::OnceCallback<void(int)> callback) {
         std::move(callback).Run(kSuccessCode);
       }));
+  EXPECT_CALL(*mock_persistence_delegate, CleanupTemporaryKeyData());
 
   auto manager = KeyRotationManager::CreateForTesting(
       std::move(mock_network_delegate), std::move(mock_persistence_delegate));
@@ -213,6 +215,7 @@
                           base::OnceCallback<void(int)> callback) {
         std::move(callback).Run(kSuccessCode);
       }));
+  EXPECT_CALL(*mock_persistence_delegate, CleanupTemporaryKeyData());
 
   auto manager = KeyRotationManager::CreateForTesting(
       std::move(mock_network_delegate), std::move(mock_persistence_delegate));
@@ -445,6 +448,7 @@
                           base::OnceCallback<void(int)> callback) {
         std::move(callback).Run(kSuccessCode);
       }));
+  EXPECT_CALL(*mock_persistence_delegate, CleanupTemporaryKeyData());
 
   auto manager = KeyRotationManager::CreateForTesting(
       std::move(mock_network_delegate), std::move(mock_persistence_delegate));
diff --git a/chrome/browser/extensions/api/commands/command_service.cc b/chrome/browser/extensions/api/commands/command_service.cc
index 2c95d52a..87ea997b 100644
--- a/chrome/browser/extensions/api/commands/command_service.cc
+++ b/chrome/browser/extensions/api/commands/command_service.cc
@@ -554,23 +554,19 @@
 void CommandService::RemoveDefunctExtensionSuggestedCommandPrefs(
     const Extension* extension) {
   ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
-  const base::DictionaryValue* current_prefs = nullptr;
-  extension_prefs->ReadPrefAsDictionary(extension->id(),
-                                        kCommands,
-                                        &current_prefs);
+  const base::Value::Dict* current_prefs =
+      extension_prefs->ReadPrefAsDict(extension->id(), kCommands);
 
   if (current_prefs) {
-    std::unique_ptr<base::DictionaryValue> suggested_key_prefs =
-        base::DictionaryValue::From(
-            base::Value::ToUniquePtrValue(current_prefs->Clone()));
+    base::Value::Dict suggested_key_prefs = current_prefs->Clone();
+
     const CommandMap* named_commands =
         CommandsInfo::GetNamedCommands(extension);
 
     const Command* browser_action_command =
         CommandsInfo::GetBrowserActionCommand(extension);
-    for (base::DictionaryValue::Iterator it(*current_prefs);
-         !it.IsAtEnd(); it.Advance()) {
-      if (it.key() == manifest_values::kBrowserActionCommandEvent) {
+    for (const auto [key, _] : *current_prefs) {
+      if (key == manifest_values::kBrowserActionCommandEvent) {
         // The browser action command may be defaulted to an unassigned
         // accelerator if a browser action is specified by the extension but a
         // keybinding is not declared. See
@@ -578,22 +574,23 @@
         if (!browser_action_command ||
             browser_action_command->accelerator().key_code() ==
                 ui::VKEY_UNKNOWN) {
-          suggested_key_prefs->RemoveKey(it.key());
+          suggested_key_prefs.Remove(key);
         }
-      } else if (it.key() == manifest_values::kPageActionCommandEvent) {
+      } else if (key == manifest_values::kPageActionCommandEvent) {
         if (!CommandsInfo::GetPageActionCommand(extension))
-          suggested_key_prefs->RemoveKey(it.key());
-      } else if (it.key() == manifest_values::kActionCommandEvent) {
+          suggested_key_prefs.Remove(key);
+      } else if (key == manifest_values::kActionCommandEvent) {
         if (!CommandsInfo::GetActionCommand(extension))
-          suggested_key_prefs->RemoveKey(it.key());
+          suggested_key_prefs.Remove(key);
       } else if (named_commands) {
-        if (named_commands->find(it.key()) == named_commands->end())
-          suggested_key_prefs->RemoveKey(it.key());
+        if (named_commands->find(key) == named_commands->end())
+          suggested_key_prefs.Remove(key);
       }
     }
 
-    extension_prefs->UpdateExtensionPref(extension->id(), kCommands,
-                                         std::move(suggested_key_prefs));
+    extension_prefs->UpdateExtensionPref(
+        extension->id(), kCommands,
+        std::make_unique<base::Value>(std::move(suggested_key_prefs)));
   }
 }
 
diff --git a/chrome/browser/extensions/api/proxy/proxy_apitest.cc b/chrome/browser/extensions/api/proxy/proxy_apitest.cc
index 419d674..599925fd 100644
--- a/chrome/browser/extensions/api/proxy/proxy_apitest.cc
+++ b/chrome/browser/extensions/api/proxy/proxy_apitest.cc
@@ -46,8 +46,10 @@
     ASSERT_TRUE(pref != NULL);
     EXPECT_TRUE(pref->IsExtensionControlled());
 
+    // TODO(https://crbug.com/1348219) This should call
+    // `PrefService::GetValueDict`.
     ProxyConfigDictionary dict(
-        pref_service->GetDictionary(proxy_config::prefs::kProxy)->Clone());
+        pref_service->GetValue(proxy_config::prefs::kProxy).Clone());
 
     ProxyPrefs::ProxyMode mode;
     ASSERT_TRUE(dict.GetMode(&mode));
diff --git a/chrome/browser/extensions/api/scripting/scripting_api.cc b/chrome/browser/extensions/api/scripting/scripting_api.cc
index 08ba987..23d0a10 100644
--- a/chrome/browser/extensions/api/scripting/scripting_api.cc
+++ b/chrome/browser/extensions/api/scripting/scripting_api.cc
@@ -682,10 +682,12 @@
           : mojom::RunLocation::kDocumentIdle;
   script_executor->ExecuteScript(
       mojom::HostID(mojom::HostID::HostType::kExtensions, extension()->id()),
-      mojom::CodeInjection::NewJs(
-          mojom::JSInjection::New(std::move(sources), execution_world,
-                                  /*wants_result=*/true, user_gesture(),
-                                  /*wait_for_promise=*/true)),
+      mojom::CodeInjection::NewJs(mojom::JSInjection::New(
+          std::move(sources), execution_world,
+          blink::mojom::WantResultOption::kWantResult,
+          user_gesture() ? blink::mojom::UserActivationOption::kActivate
+                         : blink::mojom::UserActivationOption::kDoNotActivate,
+          blink::mojom::PromiseResultOption::kAwait)),
       frame_scope, frame_ids, ScriptExecutor::MATCH_ABOUT_BLANK, run_location,
       ScriptExecutor::DEFAULT_PROCESS,
       /* webview_src */ GURL(),
diff --git a/chrome/browser/extensions/extension_web_ui.cc b/chrome/browser/extensions/extension_web_ui.cc
index bec13da5..4c136ca 100644
--- a/chrome/browser/extensions/extension_web_ui.cc
+++ b/chrome/browser/extensions/extension_web_ui.cc
@@ -355,13 +355,11 @@
   DCHECK(url.SchemeIs(content::kChromeUIScheme));
 
   Profile* profile = Profile::FromBrowserContext(browser_context);
-  const base::Value* overrides = profile->GetPrefs()->GetDictionary(
-      ExtensionWebUI::kExtensionURLOverrides);
+  const base::Value::Dict& overrides =
+      profile->GetPrefs()->GetValueDict(ExtensionWebUI::kExtensionURLOverrides);
 
-  if (!overrides)
-    return {};  // No overrides present for this host.
-
-  const base::Value* url_list = overrides->FindListPath(url.host_piece());
+  const base::Value::List* url_list =
+      overrides.FindListByDottedPath(url.host_piece());
   if (!url_list)
     return {};  // No overrides present for this host.
 
@@ -374,7 +372,7 @@
   std::vector<GURL> component_overrides;
 
   // Iterate over the URL list looking for suitable overrides.
-  for (const auto& value : url_list->GetListDeprecated()) {
+  for (const auto& value : *url_list) {
     GURL override_url;
     const Extension* extension = nullptr;
     if (!ValidateOverrideURL(&value, url, extensions, &override_url,
diff --git a/chrome/browser/extensions/script_executor_browsertest.cc b/chrome/browser/extensions/script_executor_browsertest.cc
index 12a395a..b05581e 100644
--- a/chrome/browser/extensions/script_executor_browsertest.cc
+++ b/chrome/browser/extensions/script_executor_browsertest.cc
@@ -133,8 +133,9 @@
       mojom::HostID(mojom::HostID::HostType::kExtensions, extension->id()),
       mojom::CodeInjection::NewJs(mojom::JSInjection::New(
           std::move(sources), mojom::ExecutionWorld::kMain,
-          true /* wants_result */, false /* user_gesture */,
-          true /* wait_for_promise */)),
+          blink::mojom::WantResultOption::kWantResult,
+          blink::mojom::UserActivationOption::kDoNotActivate,
+          blink::mojom::PromiseResultOption::kAwait)),
       ScriptExecutor::SPECIFIED_FRAMES, {ExtensionApiFrameIdMap::kTopFrameId},
       ScriptExecutor::DONT_MATCH_ABOUT_BLANK, mojom::RunLocation::kDocumentIdle,
       ScriptExecutor::DEFAULT_PROCESS, GURL() /* webview_src */,
@@ -181,8 +182,9 @@
       mojom::HostID(mojom::HostID::HostType::kExtensions, extension->id()),
       mojom::CodeInjection::NewJs(mojom::JSInjection::New(
           std::move(sources), mojom::ExecutionWorld::kIsolated,
-          true /* wants_result */, false /* user_gesture */,
-          true /* wait_for_promise */)),
+          blink::mojom::WantResultOption::kWantResult,
+          blink::mojom::UserActivationOption::kDoNotActivate,
+          blink::mojom::PromiseResultOption::kAwait)),
       ScriptExecutor::SPECIFIED_FRAMES, {ExtensionApiFrameIdMap::kTopFrameId},
       ScriptExecutor::DONT_MATCH_ABOUT_BLANK, mojom::RunLocation::kDocumentIdle,
       ScriptExecutor::DEFAULT_PROCESS, GURL() /* webview_src */,
@@ -236,8 +238,9 @@
       mojom::HostID(mojom::HostID::HostType::kExtensions, extension->id()),
       mojom::CodeInjection::NewJs(mojom::JSInjection::New(
           std::move(sources), mojom::ExecutionWorld::kIsolated,
-          true /* wants_result */, false /* user_gesture */,
-          true /* wait_for_promise */)),
+          blink::mojom::WantResultOption::kWantResult,
+          blink::mojom::UserActivationOption::kDoNotActivate,
+          blink::mojom::PromiseResultOption::kAwait)),
       ScriptExecutor::SPECIFIED_FRAMES, {ExtensionApiFrameIdMap::kTopFrameId},
       ScriptExecutor::DONT_MATCH_ABOUT_BLANK, mojom::RunLocation::kDocumentIdle,
       ScriptExecutor::DEFAULT_PROCESS, GURL() /* webview_src */,
@@ -301,8 +304,9 @@
         mojom::HostID(mojom::HostID::HostType::kExtensions, extension->id()),
         mojom::CodeInjection::NewJs(mojom::JSInjection::New(
             std::move(sources), mojom::ExecutionWorld::kIsolated,
-            true /* wants_result */, false /* user_gesture */,
-            true /* wait_for_promise */)),
+            blink::mojom::WantResultOption::kWantResult,
+            blink::mojom::UserActivationOption::kDoNotActivate,
+            blink::mojom::PromiseResultOption::kAwait)),
         ScriptExecutor::SPECIFIED_FRAMES, {ExtensionApiFrameIdMap::kTopFrameId},
         ScriptExecutor::DONT_MATCH_ABOUT_BLANK,
         mojom::RunLocation::kDocumentIdle, ScriptExecutor::DEFAULT_PROCESS,
@@ -330,8 +334,9 @@
         mojom::HostID(mojom::HostID::HostType::kExtensions, extension->id()),
         mojom::CodeInjection::NewJs(mojom::JSInjection::New(
             std::move(sources), mojom::ExecutionWorld::kIsolated,
-            true /* wants_result */, false /* user_gesture */,
-            false /* wait_for_promise */)),
+            blink::mojom::WantResultOption::kWantResult,
+            blink::mojom::UserActivationOption::kDoNotActivate,
+            blink::mojom::PromiseResultOption::kDoNotWait)),
         ScriptExecutor::SPECIFIED_FRAMES, {ExtensionApiFrameIdMap::kTopFrameId},
         ScriptExecutor::DONT_MATCH_ABOUT_BLANK,
         mojom::RunLocation::kDocumentIdle, ScriptExecutor::DEFAULT_PROCESS,
@@ -416,8 +421,9 @@
         mojom::HostID(mojom::HostID::HostType::kExtensions, extension->id()),
         mojom::CodeInjection::NewJs(mojom::JSInjection::New(
             std::move(sources), mojom::ExecutionWorld::kIsolated,
-            true /* wants_result */, false /* user_gesture */,
-            true /* wait_for_promise */)),
+            blink::mojom::WantResultOption::kWantResult,
+            blink::mojom::UserActivationOption::kDoNotActivate,
+            blink::mojom::PromiseResultOption::kAwait)),
         ScriptExecutor::SPECIFIED_FRAMES, {frame1_id, frame2_id},
         ScriptExecutor::DONT_MATCH_ABOUT_BLANK,
         mojom::RunLocation::kDocumentIdle, ScriptExecutor::DEFAULT_PROCESS,
@@ -440,8 +446,9 @@
         mojom::HostID(mojom::HostID::HostType::kExtensions, extension->id()),
         mojom::CodeInjection::NewJs(mojom::JSInjection::New(
             std::move(sources), mojom::ExecutionWorld::kIsolated,
-            true /* wants_result */, false /* user_gesture */,
-            true /* wait_for_promise */)),
+            blink::mojom::WantResultOption::kWantResult,
+            blink::mojom::UserActivationOption::kDoNotActivate,
+            blink::mojom::PromiseResultOption::kAwait)),
         ScriptExecutor::INCLUDE_SUB_FRAMES, {frame1_id, frame2_id},
         ScriptExecutor::DONT_MATCH_ABOUT_BLANK,
         mojom::RunLocation::kDocumentIdle, ScriptExecutor::DEFAULT_PROCESS,
@@ -473,8 +480,9 @@
         mojom::HostID(mojom::HostID::HostType::kExtensions, extension->id()),
         mojom::CodeInjection::NewJs(mojom::JSInjection::New(
             std::move(sources), mojom::ExecutionWorld::kIsolated,
-            true /* wants_result */, false /* user_gesture */,
-            true /* wait_for_promise */)),
+            blink::mojom::WantResultOption::kWantResult,
+            blink::mojom::UserActivationOption::kDoNotActivate,
+            blink::mojom::PromiseResultOption::kAwait)),
         ScriptExecutor::SPECIFIED_FRAMES,
         {frame1_id, frame2_id, kNonExistentFrameId},
         ScriptExecutor::DONT_MATCH_ABOUT_BLANK,
@@ -499,8 +507,9 @@
         mojom::HostID(mojom::HostID::HostType::kExtensions, extension->id()),
         mojom::CodeInjection::NewJs(mojom::JSInjection::New(
             std::move(sources), mojom::ExecutionWorld::kIsolated,
-            true /* wants_result */, false /* user_gesture */,
-            true /* wait_for_promise */)),
+            blink::mojom::WantResultOption::kWantResult,
+            blink::mojom::UserActivationOption::kDoNotActivate,
+            blink::mojom::PromiseResultOption::kAwait)),
         ScriptExecutor::SPECIFIED_FRAMES, {kNonExistentFrameId},
         ScriptExecutor::DONT_MATCH_ABOUT_BLANK,
         mojom::RunLocation::kDocumentIdle, ScriptExecutor::DEFAULT_PROCESS,
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 1a70ed48..4ae2e3f 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -791,7 +791,7 @@
   {
     "name": "canvas-oop-rasterization",
     "owners": [ "junov", "vasilyt" ],
-    "expiry_milestone": 105
+    "expiry_milestone": 110
   },
   {
     "name": "cct-brand-transparency",
@@ -5421,12 +5421,12 @@
   {
     "name": "read-later",
     "owners": [ "chrome-desktop-ui-sea@google.com", "corising", "wylieb", "chrome-collections@google.com" ],
-    "expiry_milestone": 105
+    "expiry_milestone": 110
   },
   {
     "name": "read-later-reminder-notification",
     "owners": [ "wylieb" ],
-    "expiry_milestone": 102
+    "expiry_milestone": 110
   },
   {
     "name": "read-printer-capabilities-with-xps",
@@ -5463,6 +5463,11 @@
     "expiry_milestone": 112
   },
   {
+    "name": "reduce-gpu-priority-on-background",
+    "owners": [ "boliu" ],
+    "expiry_milestone": 108
+  },
+  {
     "name": "reduce-horizontal-fling-velocity",
     "owners": [ "flackr", "input-dev" ],
     "expiry_milestone": 95
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index db30769..2381b58 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -3707,6 +3707,12 @@
     "Enables showing UI which allows for easy reverting of the decision to "
     "never save passwords on a certain webiste";
 
+const char kReduceGpuPriorityOnBackgroundName[] =
+    "Reduce GPU process priority when in background";
+const char kReduceGpuPriorityOnBackgroundDescription[] =
+    "Enable to reduce GPU process priority when Chrome is the background. "
+    "This hints to Android OS to kill GPU process first to free memory.";
+
 const char kReengagementNotificationName[] =
     "Enable re-engagement notifications";
 const char kReengagementNotificationDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 9ecb665..0b25f85f 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -2113,6 +2113,9 @@
 extern const char kRecoverFromNeverSaveAndroidName[];
 extern const char kRecoverFromNeverSaveAndroidDescription[];
 
+extern const char kReduceGpuPriorityOnBackgroundName[];
+extern const char kReduceGpuPriorityOnBackgroundDescription[];
+
 extern const char kReengagementNotificationName[];
 extern const char kReengagementNotificationDescription[];
 
diff --git a/chrome/browser/geolocation/geolocation_permission_context_delegate_unittest.cc b/chrome/browser/geolocation/geolocation_permission_context_delegate_unittest.cc
index ca7dabc..8c45956 100644
--- a/chrome/browser/geolocation/geolocation_permission_context_delegate_unittest.cc
+++ b/chrome/browser/geolocation/geolocation_permission_context_delegate_unittest.cc
@@ -14,6 +14,7 @@
 #include "components/permissions/permission_request_manager.h"
 #include "components/permissions/permission_result.h"
 #include "components/permissions/test/mock_permission_prompt_factory.h"
+#include "content/public/browser/permission_result.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/permissions/permission_utils.h"
 
@@ -84,12 +85,12 @@
                 std::move(callback)));
   }
 
-  permissions::PermissionResult GetPermissionStatusForDisplayOnSettingsUI(
+  content::PermissionResult GetPermissionResultForOriginWithoutContext(
       Profile* profile,
-      ContentSettingsType permission,
-      const GURL& origin) {
+      blink::PermissionType permission,
+      const url::Origin& origin) {
     return PermissionManagerFactory::GetForProfile(profile)
-        ->GetPermissionStatusForDisplayOnSettingsUI(permission, origin);
+        ->GetPermissionResultForOriginWithoutContext(permission, origin);
   }
 };
 
@@ -125,7 +126,7 @@
 // TODO(https://crbug.com/1318240): Flaky.
 TEST_F(GeolocationPermissionContextDelegateTests,
        DISABLED_SearchGeolocationInIncognito) {
-  GURL requesting_frame_url(kDSETestUrl);
+  url::Origin requesting_frame_url = url::Origin::Create(GURL(kDSETestUrl));
 
   SearchPermissionsService* service =
       SearchPermissionsService::Factory::GetForBrowserContext(profile());
@@ -136,19 +137,19 @@
 
   // The DSE geolocation should not be auto-granted even in a non-OTR profile.
   ASSERT_EQ(
-      CONTENT_SETTING_ASK,
-      GetPermissionStatusForDisplayOnSettingsUI(
-          profile(), ContentSettingsType::GEOLOCATION, requesting_frame_url)
-          .content_setting);
+      blink::mojom::PermissionStatus::ASK,
+      GetPermissionResultForOriginWithoutContext(
+          profile(), blink::PermissionType::GEOLOCATION, requesting_frame_url)
+          .status);
 
   Profile* otr_profile =
       profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true);
 
   // The DSE geolocation should not be auto-granted in an OTR profile.
   ASSERT_EQ(
-      CONTENT_SETTING_ASK,
-      GetPermissionStatusForDisplayOnSettingsUI(
-          otr_profile, ContentSettingsType::GEOLOCATION, requesting_frame_url)
-          .content_setting);
+      blink::mojom::PermissionStatus::ASK,
+      GetPermissionResultForOriginWithoutContext(
+          otr_profile, blink::PermissionType::GEOLOCATION, requesting_frame_url)
+          .status);
 }
 #endif
diff --git a/chrome/browser/history_clusters/BUILD.gn b/chrome/browser/history_clusters/BUILD.gn
index 4bfc7ede6..23a9848 100644
--- a/chrome/browser/history_clusters/BUILD.gn
+++ b/chrome/browser/history_clusters/BUILD.gn
@@ -55,6 +55,7 @@
     "//chrome/browser/tabmodel:java",
     "//chrome/browser/ui/android/favicon:java",
     "//chrome/browser/ui/android/strings:ui_strings_grd",
+    "//chrome/browser/ui/messages/android:java",
     "//components/browser_ui/styles/android:java",
     "//components/browser_ui/widget/android:java",
     "//components/embedder_support/android:util_java",
diff --git a/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersCoordinator.java b/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersCoordinator.java
index 9524fca..a8d2aa75 100644
--- a/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersCoordinator.java
+++ b/chrome/browser/history_clusters/java/src/org/chromium/chrome/browser/history_clusters/HistoryClustersCoordinator.java
@@ -18,12 +18,16 @@
 
 import org.chromium.chrome.browser.history_clusters.HistoryClustersItemProperties.ItemType;
 import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.ui.messages.snackbar.Snackbar;
+import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
+import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager.SnackbarController;
 import org.chromium.components.browser_ui.widget.selectable_list.SelectableItemView;
 import org.chromium.components.browser_ui.widget.selectable_list.SelectableListLayout;
 import org.chromium.components.browser_ui.widget.selectable_list.SelectableListToolbar;
 import org.chromium.components.browser_ui.widget.selectable_list.SelectionDelegate;
 import org.chromium.components.favicon.LargeIconBridge;
 import org.chromium.components.search_engines.TemplateUrlService;
+import org.chromium.ui.base.Clipboard;
 import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
@@ -34,7 +38,7 @@
  * Root component for the HistoryClusters UI component, which displays lists of related history
  * visits grouped into clusters.
  */
-public class HistoryClustersCoordinator implements OnMenuItemClickListener {
+public class HistoryClustersCoordinator implements OnMenuItemClickListener, SnackbarController {
     private static class DisabledSelectionDelegate extends SelectionDelegate {
         @Override
         public boolean toggleSelectionForItem(Object o) {
@@ -66,13 +70,14 @@
     private SelectionDelegate mDisabledSelectionDelegate = new DisabledSelectionDelegate();
     private RecyclerView mRecyclerView;
     private final HistoryClustersMetricsLogger mMetricsLogger;
+    private final SnackbarManager mSnackbarManager;
 
     @VisibleForTesting
     HistoryClustersCoordinator(@NonNull Profile profile, @NonNull Activity activity,
             TemplateUrlService templateUrlService, HistoryClustersDelegate historyClustersDelegate,
             HistoryClustersMetricsLogger metricsLogger,
-            SelectionDelegate<ClusterVisit> selectionDelegate,
-            AccessibilityUtil accessibilityUtil) {
+            SelectionDelegate<ClusterVisit> selectionDelegate, AccessibilityUtil accessibilityUtil,
+            SnackbarManager snackbarManager) {
         mActivity = activity;
         mDelegate = historyClustersDelegate;
         mModelList = new ModelList();
@@ -82,6 +87,7 @@
                                 .build();
         mMetricsLogger = metricsLogger;
         mSelectionDelegate = selectionDelegate;
+        mSnackbarManager = snackbarManager;
 
         mMediator = new HistoryClustersMediator(HistoryClustersBridge.getForProfile(profile),
                 new LargeIconBridge(profile), mActivity, mActivity.getResources(), mModelList,
@@ -96,13 +102,14 @@
      * @param historyClustersDelegate Delegate that provides functionality that must be implemented
      *         externally, e.g. populating intents targeting activities we can't reference directly.
      * @param accessibilityUtil Utility object that tells us about the current accessibility state.
+     * @param snackbarManager The {@link SnackbarManager} used to display snackbars.
      */
     public HistoryClustersCoordinator(@NonNull Profile profile, @NonNull Activity activity,
             TemplateUrlService templateUrlService, HistoryClustersDelegate historyClustersDelegate,
-            AccessibilityUtil accessibilityUtil) {
+            AccessibilityUtil accessibilityUtil, SnackbarManager snackbarManager) {
         this(profile, activity, templateUrlService, historyClustersDelegate,
                 new HistoryClustersMetricsLogger(templateUrlService), new SelectionDelegate<>(),
-                accessibilityUtil);
+                accessibilityUtil, snackbarManager);
     }
 
     public void destroy() {
@@ -260,10 +267,26 @@
         } else if (menuItem.getItemId() == R.id.selection_mode_open_in_tab_group) {
             mMediator.openVisitsInNewTabs(mSelectionDelegate.getSelectedItemsAsList(), false, true);
             return true;
+        } else if (menuItem.getItemId() == R.id.selection_mode_copy_link) {
+            Clipboard.getInstance().setText(mSelectionDelegate.getSelectedItemsAsList()
+                                                    .get(0)
+                                                    .getNormalizedUrl()
+                                                    .getSpec());
+            mSelectionDelegate.clearSelection();
+            Snackbar snackbar = Snackbar.make(mActivity.getString(R.string.copied), this,
+                    Snackbar.TYPE_NOTIFICATION, Snackbar.UMA_HISTORY_LINK_COPIED);
+            mSnackbarManager.showSnackbar(snackbar);
         }
         return false;
     }
 
+    // SnackbarController implementation.
+    @Override
+    public void onAction(Object actionData) {}
+
+    @Override
+    public void onDismissNoAction(Object actionData) {}
+
     @VisibleForTesting
     public RecyclerView getRecyclerViewFortesting() {
         return mRecyclerView;
diff --git a/chrome/browser/login_detection/login_detection_keyed_service_factory.cc b/chrome/browser/login_detection/login_detection_keyed_service_factory.cc
index 7bdbfdb..3b19867 100644
--- a/chrome/browser/login_detection/login_detection_keyed_service_factory.cc
+++ b/chrome/browser/login_detection/login_detection_keyed_service_factory.cc
@@ -18,7 +18,7 @@
 
 ProfileSelections BuildLoginDetectionProfileSelection() {
   if (!IsLoginDetectionFeatureEnabled()) {
-    return ProfileSelections::BuildNoServicesForAllProfiles();
+    return ProfileSelections::BuildNoProfilesSelected();
   }
 
   return ProfileSelections::BuildDefault();
diff --git a/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc b/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc
index 023466f..2dbfa31 100644
--- a/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc
+++ b/chrome/browser/media/webrtc/permission_bubble_media_access_handler.cc
@@ -14,12 +14,10 @@
 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
 #include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
 #include "chrome/browser/media/webrtc/media_stream_device_permissions.h"
-#include "chrome/browser/permissions/permission_manager_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/pref_names.h"
 #include "components/content_settings/browser/page_specific_content_settings.h"
 #include "components/content_settings/core/browser/host_content_settings_map.h"
-#include "components/permissions/permission_manager.h"
 #include "components/permissions/permission_result.h"
 #include "components/permissions/permission_util.h"
 #include "components/permissions/permissions_client.h"
@@ -122,7 +120,7 @@
   // Otherwise, the Microphone permission request on NTP will be gated for
   // incorrect origin.
   GURL embedding_origin;
-  if (permissions::PermissionsClient::Get()->DoOriginsMatchNewTabPage(
+  if (permissions::PermissionsClient::Get()->DoURLsMatchNewTabPage(
           request.security_origin,
           web_contents->GetLastCommittedURL().DeprecatedGetOriginAsURL())) {
     embedding_origin =
@@ -133,7 +131,7 @@
   }
 
   content_settings->OnMediaStreamPermissionSet(
-      PermissionManagerFactory::GetForProfile(profile)->GetCanonicalOrigin(
+      permissions::PermissionUtil::GetCanonicalOrigin(
           ContentSettingsType::MEDIASTREAM_CAMERA, request.security_origin,
           embedding_origin),
       microphone_camera_state, selected_audio_device, selected_video_device,
diff --git a/chrome/browser/metrics/chrome_metrics_service_client.cc b/chrome/browser/metrics/chrome_metrics_service_client.cc
index 12556b1..393ae70 100644
--- a/chrome/browser/metrics/chrome_metrics_service_client.cc
+++ b/chrome/browser/metrics/chrome_metrics_service_client.cc
@@ -20,6 +20,7 @@
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/lazy_instance.h"
+#include "base/memory/ptr_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/metrics/field_trial_params.h"
 #include "base/metrics/histogram_macros.h"
@@ -127,7 +128,7 @@
 #include "components/metrics/android_metrics_provider.h"
 #else
 #include "chrome/browser/metrics/browser_activity_watcher.h"
-#include "components/performance_manager/public/metrics/metrics_provider.h"
+#include "chrome/browser/performance_manager/metrics/metrics_provider.h"
 #endif
 
 #if BUILDFLAG(IS_POSIX)
@@ -785,7 +786,7 @@
       std::make_unique<FamilyLinkUserMetricsProvider>());
 #else
   metrics_service_->RegisterMetricsProvider(
-      std::make_unique<performance_manager::MetricsProvider>(local_state));
+      base::WrapUnique(new performance_manager::MetricsProvider(local_state)));
 #endif  // BUILDFLAG(IS_ANDROID)
 
 #if BUILDFLAG(IS_WIN)
diff --git a/chrome/browser/net/cookie_policy_browsertest.cc b/chrome/browser/net/cookie_policy_browsertest.cc
index c5408a8..6b0fddec 100644
--- a/chrome/browser/net/cookie_policy_browsertest.cc
+++ b/chrome/browser/net/cookie_policy_browsertest.cc
@@ -291,7 +291,8 @@
                      bool expected) {
     switch (test_type) {
       case TestType::kFrame:
-        storage::test::ExpectStorageForFrame(frame, expected);
+        storage::test::ExpectStorageForFrame(frame, /*include_cookies=*/true,
+                                             expected);
         return;
       case TestType::kWorker:
         storage::test::ExpectStorageForWorker(frame, expected);
@@ -302,7 +303,7 @@
   void SetStorage(TestType test_type, content::RenderFrameHost* frame) {
     switch (test_type) {
       case TestType::kFrame:
-        storage::test::SetStorageForFrame(frame);
+        storage::test::SetStorageForFrame(frame, /*include_cookies=*/true);
         return;
       case TestType::kWorker:
         storage::test::SetStorageForWorker(frame);
diff --git a/chrome/browser/net/profile_network_context_service_browsertest.cc b/chrome/browser/net/profile_network_context_service_browsertest.cc
index 889e3f3..ed870f2b 100644
--- a/chrome/browser/net/profile_network_context_service_browsertest.cc
+++ b/chrome/browser/net/profile_network_context_service_browsertest.cc
@@ -57,6 +57,7 @@
 #include "content/public/common/content_features.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/simple_url_loader_test_helper.h"
+#include "content/public/test/test_cert_verifier_service_factory.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/system/data_pipe_utils.h"
 #include "net/base/features.h"
@@ -70,7 +71,6 @@
 #include "net/test/embedded_test_server/request_handler_util.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
 #include "services/cert_verifier/public/mojom/cert_verifier_service_factory.mojom.h"
-#include "services/cert_verifier/test_cert_verifier_service_factory.h"
 #include "services/network/public/cpp/cors/cors.h"
 #include "services/network/public/cpp/features.h"
 #include "services/network/public/mojom/network_context.mojom.h"
diff --git a/chrome/browser/net/storage_test_utils.cc b/chrome/browser/net/storage_test_utils.cc
index 5beb600..a9b53553 100644
--- a/chrome/browser/net/storage_test_utils.cc
+++ b/chrome/browser/net/storage_test_utils.cc
@@ -8,10 +8,12 @@
 
 namespace storage::test {
 
+const std::vector<std::string> kCookiesTypesForFrame{"Cookie", "CookieStore"};
+
 const std::vector<std::string> kStorageTypesForFrame{
-    "Cookie",         "LocalStorage", "FileSystem",       "FileSystemAccess",
-    "SessionStorage", "IndexedDb",    "WebSql",           "CacheStorage",
-    "ServiceWorker",  "CookieStore",  "StorageFoundation"};
+    "LocalStorage",   "FileSystem",    "FileSystemAccess",
+    "SessionStorage", "IndexedDb",     "WebSql",
+    "CacheStorage",   "ServiceWorker", "StorageFoundation"};
 
 const std::vector<std::string> kStorageTypesForWorker{
     "WorkerFileSystemAccess", "WorkerCacheStorage", "WorkerIndexedDb",
@@ -34,14 +36,23 @@
     "  () => { window.domAutomationController.send(false); },"
     ");";
 
+std::vector<std::string> GetStorageTypesForFrame(bool include_cookies) {
+  std::vector<std::string> types(kStorageTypesForFrame);
+  if (include_cookies) {
+    types.insert(types.end(), kCookiesTypesForFrame.begin(),
+                 kCookiesTypesForFrame.end());
+  }
+  return types;
+}
+
 std::string GetFrameContent(content::RenderFrameHost* frame) {
   return content::EvalJs(frame, "document.body.textContent").ExtractString();
 }
 
-void SetStorageForFrame(content::RenderFrameHost* frame) {
+void SetStorageForFrame(content::RenderFrameHost* frame, bool include_cookies) {
   base::flat_map<std::string, bool> actual;
   base::flat_map<std::string, bool> expected;
-  for (const auto& data_type : kStorageTypesForFrame) {
+  for (const auto& data_type : GetStorageTypesForFrame(include_cookies)) {
     actual[data_type] =
         content::EvalJs(frame, "set" + data_type + "()",
                         content::EXECUTE_SCRIPT_USE_MANUAL_REPLY)
@@ -71,10 +82,12 @@
   EXPECT_THAT(actual, testing::UnorderedElementsAreArray(expected));
 }
 
-void ExpectStorageForFrame(content::RenderFrameHost* frame, bool expected) {
+void ExpectStorageForFrame(content::RenderFrameHost* frame,
+                           bool include_cookies,
+                           bool expected) {
   base::flat_map<std::string, bool> actual;
   base::flat_map<std::string, bool> expected_elts;
-  for (const auto& data_type : kStorageTypesForFrame) {
+  for (const auto& data_type : GetStorageTypesForFrame(include_cookies)) {
     actual[data_type] =
         content::EvalJs(frame, "has" + data_type + "();",
                         content::EXECUTE_SCRIPT_USE_MANUAL_REPLY)
diff --git a/chrome/browser/net/storage_test_utils.h b/chrome/browser/net/storage_test_utils.h
index 274f663b..bfabd381 100644
--- a/chrome/browser/net/storage_test_utils.h
+++ b/chrome/browser/net/storage_test_utils.h
@@ -19,9 +19,11 @@
 
 // Helpers to set and check various types of storage on a given frame. Typically
 // used on page like //chrome/test/data/browsing_data/site_data.html
-void SetStorageForFrame(content::RenderFrameHost* frame);
+void SetStorageForFrame(content::RenderFrameHost* frame, bool include_cookies);
 void SetStorageForWorker(content::RenderFrameHost* frame);
-void ExpectStorageForFrame(content::RenderFrameHost* frame, bool expected);
+void ExpectStorageForFrame(content::RenderFrameHost* frame,
+                           bool include_cookies,
+                           bool expected);
 void ExpectStorageForWorker(content::RenderFrameHost* frame, bool expected);
 
 // Helpers to set and check various types of cross tab info for a given frame.
diff --git a/chrome/browser/net/system_network_context_manager_browsertest.cc b/chrome/browser/net/system_network_context_manager_browsertest.cc
index 42b69ed5..c73337ac 100644
--- a/chrome/browser/net/system_network_context_manager_browsertest.cc
+++ b/chrome/browser/net/system_network_context_manager_browsertest.cc
@@ -35,11 +35,11 @@
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/frame_test_utils.h"
+#include "content/public/test/test_cert_verifier_service_factory.h"
 #include "content/public/test/test_utils.h"
 #include "net/cookies/canonical_cookie_test_helpers.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/net_buildflags.h"
-#include "services/cert_verifier/test_cert_verifier_service_factory.h"
 #include "services/network/public/cpp/features.h"
 #include "services/network/public/cpp/network_service_buildflags.h"
 #include "services/network/public/mojom/network_context.mojom.h"
diff --git a/chrome/browser/notifications/chrome_ash_message_center_client_unittest.cc b/chrome/browser/notifications/chrome_ash_message_center_client_unittest.cc
index 7c284cda..b63879a 100644
--- a/chrome/browser/notifications/chrome_ash_message_center_client_unittest.cc
+++ b/chrome/browser/notifications/chrome_ash_message_center_client_unittest.cc
@@ -26,6 +26,7 @@
 #include "components/permissions/test/permission_test_util.h"
 #include "components/user_manager/scoped_user_manager.h"
 #include "content/public/browser/permission_controller.h"
+#include "content/public/browser/permission_result.h"
 #include "content/public/test/browser_task_environment.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_builder.h"
@@ -251,19 +252,21 @@
 
   // (1) Enable the permission when the default is to ask (expected to set).
   message_center_client()->SetNotifierEnabled(notifier_id, true);
-  EXPECT_EQ(blink::mojom::PermissionStatus::GRANTED,
-            profile->GetPermissionController()
-                ->GetPermissionStatusForOriginWithoutContext(
-                    blink::PermissionType::NOTIFICATIONS,
-                    url::Origin::Create(origin)));
+  EXPECT_EQ(
+      blink::mojom::PermissionStatus::GRANTED,
+      profile->GetPermissionController()
+          ->GetPermissionResultForOriginWithoutContext(
+              blink::PermissionType::NOTIFICATIONS, url::Origin::Create(origin))
+          .status);
 
   // (2) Disable the permission when the default is to ask (expected to clear).
   message_center_client()->SetNotifierEnabled(notifier_id, false);
-  EXPECT_EQ(blink::mojom::PermissionStatus::ASK,
-            profile->GetPermissionController()
-                ->GetPermissionStatusForOriginWithoutContext(
-                    blink::PermissionType::NOTIFICATIONS,
-                    url::Origin::Create(origin)));
+  EXPECT_EQ(
+      blink::mojom::PermissionStatus::ASK,
+      profile->GetPermissionController()
+          ->GetPermissionResultForOriginWithoutContext(
+              blink::PermissionType::NOTIFICATIONS, url::Origin::Create(origin))
+          .status);
 
   // Change the default content setting vaule for notifications to ALLOW.
   HostContentSettingsMapFactory::GetForProfile(profile)
@@ -272,20 +275,22 @@
 
   // (3) Disable the permission when the default is allowed (expected to set).
   message_center_client()->SetNotifierEnabled(notifier_id, false);
-  EXPECT_EQ(blink::mojom::PermissionStatus::DENIED,
-            profile->GetPermissionController()
-                ->GetPermissionStatusForOriginWithoutContext(
-                    blink::PermissionType::NOTIFICATIONS,
-                    url::Origin::Create(origin)));
+  EXPECT_EQ(
+      blink::mojom::PermissionStatus::DENIED,
+      profile->GetPermissionController()
+          ->GetPermissionResultForOriginWithoutContext(
+              blink::PermissionType::NOTIFICATIONS, url::Origin::Create(origin))
+          .status);
 
   // (4) Enable the permission when the default is allowed (expected to clear).
   message_center_client()->SetNotifierEnabled(notifier_id, true);
 
-  EXPECT_EQ(blink::mojom::PermissionStatus::GRANTED,
-            profile->GetPermissionController()
-                ->GetPermissionStatusForOriginWithoutContext(
-                    blink::PermissionType::NOTIFICATIONS,
-                    url::Origin::Create(origin)));
+  EXPECT_EQ(
+      blink::mojom::PermissionStatus::GRANTED,
+      profile->GetPermissionController()
+          ->GetPermissionResultForOriginWithoutContext(
+              blink::PermissionType::NOTIFICATIONS, url::Origin::Create(origin))
+          .status);
 
   // Now change the default content setting value to BLOCK.
   HostContentSettingsMapFactory::GetForProfile(profile)
@@ -294,19 +299,21 @@
 
   // (5) Enable the permission when the default is blocked (expected to set).
   message_center_client()->SetNotifierEnabled(notifier_id, true);
-  EXPECT_EQ(blink::mojom::PermissionStatus::GRANTED,
-            profile->GetPermissionController()
-                ->GetPermissionStatusForOriginWithoutContext(
-                    blink::PermissionType::NOTIFICATIONS,
-                    url::Origin::Create(origin)));
+  EXPECT_EQ(
+      blink::mojom::PermissionStatus::GRANTED,
+      profile->GetPermissionController()
+          ->GetPermissionResultForOriginWithoutContext(
+              blink::PermissionType::NOTIFICATIONS, url::Origin::Create(origin))
+          .status);
 
   // (6) Disable the permission when the default is blocked (expected to clear).
   message_center_client()->SetNotifierEnabled(notifier_id, false);
-  EXPECT_EQ(blink::mojom::PermissionStatus::DENIED,
-            profile->GetPermissionController()
-                ->GetPermissionStatusForOriginWithoutContext(
-                    blink::PermissionType::NOTIFICATIONS,
-                    url::Origin::Create(origin)));
+  EXPECT_EQ(
+      blink::mojom::PermissionStatus::DENIED,
+      profile->GetPermissionController()
+          ->GetPermissionResultForOriginWithoutContext(
+              blink::PermissionType::NOTIFICATIONS, url::Origin::Create(origin))
+          .status);
 }
 
 }  // namespace
diff --git a/chrome/browser/notifications/notifier_state_tracker.cc b/chrome/browser/notifications/notifier_state_tracker.cc
index 4d83cfb..465f3be 100644
--- a/chrome/browser/notifications/notifier_state_tracker.cc
+++ b/chrome/browser/notifications/notifier_state_tracker.cc
@@ -19,6 +19,7 @@
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/prefs/scoped_user_pref_update.h"
 #include "content/public/browser/permission_controller.h"
+#include "content/public/browser/permission_result.h"
 #include "extensions/buildflags/buildflags.h"
 #include "third_party/blink/public/common/permissions/permission_utils.h"
 #include "ui/message_center/public/cpp/notifier_id.h"
@@ -69,10 +70,10 @@
           disabled_extension_ids_.end();
     case message_center::NotifierType::WEB_PAGE:
       return profile_->GetPermissionController()
-                 ->GetPermissionStatusForOriginWithoutContext(
+                 ->GetPermissionResultForOriginWithoutContext(
                      blink::PermissionType::NOTIFICATIONS,
-                     url::Origin::Create(notifier_id.url)) ==
-             blink::mojom::PermissionStatus::GRANTED;
+                     url::Origin::Create(notifier_id.url))
+                 .status == blink::mojom::PermissionStatus::GRANTED;
     case message_center::NotifierType::SYSTEM_COMPONENT:
       // We do not disable system component notifications.
       return true;
diff --git a/chrome/browser/notifications/persistent_notification_handler.cc b/chrome/browser/notifications/persistent_notification_handler.cc
index 45a3e5d..0d5b6973 100644
--- a/chrome/browser/notifications/persistent_notification_handler.cc
+++ b/chrome/browser/notifications/persistent_notification_handler.cc
@@ -23,6 +23,7 @@
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/notification_event_dispatcher.h"
 #include "content/public/browser/permission_controller.h"
+#include "content/public/browser/permission_result.h"
 #include "content/public/common/persistent_notification_status.h"
 #include "third_party/blink/public/common/permissions/permission_utils.h"
 #include "url/gurl.h"
@@ -109,9 +110,9 @@
 
   blink::mojom::PermissionStatus permission_status =
       profile->GetPermissionController()
-          ->GetPermissionStatusForOriginWithoutContext(
-              blink::PermissionType::NOTIFICATIONS,
-              url::Origin::Create(origin));
+          ->GetPermissionResultForOriginWithoutContext(
+              blink::PermissionType::NOTIFICATIONS, url::Origin::Create(origin))
+          .status;
 
   // Don't process click events when the |origin| doesn't have permission. This
   // can't be a DCHECK because of potential races with native notifications.
diff --git a/chrome/browser/notifications/persistent_notification_handler_unittest.cc b/chrome/browser/notifications/persistent_notification_handler_unittest.cc
index 59d7c3db..340327de 100644
--- a/chrome/browser/notifications/persistent_notification_handler_unittest.cc
+++ b/chrome/browser/notifications/persistent_notification_handler_unittest.cc
@@ -18,6 +18,7 @@
 #include "chrome/browser/notifications/platform_notification_service_factory.h"
 #include "chrome/browser/notifications/platform_notification_service_impl.h"
 #include "chrome/test/base/testing_profile.h"
+#include "content/public/browser/permission_result.h"
 #include "content/public/common/persistent_notification_status.h"
 #include "content/public/test/browser_task_environment.h"
 #include "content/public/test/mock_permission_manager.h"
@@ -51,9 +52,10 @@
   // Sets the notification permission status to |permission_status|.
   void SetNotificationPermissionStatus(
       blink::mojom::PermissionStatus permission_status) {
-    ON_CALL(*permission_manager_,
-            GetPermissionStatus(blink::PermissionType::NOTIFICATIONS, _, _))
-        .WillByDefault(Return(permission_status));
+    ON_CALL(*permission_manager_, GetPermissionResultForOriginWithoutContext(
+                                      blink::PermissionType::NOTIFICATIONS, _))
+        .WillByDefault(Return(content::PermissionResult(
+            permission_status, content::PermissionStatusSource::UNSPECIFIED)));
   }
 
   // TestingProfile overrides:
diff --git a/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.cc b/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.cc
index 918c451..15f21c3 100644
--- a/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.cc
+++ b/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.cc
@@ -17,6 +17,7 @@
 #include "chrome/browser/performance_manager/decorators/helpers/page_live_state_decorator_helper.h"
 #include "chrome/browser/performance_manager/decorators/page_aggregator.h"
 #include "chrome/browser/performance_manager/metrics/memory_pressure_metrics.h"
+#include "chrome/browser/performance_manager/metrics/metrics_provider.h"
 #include "chrome/browser/performance_manager/observers/page_load_metrics_observer.h"
 #include "chrome/browser/performance_manager/policies/background_tab_loading_policy.h"
 #include "chrome/browser/performance_manager/policies/policy_features.h"
@@ -211,9 +212,15 @@
           performance_manager::features::kHighEfficiencyModeAvailable) ||
       base::FeatureList::IsEnabled(
           performance_manager::features::kBatterySaverModeAvailable)) {
-    user_performance_tuning_manager_ = std::make_unique<
+    user_performance_tuning_manager_ = std::unique_ptr<
         performance_manager::user_tuning::UserPerformanceTuningManager>(
-        g_browser_process->local_state());
+        new performance_manager::user_tuning::UserPerformanceTuningManager(
+            g_browser_process->local_state()));
+
+    // This object is created by the metrics service before threads, but it
+    // needs the UserPerformanceTuningManager to exist. At this point it's
+    // instantiated, but still needs to be initialized.
+    performance_manager::MetricsProvider::GetInstance()->Initialize();
   }
 #endif
 }
diff --git a/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.h b/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.h
index 7b7594a..d69ce6e7 100644
--- a/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.h
+++ b/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.h
@@ -35,12 +35,10 @@
 #endif
 
 namespace user_tuning {
+class ProfileDiscardOptOutListHelper;
 class UserPerformanceTuningManager;
 }
 
-namespace user_tuning {
-class ProfileDiscardOptOutListHelper;
-}
 }  // namespace performance_manager
 
 // Handles the initialization of the performance manager and a few dependent
diff --git a/chrome/browser/performance_manager/metrics/metrics_provider.cc b/chrome/browser/performance_manager/metrics/metrics_provider.cc
new file mode 100644
index 0000000..41056ab
--- /dev/null
+++ b/chrome/browser/performance_manager/metrics/metrics_provider.cc
@@ -0,0 +1,115 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/performance_manager/metrics/metrics_provider.h"
+
+#include "base/metrics/histogram_functions.h"
+#include "chrome/browser/performance_manager/user_tuning/user_performance_tuning_manager.h"
+#include "components/performance_manager/public/user_tuning/prefs.h"
+#include "components/prefs/pref_service.h"
+
+namespace performance_manager {
+
+namespace {
+
+MetricsProvider* g_metrics_provider = nullptr;
+
+}
+
+// static
+MetricsProvider* MetricsProvider::GetInstance() {
+  DCHECK(g_metrics_provider);
+  return g_metrics_provider;
+}
+
+MetricsProvider::~MetricsProvider() {
+  DCHECK_EQ(this, g_metrics_provider);
+  g_metrics_provider = nullptr;
+}
+
+void MetricsProvider::Initialize() {
+  DCHECK(!initialized_);
+
+  pref_change_registrar_.Init(local_state_);
+  pref_change_registrar_.Add(
+      performance_manager::user_tuning::prefs::kHighEfficiencyModeEnabled,
+      base::BindRepeating(&MetricsProvider::OnTuningModesChanged,
+                          base::Unretained(this)));
+  performance_manager::user_tuning::UserPerformanceTuningManager::GetInstance()
+      ->AddObserver(this);
+
+  initialized_ = true;
+  current_mode_ = ComputeCurrentMode();
+}
+
+void MetricsProvider::ProvideCurrentSessionData(
+    metrics::ChromeUserMetricsExtension* uma_proto) {
+  // It's valid for this to be called when `initialized_` is false if the finch
+  // features controlling battery saver and high efficiency are disabled.
+  // TODO(crbug.com/1348590): CHECK(initialized_) when the features are enabled
+  // and removed.
+  base::UmaHistogramEnumeration("PerformanceManager.UserTuning.EfficiencyMode",
+                                current_mode_);
+
+  // Set `current_mode_` to represent the state of the modes as they are now, so
+  // that this mode is what is adequately reported at the next report, unless it
+  // changes in the meantime.
+  current_mode_ = ComputeCurrentMode();
+}
+
+MetricsProvider::MetricsProvider(PrefService* local_state)
+    : local_state_(local_state) {
+  DCHECK(!g_metrics_provider);
+  g_metrics_provider = this;
+}
+
+void MetricsProvider::OnBatterySaverModeChanged(bool is_active) {
+  OnTuningModesChanged();
+}
+
+void MetricsProvider::OnTuningModesChanged() {
+  EfficiencyMode new_mode = ComputeCurrentMode();
+
+  // If the mode changes between UMA reports, mark it as Mixed for this
+  // interval.
+  if (current_mode_ != new_mode) {
+    current_mode_ = EfficiencyMode::kMixed;
+  }
+}
+
+MetricsProvider::EfficiencyMode MetricsProvider::ComputeCurrentMode() const {
+  // It's valid for this to be uninitialized if the battery saver/high
+  // efficiency modes are unavailable. In that case, the browser is running in
+  // normal mode, so return kNormal.
+  // TODO(crbug.com/1348590): Change this to a DCHECK when the features are
+  // enabled and removed.
+  if (!initialized_) {
+    return EfficiencyMode::kNormal;
+  }
+
+  bool high_efficiency_enabled = local_state_->GetBoolean(
+      performance_manager::user_tuning::prefs::kHighEfficiencyModeEnabled);
+
+  performance_manager::user_tuning::UserPerformanceTuningManager*
+      user_performance_tuning_manager = performance_manager::user_tuning::
+          UserPerformanceTuningManager::GetInstance();
+  bool battery_saver_enabled =
+      user_performance_tuning_manager->IsBatterySaverActive();
+
+  if (high_efficiency_enabled && battery_saver_enabled) {
+    return EfficiencyMode::kBoth;
+  }
+
+  if (high_efficiency_enabled) {
+    return EfficiencyMode::kHighEfficiency;
+  }
+
+  if (battery_saver_enabled) {
+    return EfficiencyMode::kBatterySaver;
+  }
+
+  return EfficiencyMode::kNormal;
+}
+
+}  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/metrics/metrics_provider.h b/chrome/browser/performance_manager/metrics/metrics_provider.h
new file mode 100644
index 0000000..d42d7be
--- /dev/null
+++ b/chrome/browser/performance_manager/metrics/metrics_provider.h
@@ -0,0 +1,79 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_PERFORMANCE_MANAGER_METRICS_METRICS_PROVIDER_H_
+#define CHROME_BROWSER_PERFORMANCE_MANAGER_METRICS_METRICS_PROVIDER_H_
+
+#include "base/memory/raw_ptr.h"
+#include "chrome/browser/performance_manager/user_tuning/user_performance_tuning_manager.h"
+#include "components/metrics/metrics_provider.h"
+#include "components/prefs/pref_change_registrar.h"
+
+class ChromeMetricsServiceClient;
+class PerformanceManagerMetricsProviderTest;
+class PrefService;
+
+namespace performance_manager {
+
+// A metrics provider to add some performance manager related metrics to the UMA
+// protos on each upload.
+class MetricsProvider : public ::metrics::MetricsProvider,
+                        public performance_manager::user_tuning::
+                            UserPerformanceTuningManager::Observer {
+ public:
+  enum class EfficiencyMode {
+    // No efficiency mode for the entire upload window
+    kNormal = 0,
+    // In high efficiency mode for the entire upload window
+    kHighEfficiency = 1,
+    // In battery saver mode for the entire upload window
+    kBatterySaver = 2,
+    // Both modes enabled for the entire upload window
+    kBoth = 3,
+    // The modes were changed during the upload window
+    kMixed = 4,
+    // Max value, used in UMA histograms macros
+    kMaxValue = kMixed
+  };
+
+  static MetricsProvider* GetInstance();
+
+  ~MetricsProvider() override;
+
+  void Initialize();
+
+  // metrics::MetricsProvider:
+  // This is only called from UMA code but is public for testing.
+  void ProvideCurrentSessionData(
+      ::metrics::ChromeUserMetricsExtension* uma_proto) override;
+
+ private:
+  friend class ::ChromeMetricsServiceClient;
+  friend class ::PerformanceManagerMetricsProviderTest;
+
+  explicit MetricsProvider(PrefService* local_state);
+
+  // UserPerformanceTuningManager::Observer:
+  void OnBatterySaverModeChanged(bool is_active) override;
+  void OnExternalPowerConnectedChanged(
+      bool external_power_connected) override{};
+  void OnBatteryThresholdReached() override{};
+  void OnMemoryThresholdReached() override{};
+  void OnTabCountThresholdReached() override{};
+  void OnJankThresholdReached() override{};
+
+  void OnTuningModesChanged();
+  EfficiencyMode ComputeCurrentMode() const;
+
+  PrefChangeRegistrar pref_change_registrar_;
+  const raw_ptr<PrefService> local_state_;
+  EfficiencyMode current_mode_ = EfficiencyMode::kNormal;
+
+  bool initialized_ = false;
+  ;
+};
+
+}  // namespace performance_manager
+
+#endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_METRICS_METRICS_PROVIDER_H_
diff --git a/components/performance_manager/metrics/metrics_provider_unittest.cc b/chrome/browser/performance_manager/metrics/metrics_provider_unittest.cc
similarity index 63%
rename from components/performance_manager/metrics/metrics_provider_unittest.cc
rename to chrome/browser/performance_manager/metrics/metrics_provider_unittest.cc
index ef18ea4..a221e79 100644
--- a/components/performance_manager/metrics/metrics_provider_unittest.cc
+++ b/chrome/browser/performance_manager/metrics/metrics_provider_unittest.cc
@@ -2,13 +2,27 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/performance_manager/public/metrics/metrics_provider.h"
+#include "chrome/browser/performance_manager/metrics/metrics_provider.h"
 
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/performance_manager/user_tuning/fake_frame_throttling_delegate.h"
+#include "chrome/browser/performance_manager/user_tuning/user_performance_tuning_manager.h"
+#include "components/performance_manager/public/features.h"
 #include "components/performance_manager/public/user_tuning/prefs.h"
 #include "components/prefs/testing_pref_service.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#include "base/logging.h"
+
+class FakeHighEfficiencyModeToggleDelegate
+    : public performance_manager::user_tuning::UserPerformanceTuningManager::
+          HighEfficiencyModeToggleDelegate {
+ public:
+  void ToggleHighEfficiencyMode(bool enabled) override {}
+  ~FakeHighEfficiencyModeToggleDelegate() override = default;
+};
+
 class PerformanceManagerMetricsProviderTest : public testing::Test {
  protected:
   PrefService* local_state() { return &local_state_; }
@@ -32,32 +46,55 @@
                               sample, 1);
   }
 
+  void InitProvider() { provider_->Initialize(); }
+
+  performance_manager::MetricsProvider* provider() { return provider_.get(); }
+
  private:
   void SetUp() override {
+    feature_list_.InitWithFeatures(
+        {performance_manager::features::kHighEfficiencyModeAvailable,
+         performance_manager::features::kBatterySaverModeAvailable},
+        {});
+
     performance_manager::user_tuning::prefs::RegisterLocalStatePrefs(
         local_state_.registry());
+
+    manager_.reset(
+        new performance_manager::user_tuning::UserPerformanceTuningManager(
+            &local_state_,
+            std::make_unique<performance_manager::FakeFrameThrottlingDelegate>(
+                &throttling_enabled_),
+            std::make_unique<FakeHighEfficiencyModeToggleDelegate>()));
+    provider_.reset(new performance_manager::MetricsProvider(local_state()));
   }
 
   TestingPrefServiceSimple local_state_;
+  base::test::ScopedFeatureList feature_list_;
+
+  bool throttling_enabled_ = false;
+  std::unique_ptr<
+      performance_manager::user_tuning::UserPerformanceTuningManager>
+      manager_;
+  std::unique_ptr<performance_manager::MetricsProvider> provider_;
 };
 
 TEST_F(PerformanceManagerMetricsProviderTest, TestNormalMode) {
+  InitProvider();
   base::HistogramTester tester;
 
-  performance_manager::MetricsProvider provider(local_state());
-  provider.ProvideCurrentSessionData(nullptr);
+  provider()->ProvideCurrentSessionData(nullptr);
 
   ExpectSingleUniqueSample(
       tester, performance_manager::MetricsProvider::EfficiencyMode::kNormal);
 }
 
 TEST_F(PerformanceManagerMetricsProviderTest, TestMixedMode) {
-  performance_manager::MetricsProvider provider(local_state());
-
+  InitProvider();
   {
     base::HistogramTester tester;
     // Start in normal mode
-    provider.ProvideCurrentSessionData(nullptr);
+    provider()->ProvideCurrentSessionData(nullptr);
     ExpectSingleUniqueSample(
         tester, performance_manager::MetricsProvider::EfficiencyMode::kNormal);
   }
@@ -68,7 +105,7 @@
     // because we transitioned from normal to High-Efficiency during the
     // interval.
     SetHighEfficiencyEnabled(true);
-    provider.ProvideCurrentSessionData(nullptr);
+    provider()->ProvideCurrentSessionData(nullptr);
     ExpectSingleUniqueSample(
         tester, performance_manager::MetricsProvider::EfficiencyMode::kMixed);
   }
@@ -77,7 +114,7 @@
     base::HistogramTester tester;
     // If another UMA upload happens without mode changes, this one will report
     // High-Efficiency Mode.
-    provider.ProvideCurrentSessionData(nullptr);
+    provider()->ProvideCurrentSessionData(nullptr);
     ExpectSingleUniqueSample(
         tester,
         performance_manager::MetricsProvider::EfficiencyMode::kHighEfficiency);
@@ -88,13 +125,13 @@
   SetHighEfficiencyEnabled(true);
   SetBatterySaverEnabled(true);
 
-  performance_manager::MetricsProvider provider(local_state());
+  InitProvider();
 
   {
     base::HistogramTester tester;
     // Start with both modes enabled (such as a Chrome startup after having
     // enabled both modes in a previous session).
-    provider.ProvideCurrentSessionData(nullptr);
+    provider()->ProvideCurrentSessionData(nullptr);
     ExpectSingleUniqueSample(
         tester, performance_manager::MetricsProvider::EfficiencyMode::kBoth);
   }
@@ -103,7 +140,7 @@
     base::HistogramTester tester;
     // Disabling High-Efficiency Mode will cause the next report to be "mixed".
     SetHighEfficiencyEnabled(false);
-    provider.ProvideCurrentSessionData(nullptr);
+    provider()->ProvideCurrentSessionData(nullptr);
     ExpectSingleUniqueSample(
         tester, performance_manager::MetricsProvider::EfficiencyMode::kMixed);
   }
@@ -111,7 +148,7 @@
   {
     base::HistogramTester tester;
     // No changes until the following report, "Battery saver" is reported
-    provider.ProvideCurrentSessionData(nullptr);
+    provider()->ProvideCurrentSessionData(nullptr);
     ExpectSingleUniqueSample(
         tester,
         performance_manager::MetricsProvider::EfficiencyMode::kBatterySaver);
@@ -122,7 +159,7 @@
     // Re-enabling High-Efficiency Mode will cause the next report to indicate
     // "mixed".
     SetHighEfficiencyEnabled(true);
-    provider.ProvideCurrentSessionData(nullptr);
+    provider()->ProvideCurrentSessionData(nullptr);
     ExpectSingleUniqueSample(
         tester, performance_manager::MetricsProvider::EfficiencyMode::kMixed);
   }
@@ -130,8 +167,8 @@
   {
     base::HistogramTester tester;
     // One more report with no changes, this one reports "both" again.
-    provider.ProvideCurrentSessionData(nullptr);
+    provider()->ProvideCurrentSessionData(nullptr);
     ExpectSingleUniqueSample(
         tester, performance_manager::MetricsProvider::EfficiencyMode::kBoth);
   }
-}
\ No newline at end of file
+}
diff --git a/chrome/browser/performance_manager/user_tuning/fake_frame_throttling_delegate.cc b/chrome/browser/performance_manager/user_tuning/fake_frame_throttling_delegate.cc
new file mode 100644
index 0000000..c2ded5b
--- /dev/null
+++ b/chrome/browser/performance_manager/user_tuning/fake_frame_throttling_delegate.cc
@@ -0,0 +1,20 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/performance_manager/user_tuning/fake_frame_throttling_delegate.h"
+
+namespace performance_manager {
+
+void FakeFrameThrottlingDelegate::StartThrottlingAllFrameSinks() {
+  *throttling_enabled_ = true;
+}
+void FakeFrameThrottlingDelegate::StopThrottlingAllFrameSinks() {
+  *throttling_enabled_ = false;
+}
+
+FakeFrameThrottlingDelegate::FakeFrameThrottlingDelegate(
+    bool* throttling_enabled)
+    : throttling_enabled_(throttling_enabled) {}
+
+}  // namespace performance_manager
diff --git a/chrome/browser/performance_manager/user_tuning/fake_frame_throttling_delegate.h b/chrome/browser/performance_manager/user_tuning/fake_frame_throttling_delegate.h
new file mode 100644
index 0000000..a36b08b
--- /dev/null
+++ b/chrome/browser/performance_manager/user_tuning/fake_frame_throttling_delegate.h
@@ -0,0 +1,27 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_PERFORMANCE_MANAGER_USER_TUNING_FAKE_FRAME_THROTTLING_DELEGATE_H_
+#define CHROME_BROWSER_PERFORMANCE_MANAGER_USER_TUNING_FAKE_FRAME_THROTTLING_DELEGATE_H_
+
+#include "chrome/browser/performance_manager/user_tuning/user_performance_tuning_manager.h"
+
+namespace performance_manager {
+
+class FakeFrameThrottlingDelegate
+    : public performance_manager::user_tuning::UserPerformanceTuningManager::
+          FrameThrottlingDelegate {
+ public:
+  void StartThrottlingAllFrameSinks() override;
+  void StopThrottlingAllFrameSinks() override;
+
+  explicit FakeFrameThrottlingDelegate(bool* throttling_enabled);
+  ~FakeFrameThrottlingDelegate() override = default;
+
+  bool* throttling_enabled_;
+};
+
+}  // namespace performance_manager
+
+#endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_USER_TUNING_FAKE_FRAME_THROTTLING_DELEGATE_H_
diff --git a/chrome/browser/performance_manager/user_tuning/user_performance_tuning_manager.cc b/chrome/browser/performance_manager/user_tuning/user_performance_tuning_manager.cc
index 4cdeea92..01fd0f8 100644
--- a/chrome/browser/performance_manager/user_tuning/user_performance_tuning_manager.cc
+++ b/chrome/browser/performance_manager/user_tuning/user_performance_tuning_manager.cc
@@ -15,6 +15,8 @@
 namespace performance_manager::user_tuning {
 namespace {
 
+UserPerformanceTuningManager* g_user_performance_tuning_manager = nullptr;
+
 class FrameThrottlingDelegateImpl
     : public performance_manager::user_tuning::UserPerformanceTuningManager::
           FrameThrottlingDelegate {
@@ -30,40 +32,35 @@
   ~FrameThrottlingDelegateImpl() override = default;
 };
 
+class HighEfficiencyModeToggleDelegateImpl
+    : public performance_manager::user_tuning::UserPerformanceTuningManager::
+          HighEfficiencyModeToggleDelegate {
+ public:
+  void ToggleHighEfficiencyMode(bool enabled) override {
+    performance_manager::PerformanceManager::CallOnGraph(
+        FROM_HERE, base::BindOnce(
+                       [](bool enabled, performance_manager::Graph* graph) {
+                         policies::HighEfficiencyModePolicy::GetInstance()
+                             ->OnHighEfficiencyModeChanged(enabled);
+                       },
+                       enabled));
+  }
+
+  ~HighEfficiencyModeToggleDelegateImpl() override = default;
+};
+
 }  // namespace
 
-UserPerformanceTuningManager::UserPerformanceTuningManager(
-    PrefService* local_state,
-    std::unique_ptr<FrameThrottlingDelegate> frame_throttling_delegate)
-    : frame_throttling_delegate_(
-          frame_throttling_delegate
-              ? std::move(frame_throttling_delegate)
-              : std::make_unique<FrameThrottlingDelegateImpl>()) {
-  pref_change_registrar_.Init(local_state);
-
-  if (base::FeatureList::IsEnabled(
-          performance_manager::features::kHighEfficiencyModeAvailable)) {
-    pref_change_registrar_.Add(
-        performance_manager::user_tuning::prefs::kHighEfficiencyModeEnabled,
-        base::BindRepeating(
-            &UserPerformanceTuningManager::OnHighEfficiencyModePrefChanged,
-            base::Unretained(this)));
-    // Make sure the initial state of the pref is passed on to the policy.
-    OnHighEfficiencyModePrefChanged();
-  }
-
-  if (base::FeatureList::IsEnabled(
-          performance_manager::features::kBatterySaverModeAvailable)) {
-    pref_change_registrar_.Add(
-        performance_manager::user_tuning::prefs::kBatterySaverModeEnabled,
-        base::BindRepeating(
-            &UserPerformanceTuningManager::OnBatterySaverModePrefChanged,
-            base::Unretained(this)));
-    OnBatterySaverModePrefChanged();
-  }
+// static
+UserPerformanceTuningManager* UserPerformanceTuningManager::GetInstance() {
+  DCHECK(g_user_performance_tuning_manager);
+  return g_user_performance_tuning_manager;
 }
 
-UserPerformanceTuningManager::~UserPerformanceTuningManager() = default;
+UserPerformanceTuningManager::~UserPerformanceTuningManager() {
+  DCHECK_EQ(this, g_user_performance_tuning_manager);
+  g_user_performance_tuning_manager = nullptr;
+}
 
 void UserPerformanceTuningManager::AddObserver(Observer* o) {
   observers_.AddObserver(o);
@@ -92,16 +89,50 @@
   return battery_saver_mode_enabled_;
 }
 
+UserPerformanceTuningManager::UserPerformanceTuningManager(
+    PrefService* local_state,
+    std::unique_ptr<FrameThrottlingDelegate> frame_throttling_delegate,
+    std::unique_ptr<HighEfficiencyModeToggleDelegate>
+        high_efficiency_mode_toggle_delegate)
+    : frame_throttling_delegate_(
+          frame_throttling_delegate
+              ? std::move(frame_throttling_delegate)
+              : std::make_unique<FrameThrottlingDelegateImpl>()),
+      high_efficiency_mode_toggle_delegate_(
+          high_efficiency_mode_toggle_delegate
+              ? std::move(high_efficiency_mode_toggle_delegate)
+              : std::make_unique<HighEfficiencyModeToggleDelegateImpl>()) {
+  DCHECK(!g_user_performance_tuning_manager);
+  g_user_performance_tuning_manager = this;
+
+  pref_change_registrar_.Init(local_state);
+
+  if (base::FeatureList::IsEnabled(
+          performance_manager::features::kHighEfficiencyModeAvailable)) {
+    pref_change_registrar_.Add(
+        performance_manager::user_tuning::prefs::kHighEfficiencyModeEnabled,
+        base::BindRepeating(
+            &UserPerformanceTuningManager::OnHighEfficiencyModePrefChanged,
+            base::Unretained(this)));
+    // Make sure the initial state of the pref is passed on to the policy.
+    OnHighEfficiencyModePrefChanged();
+  }
+
+  if (base::FeatureList::IsEnabled(
+          performance_manager::features::kBatterySaverModeAvailable)) {
+    pref_change_registrar_.Add(
+        performance_manager::user_tuning::prefs::kBatterySaverModeEnabled,
+        base::BindRepeating(
+            &UserPerformanceTuningManager::OnBatterySaverModePrefChanged,
+            base::Unretained(this)));
+    OnBatterySaverModePrefChanged();
+  }
+}
+
 void UserPerformanceTuningManager::OnHighEfficiencyModePrefChanged() {
   bool enabled = pref_change_registrar_.prefs()->GetBoolean(
       performance_manager::user_tuning::prefs::kHighEfficiencyModeEnabled);
-  performance_manager::PerformanceManager::CallOnGraph(
-      FROM_HERE, base::BindOnce(
-                     [](bool enabled, performance_manager::Graph* graph) {
-                       policies::HighEfficiencyModePolicy::GetInstance()
-                           ->OnHighEfficiencyModeChanged(enabled);
-                     },
-                     enabled));
+  high_efficiency_mode_toggle_delegate_->ToggleHighEfficiencyMode(enabled);
 }
 
 void UserPerformanceTuningManager::OnBatterySaverModePrefChanged() {
diff --git a/chrome/browser/performance_manager/user_tuning/user_performance_tuning_manager.h b/chrome/browser/performance_manager/user_tuning/user_performance_tuning_manager.h
index 47014aa8..0765d0e 100644
--- a/chrome/browser/performance_manager/user_tuning/user_performance_tuning_manager.h
+++ b/chrome/browser/performance_manager/user_tuning/user_performance_tuning_manager.h
@@ -9,6 +9,8 @@
 #include "base/observer_list_types.h"
 #include "components/prefs/pref_change_registrar.h"
 
+class ChromeBrowserMainExtraPartsPerformanceManager;
+class PerformanceManagerMetricsProviderTest;
 class PrefService;
 
 namespace performance_manager::user_tuning {
@@ -23,6 +25,12 @@
     virtual ~FrameThrottlingDelegate() = default;
   };
 
+  class HighEfficiencyModeToggleDelegate {
+   public:
+    virtual void ToggleHighEfficiencyMode(bool enabled) = 0;
+    virtual ~HighEfficiencyModeToggleDelegate() = default;
+  };
+
   class Observer : public base::CheckedObserver {
    public:
     // Raised when the battery saver mode interventions are activated or
@@ -53,10 +61,8 @@
     virtual void OnJankThresholdReached() = 0;
   };
 
-  explicit UserPerformanceTuningManager(
-      PrefService* local_state,
-      std::unique_ptr<FrameThrottlingDelegate> frame_throttling_delegate =
-          nullptr);
+  static UserPerformanceTuningManager* GetInstance();
+
   ~UserPerformanceTuningManager();
 
   void AddObserver(Observer* o);
@@ -76,6 +82,17 @@
   bool IsBatterySaverActive() const;
 
  private:
+  friend class ::ChromeBrowserMainExtraPartsPerformanceManager;
+  friend class ::PerformanceManagerMetricsProviderTest;
+  friend class UserPerformanceTuningManagerTest;
+
+  explicit UserPerformanceTuningManager(
+      PrefService* local_state,
+      std::unique_ptr<FrameThrottlingDelegate> frame_throttling_delegate =
+          nullptr,
+      std::unique_ptr<HighEfficiencyModeToggleDelegate>
+          high_efficiency_mode_toggle_delegate = nullptr);
+
   void OnHighEfficiencyModePrefChanged();
   void OnBatterySaverModePrefChanged();
 
@@ -84,6 +101,8 @@
   bool battery_saver_mode_enabled_ = false;
   bool temporary_battery_saver_enabled_ = false;
   std::unique_ptr<FrameThrottlingDelegate> frame_throttling_delegate_;
+  std::unique_ptr<HighEfficiencyModeToggleDelegate>
+      high_efficiency_mode_toggle_delegate_;
 
   PrefChangeRegistrar pref_change_registrar_;
   base::ObserverList<Observer> observers_;
diff --git a/chrome/browser/performance_manager/user_tuning/user_performance_tuning_manager_unittest.cc b/chrome/browser/performance_manager/user_tuning/user_performance_tuning_manager_unittest.cc
index 8fd99fa..cb0429a 100644
--- a/chrome/browser/performance_manager/user_tuning/user_performance_tuning_manager_unittest.cc
+++ b/chrome/browser/performance_manager/user_tuning/user_performance_tuning_manager_unittest.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/performance_manager/user_tuning/user_performance_tuning_manager.h"
 
 #include "base/test/scoped_feature_list.h"
+#include "chrome/browser/performance_manager/user_tuning/fake_frame_throttling_delegate.h"
 #include "components/performance_manager/public/features.h"
 #include "components/performance_manager/public/user_tuning/prefs.h"
 #include "components/prefs/testing_pref_service.h"
@@ -12,19 +13,6 @@
 
 namespace performance_manager::user_tuning {
 
-class FakeFrameThrottlingDelegate
-    : public UserPerformanceTuningManager::FrameThrottlingDelegate {
- public:
-  void StartThrottlingAllFrameSinks() override { *throttling_enabled_ = true; }
-  void StopThrottlingAllFrameSinks() override { *throttling_enabled_ = false; }
-
-  explicit FakeFrameThrottlingDelegate(bool* throttling_enabled)
-      : throttling_enabled_(throttling_enabled) {}
-  ~FakeFrameThrottlingDelegate() override = default;
-
-  bool* throttling_enabled_;
-};
-
 class UserPerformanceTuningManagerTest : public testing::Test {
  public:
   void SetUp() override {
@@ -33,66 +21,64 @@
 
     performance_manager::user_tuning::prefs::RegisterLocalStatePrefs(
         local_state_.registry());
+    manager_.reset(new UserPerformanceTuningManager(
+        &local_state_,
+        std::make_unique<FakeFrameThrottlingDelegate>(&throttling_enabled_)));
   }
 
+  UserPerformanceTuningManager* manager() {
+    return UserPerformanceTuningManager::GetInstance();
+  }
+  bool throttling_enabled() const { return throttling_enabled_; }
+
   TestingPrefServiceSimple local_state_;
   base::test::ScopedFeatureList feature_list_;
+
+  bool throttling_enabled_ = false;
+  std::unique_ptr<UserPerformanceTuningManager> manager_;
 };
 
 TEST_F(UserPerformanceTuningManagerTest, TemporaryBatterySaver) {
-  bool throttling_enabled = false;
-  UserPerformanceTuningManager manager(
-      &local_state_,
-      std::make_unique<FakeFrameThrottlingDelegate>(&throttling_enabled));
-  EXPECT_FALSE(manager.IsBatterySaverActive());
-  EXPECT_FALSE(throttling_enabled);
+  EXPECT_FALSE(manager()->IsBatterySaverActive());
+  EXPECT_FALSE(throttling_enabled());
 
-  manager.SetTemporaryBatterySaver(true);
-  EXPECT_TRUE(manager.IsBatterySaverActive());
-  EXPECT_TRUE(throttling_enabled);
+  manager()->SetTemporaryBatterySaver(true);
+  EXPECT_TRUE(manager()->IsBatterySaverActive());
+  EXPECT_TRUE(throttling_enabled());
 
-  manager.SetTemporaryBatterySaver(false);
-  EXPECT_FALSE(manager.IsBatterySaverActive());
-  EXPECT_FALSE(throttling_enabled);
+  manager()->SetTemporaryBatterySaver(false);
+  EXPECT_FALSE(manager()->IsBatterySaverActive());
+  EXPECT_FALSE(throttling_enabled());
 }
 
 TEST_F(UserPerformanceTuningManagerTest, BatterySaverModePref) {
-  bool throttling_enabled = false;
-  UserPerformanceTuningManager manager(
-      &local_state_,
-      std::make_unique<FakeFrameThrottlingDelegate>(&throttling_enabled));
-  EXPECT_FALSE(manager.IsBatterySaverActive());
-  EXPECT_FALSE(throttling_enabled);
+  EXPECT_FALSE(manager()->IsBatterySaverActive());
+  EXPECT_FALSE(throttling_enabled());
 
   local_state_.SetBoolean(
       performance_manager::user_tuning::prefs::kBatterySaverModeEnabled, true);
-  EXPECT_TRUE(manager.IsBatterySaverActive());
-  EXPECT_TRUE(throttling_enabled);
+  EXPECT_TRUE(manager()->IsBatterySaverActive());
+  EXPECT_TRUE(throttling_enabled());
 
   local_state_.SetBoolean(
       performance_manager::user_tuning::prefs::kBatterySaverModeEnabled, false);
-  EXPECT_FALSE(manager.IsBatterySaverActive());
-  EXPECT_FALSE(throttling_enabled);
+  EXPECT_FALSE(manager()->IsBatterySaverActive());
+  EXPECT_FALSE(throttling_enabled());
 }
 
 TEST_F(UserPerformanceTuningManagerTest, PrefSupersedesTemporary) {
-  bool throttling_enabled = false;
-  UserPerformanceTuningManager manager(
-      &local_state_,
-      std::make_unique<FakeFrameThrottlingDelegate>(&throttling_enabled));
-
   local_state_.SetBoolean(
       performance_manager::user_tuning::prefs::kBatterySaverModeEnabled, true);
-  EXPECT_TRUE(manager.IsBatterySaverActive());
-  EXPECT_TRUE(throttling_enabled);
+  EXPECT_TRUE(manager()->IsBatterySaverActive());
+  EXPECT_TRUE(throttling_enabled());
 
-  manager.SetTemporaryBatterySaver(true);
-  EXPECT_TRUE(manager.IsBatterySaverActive());
-  EXPECT_TRUE(throttling_enabled);
+  manager()->SetTemporaryBatterySaver(true);
+  EXPECT_TRUE(manager()->IsBatterySaverActive());
+  EXPECT_TRUE(throttling_enabled());
 
-  manager.SetTemporaryBatterySaver(false);
-  EXPECT_TRUE(manager.IsBatterySaverActive());
-  EXPECT_TRUE(throttling_enabled);
+  manager()->SetTemporaryBatterySaver(false);
+  EXPECT_TRUE(manager()->IsBatterySaverActive());
+  EXPECT_TRUE(throttling_enabled());
 }
 
 }  // namespace performance_manager::user_tuning
diff --git a/chrome/browser/permissions/chrome_permission_manager_unittest.cc b/chrome/browser/permissions/chrome_permission_manager_unittest.cc
index 05b2e602..3c5b269 100644
--- a/chrome/browser/permissions/chrome_permission_manager_unittest.cc
+++ b/chrome/browser/permissions/chrome_permission_manager_unittest.cc
@@ -10,6 +10,7 @@
 #include "chrome/test/base/testing_profile.h"
 #include "components/permissions/features.h"
 #include "components/permissions/permission_manager.h"
+#include "components/permissions/permission_util.h"
 #include "extensions/buildflags/buildflags.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -63,32 +64,32 @@
 
   // "Normal" URLs are not affected by GetCanonicalOrigin.
   EXPECT_EQ(google_com,
-            GetPermissionControllerDelegate()->GetCanonicalOrigin(
+            permissions::PermissionUtil::GetCanonicalOrigin(
                 ContentSettingsType::GEOLOCATION, google_com, google_com));
   EXPECT_EQ(google_de,
-            GetPermissionControllerDelegate()->GetCanonicalOrigin(
+            permissions::PermissionUtil::GetCanonicalOrigin(
                 ContentSettingsType::GEOLOCATION, google_de, google_de));
   EXPECT_EQ(other_url,
-            GetPermissionControllerDelegate()->GetCanonicalOrigin(
+            permissions::PermissionUtil::GetCanonicalOrigin(
                 ContentSettingsType::GEOLOCATION, other_url, other_url));
   EXPECT_EQ(google_base,
-            GetPermissionControllerDelegate()->GetCanonicalOrigin(
+            permissions::PermissionUtil::GetCanonicalOrigin(
                 ContentSettingsType::GEOLOCATION, google_base, google_base));
 
   // The WebUI NTP URL gets mapped to the Google base URL.
   EXPECT_EQ(google_base,
-            GetPermissionControllerDelegate()->GetCanonicalOrigin(
+            permissions::PermissionUtil::GetCanonicalOrigin(
                 ContentSettingsType::GEOLOCATION, webui_ntp, top_level_ntp));
 
   // chrome-search://remote-ntp and other URLs are not affected.
   EXPECT_EQ(remote_ntp,
-            GetPermissionControllerDelegate()->GetCanonicalOrigin(
+            permissions::PermissionUtil::GetCanonicalOrigin(
                 ContentSettingsType::GEOLOCATION, remote_ntp, top_level_ntp));
   EXPECT_EQ(google_com,
-            GetPermissionControllerDelegate()->GetCanonicalOrigin(
+            permissions::PermissionUtil::GetCanonicalOrigin(
                 ContentSettingsType::GEOLOCATION, google_com, top_level_ntp));
   EXPECT_EQ(other_chrome_search,
-            GetPermissionControllerDelegate()->GetCanonicalOrigin(
+            permissions::PermissionUtil::GetCanonicalOrigin(
                 ContentSettingsType::GEOLOCATION, other_chrome_search,
                 top_level_ntp));
 }
@@ -99,19 +100,17 @@
 
   // The embedding origin should be returned except in the case of notifications
   // and, if they're enabled, extensions.
-  EXPECT_EQ(embedding_origin,
-            GetPermissionControllerDelegate()->GetCanonicalOrigin(
-                ContentSettingsType::GEOLOCATION, requesting_origin,
-                embedding_origin));
-  EXPECT_EQ(requesting_origin,
-            GetPermissionControllerDelegate()->GetCanonicalOrigin(
-                ContentSettingsType::NOTIFICATIONS, requesting_origin,
-                embedding_origin));
+  EXPECT_EQ(embedding_origin, permissions::PermissionUtil::GetCanonicalOrigin(
+                                  ContentSettingsType::GEOLOCATION,
+                                  requesting_origin, embedding_origin));
+  EXPECT_EQ(requesting_origin, permissions::PermissionUtil::GetCanonicalOrigin(
+                                   ContentSettingsType::NOTIFICATIONS,
+                                   requesting_origin, embedding_origin));
 #if BUILDFLAG(ENABLE_EXTENSIONS)
   const GURL extensions_requesting_origin(
       "chrome-extension://abcdefghijklmnopqrstuvxyz");
   EXPECT_EQ(extensions_requesting_origin,
-            GetPermissionControllerDelegate()->GetCanonicalOrigin(
+            permissions::PermissionUtil::GetCanonicalOrigin(
                 ContentSettingsType::GEOLOCATION, extensions_requesting_origin,
                 embedding_origin));
 #endif
diff --git a/chrome/browser/permissions/chrome_permissions_client.cc b/chrome/browser/permissions/chrome_permissions_client.cc
index 5b7804cf..994b78d9 100644
--- a/chrome/browser/permissions/chrome_permissions_client.cc
+++ b/chrome/browser/permissions/chrome_permissions_client.cc
@@ -21,7 +21,6 @@
 #include "chrome/browser/permissions/contextual_notification_permission_ui_selector.h"
 #include "chrome/browser/permissions/permission_actions_history_factory.h"
 #include "chrome/browser/permissions/permission_decision_auto_blocker_factory.h"
-#include "chrome/browser/permissions/permission_manager_factory.h"
 #include "chrome/browser/permissions/permission_revocation_request.h"
 #include "chrome/browser/permissions/prediction_based_permission_ui_selector.h"
 #include "chrome/browser/permissions/pref_notification_permission_ui_selector.h"
@@ -208,12 +207,6 @@
       Profile::FromBrowserContext(browser_context));
 }
 
-permissions::PermissionManager* ChromePermissionsClient::GetPermissionManager(
-    content::BrowserContext* browser_context) {
-  return PermissionManagerFactory::GetForProfile(
-      Profile::FromBrowserContext(browser_context));
-}
-
 double ChromePermissionsClient::GetSiteEngagementScore(
     content::BrowserContext* browser_context,
     const GURL& origin) {
@@ -442,7 +435,7 @@
   return absl::nullopt;
 }
 
-bool ChromePermissionsClient::DoOriginsMatchNewTabPage(
+bool ChromePermissionsClient::DoURLsMatchNewTabPage(
     const GURL& requesting_origin,
     const GURL& embedding_origin) {
   return embedding_origin ==
diff --git a/chrome/browser/permissions/chrome_permissions_client.h b/chrome/browser/permissions/chrome_permissions_client.h
index 788d851..f0ffb9f 100644
--- a/chrome/browser/permissions/chrome_permissions_client.h
+++ b/chrome/browser/permissions/chrome_permissions_client.h
@@ -30,8 +30,6 @@
       content::BrowserContext* browser_context) override;
   permissions::PermissionDecisionAutoBlocker* GetPermissionDecisionAutoBlocker(
       content::BrowserContext* browser_context) override;
-  permissions::PermissionManager* GetPermissionManager(
-      content::BrowserContext* browser_context) override;
   permissions::ObjectPermissionContextBase* GetChooserContext(
       content::BrowserContext* browser_context,
       ContentSettingsType type) override;
@@ -74,8 +72,10 @@
   absl::optional<GURL> OverrideCanonicalOrigin(
       const GURL& requesting_origin,
       const GURL& embedding_origin) override;
-  bool DoOriginsMatchNewTabPage(const GURL& requesting_origin,
-                                const GURL& embedding_origin) override;
+  // Checks if `requesting_origin` and `embedding_origin` are the new tab page
+  // origins.
+  bool DoURLsMatchNewTabPage(const GURL& requesting_origin,
+                             const GURL& embedding_origin) override;
 #if BUILDFLAG(IS_ANDROID)
   bool IsDseOrigin(content::BrowserContext* browser_context,
                    const url::Origin& origin) override;
diff --git a/chrome/browser/policy/test/content_settings_policy_browsertest.cc b/chrome/browser/policy/test/content_settings_policy_browsertest.cc
index d95ea47..31e197b 100644
--- a/chrome/browser/policy/test/content_settings_policy_browsertest.cc
+++ b/chrome/browser/policy/test/content_settings_policy_browsertest.cc
@@ -23,6 +23,7 @@
 #include "components/policy/core/common/policy_map.h"
 #include "components/policy/policy_constants.h"
 #include "content/public/browser/permission_controller.h"
+#include "content/public/browser/permission_result.h"
 #include "content/public/browser/render_widget_host_view.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_switches.h"
@@ -392,8 +393,10 @@
     content::PermissionController* permission_controller =
         browser()->profile()->GetPermissionController();
     EXPECT_EQ(
-        permission_controller->GetPermissionStatusForOriginWithoutContext(
-            blink::PermissionType::SENSORS, url::Origin::Create(GURL(url))),
+        permission_controller
+            ->GetPermissionResultForOriginWithoutContext(
+                blink::PermissionType::SENSORS, url::Origin::Create(GURL(url)))
+            .status,
         status);
   }
 
diff --git a/chrome/browser/profiles/profile_keyed_service_factory.h b/chrome/browser/profiles/profile_keyed_service_factory.h
index e515de3..c7e3320 100644
--- a/chrome/browser/profiles/profile_keyed_service_factory.h
+++ b/chrome/browser/profiles/profile_keyed_service_factory.h
@@ -33,7 +33,8 @@
 //   MyRedirectingKeyedServiceFactory()
 //       : ProfileKeyedServiceFactory(
 //             "MyRedirectingKeyedService",
-//             ProfileSelections::BuildServicesRedirectedInIncognito()) {}
+//             ProfileSelections::BuildRedirectedInIncognitoNonExperimental())
+//             {}
 //   }
 // };
 //
diff --git a/chrome/browser/profiles/profile_keyed_service_factory_unittest.cc b/chrome/browser/profiles/profile_keyed_service_factory_unittest.cc
index 08982ac..2b0b422 100644
--- a/chrome/browser/profiles/profile_keyed_service_factory_unittest.cc
+++ b/chrome/browser/profiles/profile_keyed_service_factory_unittest.cc
@@ -104,7 +104,7 @@
   PredefinedProfileSelectionsFactoryTest()
       : ProfileKeyedServiceFactoryTest(
             "PredefinedProfileSelectionsFactoryTest",
-            ProfileSelections::BuildServicesRedirectedToOriginal()) {}
+            ProfileSelections::BuildRedirectedInIncognito()) {}
 };
 
 TEST_F(ProfileKeyedServiceFactoryUnittest,
diff --git a/chrome/browser/profiles/profile_selections.cc b/chrome/browser/profiles/profile_selections.cc
index 88d332ec..40b7d03 100644
--- a/chrome/browser/profiles/profile_selections.cc
+++ b/chrome/browser/profiles/profile_selections.cc
@@ -44,30 +44,29 @@
 ProfileSelections::~ProfileSelections() = default;
 ProfileSelections::ProfileSelections(const ProfileSelections& other) = default;
 
-ProfileSelections ProfileSelections::BuildDefault() {
-  return ProfileSelections::Builder().Build();
-}
-
-ProfileSelections ProfileSelections::BuildServicesForAllProfiles() {
+ProfileSelections ProfileSelections::BuildForAllProfiles() {
   return ProfileSelections::Builder()
       .WithRegular(ProfileSelection::kOwnInstance)
+      .WithGuest(ProfileSelection::kOwnInstance)
+      .WithSystem(ProfileSelection::kOwnInstance)
       .Build();
 }
 
-ProfileSelections ProfileSelections::BuildNoServicesForAllProfiles() {
+ProfileSelections ProfileSelections::BuildNoProfilesSelected() {
   return ProfileSelections::Builder()
       .WithRegular(ProfileSelection::kNone)
       .Build();
 }
 
-ProfileSelections ProfileSelections::BuildServicesForRegularProfile() {
+ProfileSelections ProfileSelections::BuildForRegularProfile() {
   return ProfileSelections::Builder()
       .WithGuest(ProfileSelection::kNone)
       .WithSystem(ProfileSelection::kNone)
       .Build();
 }
 
-ProfileSelections ProfileSelections::BuildServicesRedirectedInIncognito() {
+ProfileSelections
+ProfileSelections::BuildRedirectedInIncognitoNonExperimental() {
   return ProfileSelections::Builder()
       .WithRegular(ProfileSelection::kRedirectedToOriginal)
       .WithGuest(ProfileSelection::kNone)
@@ -75,12 +74,48 @@
       .Build();
 }
 
-ProfileSelections ProfileSelections::BuildServicesRedirectedToOriginal() {
+ProfileSelections ProfileSelections::BuildRedirectedToOriginal() {
   return ProfileSelections::Builder()
       .WithRegular(ProfileSelection::kRedirectedToOriginal)
+      .WithGuest(ProfileSelection::kRedirectedToOriginal)
+      .WithSystem(ProfileSelection::kRedirectedToOriginal)
       .Build();
 }
 
+ProfileSelections ProfileSelections::BuildDefault(bool force_guest,
+                                                  bool force_system) {
+  Builder builder;
+  if (force_guest)
+    builder.WithGuest(ProfileSelection::kOriginalOnly);
+  if (force_system)
+    builder.WithSystem(ProfileSelection::kOriginalOnly);
+  return builder.Build();
+}
+
+ProfileSelections ProfileSelections::BuildRedirectedInIncognito(
+    bool force_guest,
+    bool force_system) {
+  Builder builder;
+  builder.WithRegular(ProfileSelection::kRedirectedToOriginal);
+  if (force_guest)
+    builder.WithGuest(ProfileSelection::kRedirectedToOriginal);
+  if (force_system)
+    builder.WithSystem(ProfileSelection::kRedirectedToOriginal);
+  return builder.Build();
+}
+
+ProfileSelections ProfileSelections::BuildForRegularAndIncognito(
+    bool force_guest,
+    bool force_system) {
+  Builder builder;
+  builder.WithRegular(ProfileSelection::kOwnInstance);
+  if (force_guest)
+    builder.WithGuest(ProfileSelection::kOwnInstance);
+  if (force_system)
+    builder.WithSystem(ProfileSelection::kOwnInstance);
+  return builder.Build();
+}
+
 Profile* ProfileSelections::ApplyProfileSelection(Profile* profile) const {
   DCHECK(profile);
 
diff --git a/chrome/browser/profiles/profile_selections.h b/chrome/browser/profiles/profile_selections.h
index fa3beeb..35d5d2ce 100644
--- a/chrome/browser/profiles/profile_selections.h
+++ b/chrome/browser/profiles/profile_selections.h
@@ -9,8 +9,15 @@
 
 class Profile;
 
-// Enum used to map the logic of selecting the right profile for the service to
-// be created for, based on the given profile.
+// The class `ProfileSelections` and enum `ProfileSelection` are not coupled
+// with the usage of `ProfileKeyedServiceFactory`, however the experiment of
+// changing the default value of `ProfileSelections` behavior is mainly done for
+// the `ProfileKeyedServiceFactory`.
+// If other usages are needed it is best not to use the builders that contains
+// experimental code (mentioned below).
+
+// Enum used to map the logic of selecting the right profile based on the given
+// profile.
 enum class ProfileSelection {
   kNone,                  // Original: No Profile  --  OTR: No Profile
   kOriginalOnly,          // Original: Self        --  OTR: No Profile
@@ -32,35 +39,160 @@
 
   // - Predefined `ProfileSelections`
 
-  // Default implementation, as of now:
-  // - No services in OTR.
-  // - Regular profile returns itself (original).
-  // - Guest and System profiles follow Regular profile behaviour.
+  // Regular builders (independent of the experiments):
+
+  // All Profiles are selected.
+  // +---------+------------+------------+
+  // |         |  Original  |    OTR     |
+  // +---------+------------+------------+
+  // | Regular | self       | self       |
+  // | Guest   | self       | self       |
+  // | System  | self       | self       |
+  // +---------+------------+------------+
+  static ProfileSelections BuildForAllProfiles();
+
+  // No Profiles are selected.
+  // +---------+------------+------------+
+  // |         |  Original  |    OTR     |
+  // +---------+------------+------------+
+  // | Regular | no profile | no profile |
+  // | Guest   | no profile | no profile |
+  // | System  | no profile | no profile |
+  // +---------+------------+------------+
+  static ProfileSelections BuildNoProfilesSelected();
+
+  // Only select the regular profile.
+  // +---------+------------+------------+
+  // |         |  Original  |    OTR     |
+  // +---------+------------+------------+
+  // | Regular | self       | no profile |
+  // | Guest   | no profile | no profile |
+  // | System  | no profile | no profile |
+  // +---------+------------+------------+
+  static ProfileSelections BuildForRegularProfile();
+
+  // Redirects incognito profiles to their original regular profile. No
+  // profiles for Guest and System profiles. "NonExperimental" is added to
+  // differentiate with the experimental behavior during the experiment, once
+  // done it will be the equivalent builder.
+  // +---------+------------+------------+
+  // |         |  Original  |    OTR     |
+  // +---------+------------+------------+
+  // | Regular | self       | original   |
+  // | Guest   | no profile | no profile |
+  // | System  | no profile | no profile |
+  // +---------+------------+------------+
+  static ProfileSelections BuildRedirectedInIncognitoNonExperimental();
+
+  // Redirects all OTR profiles to their original profiles.
+  // Includes all profile types (Regular, Guest and System).
+  // +---------+------------+------------+
+  // |         |  Original  |    OTR     |
+  // +---------+------------+------------+
+  // | Regular | self       | original   |
+  // | Guest   | self       | original   |
+  // | System  | self       | original   |
+  // +---------+------------+------------+
+  static ProfileSelections BuildRedirectedToOriginal();
+
+  // Experimental builders:
+  //
+  // Experimental builders (should only be used for the transition from
+  // `BrowserContextKeyedServiceFactory` to `ProfileKeyedServiceFactory`):
+  // The following builder will contain experimental code indirectly, by not
+  // giving a value to Guest and System Profile (unless forced by parameters).
+  // The experiment is targeted to affect usages on
+  // `ProfileKeyedServiceFactory`. During the experiment phase, these builders
+  // will not have very accurate function names, the name is based on the end
+  // result behavior the experiment enforced behavior. With/Without experiment
+  // behavior is described per experimental builder. The parameters force_* will
+  // allow to have an easier transition when adapting to the experiment.
+
+  // Default implementation, without the experiment:
+  // +---------+------------+------------+
+  // |         |  Original  |    OTR     |
+  // +---------+------------+------------+
+  // | Regular | self       | no profile |
+  // | Guest   | self       | no profile |
+  // | System  | self       | no profile |
+  // +---------+------------+------------+
   //
   // After the migration(crbug.com/1284664) this default behaviour will change.
-  // It will be similar to the current `BuildServicesForRegularProfile()`.
-  // - No services in OTR.
-  // - Regular profile returns itself (original).
-  // - No services for Guest and System profile.
-  static ProfileSelections BuildDefault();
+  // +---------+------------+------------+
+  // |         |  Original  |    OTR     |
+  // +---------+------------+------------+
+  // | Regular | self       | no profile |
+  // | Guest   | no profile | no profile |
+  // | System  | no profile | no profile |
+  // +---------+------------+------------+
+  //
+  // Parameters: (used during the experiment)
+  // - force_guest: true, force Guest with `ProfileSelection::kOriginalOnly`.
+  // - force_system: true, force System with `ProfileSelection::kOriginalOnly`.
+  static ProfileSelections BuildDefault(bool force_guest = false,
+                                        bool force_system = false);
 
-  // Services available for all profiles.
-  static ProfileSelections BuildServicesForAllProfiles();
+  // Without the experiment:
+  // - Returns Regular for Regular, incognito and other regular OTR profiles.
+  // - Returns Guest Original for GuestOriginal  and GuestOTR (same as Regular).
+  // - Returns System Original for SystemOriginal  and SystemOTR (same as
+  // Regular).
+  //
+  // With the experiment:
+  // - Returns Regular for Regular, incognito and other regular OTR profiles.
+  // - Return nullptr for all Guest and System profiles.
+  //
+  // Without the experiment:
+  // +---------+------------+------------+
+  // |         |  Original  |    OTR     |
+  // +---------+------------+------------+
+  // | Regular | self       | original   |
+  // | Guest   | self       | original   |
+  // | System  | self       | original   |
+  // +---------+------------+------------+
+  //
+  // With the experiment:
+  // +---------+------------+------------+
+  // |         |  Original  |    OTR     |
+  // +---------+------------+------------+
+  // | Regular | self       | original   |
+  // | Guest   | no profile | no profile |
+  // | System  | no profile | no profile |
+  // +---------+------------+------------+
+  //
+  // Parameters: (used during the experiment)
+  // - force_guest: true, force Guest with
+  // `ProfileSelecion::kRedirectedToOriginal`.
+  // - force_system: true, force System with
+  // `ProfileSelecion::kRedirectedToOriginal`.
+  static ProfileSelections BuildRedirectedInIncognito(
+      bool force_guest = false,
+      bool force_system = false);
 
-  // No services for all profiles.
-  static ProfileSelections BuildNoServicesForAllProfiles();
-
-  // Only build services for the regular profile.
-  static ProfileSelections BuildServicesForRegularProfile();
-
-  // Redirects building services for regular off the record profiles (incognito
-  // and other off the record profiles) to regular Profile.  Doesn't build
-  // services for Guest and System profiles.
-  static ProfileSelections BuildServicesRedirectedInIncognito();
-
-  // Redirects building services for both OTR and Original profile to Original
-  // Profile for all profile types (Regular, Guest and System).
-  static ProfileSelections BuildServicesRedirectedToOriginal();
+  // Without the experiment:
+  // +---------+------------+------------+
+  // |         |  Original  |    OTR     |
+  // +---------+------------+------------+
+  // | Regular | self       | self       |
+  // | Guest   | self       | self       |
+  // | System  | self       | self       |
+  // +---------+------------+------------+
+  //
+  // With the experiment:
+  // +---------+------------+------------+
+  // |         |  Original  |    OTR     |
+  // +---------+------------+------------+
+  // | Regular | self       | self       |
+  // | Guest   | no profile | no profile |
+  // | System  | no profile | no profile |
+  // +---------+------------+------------+
+  //
+  // Parameters: (used during the experiment)
+  // - force_guest: true, force Guest with `ProfileSelecion::kOwnInstance`.
+  // - force_system: true, force System with `ProfileSelecion::kOwnInstance`.
+  static ProfileSelections BuildForRegularAndIncognito(
+      bool force_guest = false,
+      bool force_system = false);
 
   // Builder to construct the `ProfileSelections` parameters.
   class Builder {
diff --git a/chrome/browser/profiles/profile_selections_unittest.cc b/chrome/browser/profiles/profile_selections_unittest.cc
index 8ac6354..e7c126a 100644
--- a/chrome/browser/profiles/profile_selections_unittest.cc
+++ b/chrome/browser/profiles/profile_selections_unittest.cc
@@ -49,21 +49,6 @@
   ProfileTestingHelper profile_testing_helper_;
 };
 
-TEST_F(ProfileSelectionsTest, DefaultImplementation) {
-  ProfileSelections selections = ProfileSelections::BuildDefault();
-
-  TestProfileSelection(selections, regular_profile(), regular_profile());
-  TestProfileSelection(selections, incognito_profile(), nullptr);
-
-  TestProfileSelection(selections, guest_profile(), guest_profile());
-  TestProfileSelection(selections, guest_profile_otr(), nullptr);
-
-#if !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_ANDROID)
-  TestProfileSelection(selections, system_profile(), system_profile());
-  TestProfileSelection(selections, system_profile_otr(), nullptr);
-#endif  // !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_ANDROID)
-}
-
 TEST_F(ProfileSelectionsTest, CustomImplementation) {
   ProfileSelections selections =
       ProfileSelections::Builder()
@@ -85,8 +70,7 @@
 }
 
 TEST_F(ProfileSelectionsTest, OnlyRegularProfile) {
-  ProfileSelections selections =
-      ProfileSelections::BuildServicesForRegularProfile();
+  ProfileSelections selections = ProfileSelections::BuildForRegularProfile();
 
   TestProfileSelection(selections, regular_profile(), regular_profile());
   TestProfileSelection(selections, incognito_profile(), nullptr);
@@ -102,7 +86,7 @@
 
 TEST_F(ProfileSelectionsTest, RedirectedInIncognito) {
   ProfileSelections selections =
-      ProfileSelections::BuildServicesRedirectedInIncognito();
+      ProfileSelections::BuildRedirectedInIncognitoNonExperimental();
 
   TestProfileSelection(selections, regular_profile(), regular_profile());
   TestProfileSelection(selections, incognito_profile(), regular_profile());
@@ -117,8 +101,7 @@
 }
 
 TEST_F(ProfileSelectionsTest, RedirectedToOriginal) {
-  ProfileSelections selections =
-      ProfileSelections::BuildServicesRedirectedToOriginal();
+  ProfileSelections selections = ProfileSelections::BuildRedirectedToOriginal();
 
   TestProfileSelection(selections, regular_profile(), regular_profile());
   TestProfileSelection(selections, incognito_profile(), regular_profile());
@@ -133,8 +116,7 @@
 }
 
 TEST_F(ProfileSelectionsTest, ForAllProfiles) {
-  ProfileSelections selections =
-      ProfileSelections::BuildServicesForAllProfiles();
+  ProfileSelections selections = ProfileSelections::BuildForAllProfiles();
 
   TestProfileSelection(selections, regular_profile(), regular_profile());
   TestProfileSelection(selections, incognito_profile(), incognito_profile());
@@ -149,8 +131,7 @@
 }
 
 TEST_F(ProfileSelectionsTest, NoProfiles) {
-  ProfileSelections selections =
-      ProfileSelections::BuildNoServicesForAllProfiles();
+  ProfileSelections selections = ProfileSelections::BuildNoProfilesSelected();
 
   TestProfileSelection(selections, regular_profile(), nullptr);
   TestProfileSelection(selections, incognito_profile(), nullptr);
@@ -163,3 +144,70 @@
   TestProfileSelection(selections, system_profile_otr(), nullptr);
 #endif  // !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_ANDROID)
 }
+
+// Testing Experimental Builders.
+// As long as the experiments are not active, force values will not have an
+// effect on the expected values, the tests will be adapted to reflect that when
+// taking into account the experiment.
+class ProfileSelectionsTestWithParams
+    : public ProfileSelectionsTest,
+      public ::testing::WithParamInterface<std::tuple<bool, bool>> {};
+
+TEST_P(ProfileSelectionsTestWithParams, BuildDefault) {
+  bool force_guest = std::get<0>(GetParam());
+  bool force_system = std::get<1>(GetParam());
+  ProfileSelections selections =
+      ProfileSelections::BuildDefault(force_guest, force_system);
+
+  TestProfileSelection(selections, regular_profile(), regular_profile());
+  TestProfileSelection(selections, incognito_profile(), nullptr);
+
+  TestProfileSelection(selections, guest_profile(), guest_profile());
+  TestProfileSelection(selections, guest_profile_otr(), nullptr);
+
+#if !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_ANDROID)
+  TestProfileSelection(selections, system_profile(), system_profile());
+  TestProfileSelection(selections, system_profile_otr(), nullptr);
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_ANDROID)
+}
+
+TEST_P(ProfileSelectionsTestWithParams, BuildRedirectedInIncognito) {
+  bool force_guest = std::get<0>(GetParam());
+  bool force_system = std::get<1>(GetParam());
+  ProfileSelections selections =
+      ProfileSelections::BuildRedirectedInIncognito(force_guest, force_system);
+
+  TestProfileSelection(selections, regular_profile(), regular_profile());
+  TestProfileSelection(selections, incognito_profile(), regular_profile());
+
+  TestProfileSelection(selections, guest_profile(), guest_profile());
+  TestProfileSelection(selections, guest_profile_otr(), guest_profile());
+
+#if !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_ANDROID)
+  TestProfileSelection(selections, system_profile(), system_profile());
+  TestProfileSelection(selections, system_profile_otr(), system_profile());
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_ANDROID)
+}
+
+TEST_P(ProfileSelectionsTestWithParams, BuildForRegularAndIncognito) {
+  bool force_guest = std::get<0>(GetParam());
+  bool force_system = std::get<1>(GetParam());
+  ProfileSelections selections =
+      ProfileSelections::BuildForRegularAndIncognito(force_guest, force_system);
+
+  TestProfileSelection(selections, regular_profile(), regular_profile());
+  TestProfileSelection(selections, incognito_profile(), incognito_profile());
+
+  TestProfileSelection(selections, guest_profile(), guest_profile());
+  TestProfileSelection(selections, guest_profile_otr(), guest_profile_otr());
+
+#if !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_ANDROID)
+  TestProfileSelection(selections, system_profile(), system_profile());
+  TestProfileSelection(selections, system_profile_otr(), system_profile_otr());
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH) && !BUILDFLAG(IS_ANDROID)
+}
+
+INSTANTIATE_TEST_SUITE_P(ExperimentalBuilders,
+                         ProfileSelectionsTestWithParams,
+                         ::testing::Combine(::testing::Bool(),
+                                            ::testing::Bool()));
diff --git a/chrome/browser/push_messaging/push_messaging_service_impl.cc b/chrome/browser/push_messaging/push_messaging_service_impl.cc
index 993582aa0..bbbf4d8a 100644
--- a/chrome/browser/push_messaging/push_messaging_service_impl.cc
+++ b/chrome/browser/push_messaging/push_messaging_service_impl.cc
@@ -55,6 +55,7 @@
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/devtools_background_services_context.h"
 #include "content/public/browser/permission_controller.h"
+#include "content/public/browser/permission_result.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/service_worker_context.h"
@@ -961,8 +962,9 @@
         url::Origin::Create(origin));
   } else {
     return profile_->GetPermissionController()
-        ->GetPermissionStatusForOriginWithoutContext(
-            blink::PermissionType::NOTIFICATIONS, url::Origin::Create(origin));
+        ->GetPermissionResultForOriginWithoutContext(
+            blink::PermissionType::NOTIFICATIONS, url::Origin::Create(origin))
+        .status;
   }
 }
 
diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu.cc b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
index 48833e05..754df5997 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu.cc
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
@@ -3241,8 +3241,9 @@
 
   if (!base::FeatureList::IsEnabled(
           lens::features::kEnableRegionSearchOnPdfViewer) &&
-      IsFrameInPdfViewer(GetRenderFrameHost()))
+      IsFrameInPdfViewer(GetRenderFrameHost())) {
     return false;
+  }
 
   const TemplateURL* provider = service->GetDefaultSearchProvider();
   const bool provider_supports_image_search =
@@ -3587,14 +3588,12 @@
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
   if (!lens_region_search_controller_) {
     Browser* browser = GetBrowser();
-    WebContents* web_contents;
+    WebContents* web_contents = source_web_contents_;
     if (base::FeatureList::IsEnabled(
             lens::features::kEnableRegionSearchOnPdfViewer)) {
-      // We don't use |source_web_contents_| here because it doesn't work with
+      // We don't use `source_web_contents_` here because it doesn't work with
       // the PDF reader.
       web_contents = browser->tab_strip_model()->GetActiveWebContents();
-    } else {
-      web_contents = source_web_contents_;
     }
     lens_region_search_controller_ =
         std::make_unique<lens::LensRegionSearchController>(web_contents,
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
index 37e1d397..16032ed5 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
@@ -21,7 +21,6 @@
 chromevox_modules = [
   "../common/automation_predicate.js",
   "../common/constants.js",
-  "../common/cursors/recovery_strategy.js",
   "background/braille/cursor_dots.js",
   "background/braille/pan_strategy.js",
   "common/abstract_earcons.js",
@@ -40,6 +39,7 @@
   "../common/automation_util.js",
   "../common/cursors/cursor.js",
   "../common/cursors/range.js",
+  "../common/cursors/recovery_strategy.js",
   "../common/event_generator.js",
   "../common/instance_checker.js",
   "../common/key_code.js",
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/auto_scroll_handler_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/auto_scroll_handler_test.js
index 9e54419..0cff5bd 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/auto_scroll_handler_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/auto_scroll_handler_test.js
@@ -218,8 +218,8 @@
       .call(doCmd('nextObject'))
       .expectSpeech('4th item')
       .call(doCmd('nextObject'))
-      .expectSpeech('5th item')
-      .replay();
+      .expectSpeech('5th item');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F(
@@ -238,8 +238,8 @@
           .call(doCmd('nextObject'))
           .expectSpeech('hello')
           .call(doCmd('nextObject'))
-          .expectSpeech('world')
-          .replay();
+          .expectSpeech('world');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -252,8 +252,8 @@
           .call(doCmd('nextObject'))  // scroll forward
           .expectSpeech('3rd item')
           .call(doCmd('previousObject'))  // scroll backward
-          .expectSpeech('2nd item')
-          .replay();
+          .expectSpeech('2nd item');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -270,8 +270,8 @@
           .call(doCmd('previousWord'))  // scroll backward
           .expectSpeech('item')
           .call(doCmd('previousWord'))
-          .expectSpeech('2nd')
-          .replay();
+          .expectSpeech('2nd');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -299,8 +299,8 @@
           .call(doCmd('previousCharacter'))  // scroll backward
           .expectSpeech('m')
           .call(doCmd('previousCharacter'))
-          .expectSpeech('e')
-          .replay();
+          .expectSpeech('e');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -318,6 +318,6 @@
           .call(doCmd('nextSimilarItem'))  // scroll forward
           .expectSpeech('3rd item')
           .call(doCmd('previousSimilarItem'))  // scroll backward
-          .expectSpeech('2nd item')
-          .replay();
+          .expectSpeech('2nd item');
+      await mockFeedback.replay();
     });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js
index 1ee59192..32c57c5e 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js
@@ -311,7 +311,7 @@
           .expectSpeech('of test')
           .expectBraille('of test');
 
-      mockFeedback.replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'CaretNavigation', async function() {
@@ -338,7 +338,7 @@
   mockFeedback.call(doCmd('previousCharacter')).expectSpeech('a', 'Link');
   mockFeedback.call(doCmd('previousCharacter')).expectSpeech('t');
   mockFeedback.call(doCmd('nextWord')).expectSpeech('echo', 'Link');
-  mockFeedback.replay();
+  await mockFeedback.replay();
 });
 
 /** Tests that individual buttons are stops for move-by-word functionality. */
@@ -366,7 +366,7 @@
       mockFeedback.call(doCmd('previousWord')).expectSpeech('one');
       mockFeedback.call(doCmd('previousWord')).expectSpeech('button');
       mockFeedback.call(doCmd('previousWord')).expectSpeech('hello');
-      mockFeedback.replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'SelectSingleBasic', async function() {
@@ -380,7 +380,7 @@
       .call(press(KeyCode.DOWN))
       .expectSpeech('banana', /3 of 3/)
       .expectBraille('banana 3/3');
-  mockFeedback.replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'ContinuousRead', async function() {
@@ -390,14 +390,14 @@
       .call(doCmd('readFromHere'))
       .expectSpeech(
           'start', 'alpha', 'Link', 'beta', 'Link', 'charlie', 'Heading 1');
-  mockFeedback.replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'InitialFocus', async function() {
   const mockFeedback = this.createMockFeedback();
   await this.runWithLoadedTree('<a href="a">a</a>');
   mockFeedback.expectSpeech('a').expectSpeech('Link');
-  mockFeedback.replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'AriaLabel', async function() {
@@ -409,7 +409,7 @@
       .expectSpeech('Link')
       .expectSpeech('Press Search+Space to activate')
       .expectBraille('foo lnk');
-  mockFeedback.replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'ShowContextMenu', async function() {
@@ -423,7 +423,7 @@
       .expectSpeech(/menu opened/)
       .call(press(KeyCode.ESCAPE))
       .expectSpeech('a', 'Link');
-  mockFeedback.replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'BrailleRouting', async function() {
@@ -462,7 +462,7 @@
       .call(function() {
         assertEquals(3, textField.textSelStart);
       });
-  mockFeedback.replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'FocusInputElement', async function() {
@@ -484,7 +484,7 @@
       .call(focus(name))
       .expectNextSpeechUtteranceIsNot('Blue')
       .expectSpeech('Lancelot', 'Edit text');
-  mockFeedback.replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'UseEditableState', async function() {
@@ -549,7 +549,7 @@
       .expectSpeech(/Slider/)
       .expectEarcon(Earcon.SLIDER);
 
-  mockFeedback.replay();
+  await mockFeedback.replay();
 });
 
 TEST_F('ChromeVoxBackgroundTest', 'GlobsToRegExp', function() {
@@ -632,8 +632,8 @@
       .expectNextSpeechUtteranceIsNot('noisy')
       .call(focus(slider))
       .expectSpeech('noisy')
-      .expectSpeech('noisy')
-      .replay();
+      .expectSpeech('noisy');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'Checkbox', async function() {
@@ -666,15 +666,16 @@
       .call(doDefault(cbx))
       .expectSpeech('go')
       .expectSpeech('Check box')
-      .expectSpeech('Not checked')
-      .replay();
+      .expectSpeech('Not checked');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'MixedCheckbox', async function() {
   const mockFeedback = this.createMockFeedback();
   const site = '<div id="go" role="checkbox" aria-checked="mixed">go</div>';
   const root = await this.runWithLoadedTree(site);
-  mockFeedback.expectSpeech('go', 'Check box', 'Partially checked').replay();
+  mockFeedback.expectSpeech('go', 'Check box', 'Partially checked');
+  await mockFeedback.replay();
 });
 
 /** Tests navigating into and out of iframes using nextButton */
@@ -684,7 +685,7 @@
       const mockFeedback = this.createMockFeedback();
 
       let running = false;
-      const runTestIfIframeIsLoaded = function(rootNode) {
+      const runTestIfIframeIsLoaded = async function(rootNode) {
         if (running) {
           return;
         }
@@ -707,7 +708,7 @@
             .expectSpeech('Inside', 'Button');
         mockFeedback.call(doCmd('previousButton'))
             .expectSpeech('Before', 'Button');
-        mockFeedback.replay();
+        await mockFeedback.replay();
       }.bind(this);
 
       const rootNode = await this.runWithLoadedTree(this.iframesDoc);
@@ -727,7 +728,7 @@
       const mockFeedback = this.createMockFeedback();
 
       let running = false;
-      const runTestIfIframeIsLoaded = function(rootNode) {
+      const runTestIfIframeIsLoaded = async function(rootNode) {
         if (running) {
           return;
         }
@@ -759,7 +760,7 @@
             .expectSpeech('Inside', 'Button');
         mockFeedback.call(doCmd('previousObject'))
             .expectSpeech('Before', 'Button');
-        mockFeedback.replay();
+        await mockFeedback.replay();
       }.bind(this);
 
       const rootNode = await this.runWithLoadedTree(this.iframesDoc);
@@ -800,8 +801,8 @@
       .expectSpeech('Expanded')
       .call(selectLastOption)
       .expectNextSpeechUtteranceIsNot('apple')
-      .expectSpeech('grapefruit')
-      .replay();
+      .expectSpeech('grapefruit');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'ToggleButton', async function() {
@@ -832,9 +833,9 @@
 
       .call(move)
       .expectSpeech('close')
-      .expectSpeech('Button')
+      .expectSpeech('Button');
 
-      .replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'EditText', async function() {
@@ -849,15 +850,15 @@
   mockFeedback.call(nextEditText)
       .expectSpeech('Combo box')
       .call(previousEditText)
-      .expectSpeech('Edit text')
-      .replay();
+      .expectSpeech('Edit text');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'ComboBox', async function() {
   const mockFeedback = this.createMockFeedback();
   await this.runWithLoadedTree(this.comboBoxDoc);
-  mockFeedback.expectSpeech('Edit text', 'Choose an item', 'Combo box')
-      .replay();
+  mockFeedback.expectSpeech('Edit text', 'Choose an item', 'Combo box');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'BackwardForwardSync', async function() {
@@ -888,8 +889,8 @@
       .call(this.doCmd('previousObject'))
       .expectSpeech('Edit text')
       .call(this.doCmd('previousObject'))
-      .expectSpeech('Group')
-      .replay();
+      .expectSpeech('Group');
+  await mockFeedback.replay();
 });
 
 /** Tests that navigation works when the current object disappears. */
@@ -927,7 +928,7 @@
           .expectSpeech('Before3');
   */
 
-  mockFeedback.replay();
+  await mockFeedback.replay();
 });
 
 /** Tests that focus jumps to details properly when indicated. */
@@ -935,7 +936,7 @@
   const mockFeedback = this.createMockFeedback();
   const rootNode = await this.runWithLoadedTree(this.detailsDoc);
   mockFeedback.call(doCmd('jumpToDetails')).expectSpeech('Details');
-  mockFeedback.replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F(
@@ -944,10 +945,8 @@
       const site = '<input type="submit" aria-label="foo" value="foo"></input>';
       const root = await this.runWithLoadedTree(site);
       const btn = root.find({role: RoleType.BUTTON});
-      mockFeedback.call(focus(btn))
-          .expectSpeech('foo')
-          .expectSpeech('Button')
-          .replay();
+      mockFeedback.call(focus(btn)).expectSpeech('foo').expectSpeech('Button');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'NameFromHeadingLink', async function() {
@@ -961,8 +960,8 @@
   mockFeedback.call(focus(link))
       .expectSpeech('go')
       .expectSpeech('Link')
-      .expectSpeech('Heading 1')
-      .replay();
+      .expectSpeech('Heading 1');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'OptionChildIndexCount', async function() {
@@ -991,8 +990,8 @@
       .expectSpeech(' 1 of 2 ')
       .call(doCmd('nextObject'))
       .expectSpeech('banana')
-      .expectSpeech(' 2 of 2 ')
-      .replay();
+      .expectSpeech(' 2 of 2 ');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'ListMarkerIsIgnored', async function() {
@@ -1000,8 +999,8 @@
   const root = await this.runWithLoadedTree('<ul><li>apple</ul>');
   mockFeedback.call(doCmd('nextObject'))
       .expectNextSpeechUtteranceIsNot('listMarker')
-      .expectSpeech('\u2022 apple')  // bullet apple
-      .replay();
+      .expectSpeech('\u2022 apple');  // bullet apple
+  await mockFeedback.replay();
 });
 
 AX_TEST_F(
@@ -1017,8 +1016,8 @@
           .expectSpeech('NW')
           .call(doCmd('previousHeading'))
           .expectNextSpeechUtteranceIsNot('NE')
-          .expectSpeech('NW')
-          .replay();
+          .expectSpeech('NW');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -1051,8 +1050,8 @@
           .call(assertRangeHasText('Most Popular'))
           .call(doCmd('nextHeading'))
           .expectSpeech('Sports')
-          .call(assertRangeHasText('Sports'))
-          .replay();
+          .call(assertRangeHasText('Sports'));
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'Selection', async function() {
@@ -1081,8 +1080,8 @@
       .expectSpeech('i', 'unselected')
       .call(doCmd('nextCharacter'))
       .call(doCmd('nextCharacter'))
-      .expectSpeech('End selection', 'sim')
-      .replay();
+      .expectSpeech('End selection', 'sim');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'BasicTableCommands', async function() {
@@ -1150,9 +1149,9 @@
       .call(doCmd('goToFirstCell'))
       .expectSpeech('name', 'row 1 column 1')
       .call(doCmd('goToFirstCell'))
-      .expectSpeech('name')
+      .expectSpeech('name');
 
-      .replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'MissingTableCells', async function() {
@@ -1193,15 +1192,16 @@
       .call(doCmd('goToLastCell'))
       .expectSpeech('f', 'row 3 column 1')
       .call(doCmd('goToLastCell'))
-      .expectSpeech('f')
-      .replay();
+      .expectSpeech('f');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'DisabledState', async function() {
   const mockFeedback = this.createMockFeedback();
   const site = '<button aria-disabled="true">ok</button>';
   const root = await this.runWithLoadedTree(site);
-  mockFeedback.expectSpeech('ok', 'Disabled', 'Button').replay();
+  mockFeedback.expectSpeech('ok', 'Disabled', 'Button');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'HeadingLevels', async function() {
@@ -1221,7 +1221,7 @@
   for (let i = 1; i <= 6; i++) {
     makeLevelAssertions(i);
   }
-  mockFeedback.replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'EditableNavigation', async function() {
@@ -1239,8 +1239,8 @@
       .call(doCmd('nextWord'))
       .expectSpeech('a')
       .call(doCmd('nextWord'))
-      .expectSpeech('test')
-      .replay();
+      .expectSpeech('test');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'NavigationMovesFocus', async function() {
@@ -1274,8 +1274,8 @@
           .expectBraille(text, {startIndex: 0, endIndex: 4})  // This
           .call(doCmd('nextLine'))
           // Ensure nothing is selected when the range covers the entire line.
-          .expectBraille('with a second line', {startIndex: -1, endIndex: -1})
-          .replay();
+          .expectBraille('with a second line', {startIndex: -1, endIndex: -1});
+      await mockFeedback.replay();
     });
 
 // This tests ChromeVox's special support for following an in-page link
@@ -1289,8 +1289,8 @@
   const root = await this.runWithLoadedTree(site);
   mockFeedback.expectSpeech('hi', 'Internal link')
       .call(doCmd('forceClickOnCurrentItem'))
-      .expectSpeech('there', 'Button')
-      .replay();
+      .expectSpeech('there', 'Button');
+  await mockFeedback.replay();
 });
 
 // This tests ChromeVox's handling of the scrolledToAnchor event, which is
@@ -1314,8 +1314,8 @@
           .call(press(KeyCode.RETURN))
           .expectSpeech('Found It')
           .call(doCmd('nextHeading'))
-          .expectSpeech('Continue Here', 'Heading 2')
-          .replay();
+          .expectSpeech('Continue Here', 'Heading 2');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'ListItem', async function() {
@@ -1348,8 +1348,8 @@
       // Mixing with line nav.
       .call(doCmd('nextLine'))
       .expectSpeech('3. chicken', 'List item')
-      .expectBraille('3. chicken lstitm lst end')
-      .replay();
+      .expectBraille('3. chicken lstitm lst end');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'BusyHeading', async function() {
@@ -1365,8 +1365,8 @@
   mockFeedback.call(doCmd('nextLine'))
       .expectSpeech(
           'Lots', 'Link', 'going', 'Link', 'here', 'Link', 'Heading 2')
-      .expectBraille('Lots lnk going lnk here lnk h2')
-      .replay();
+      .expectBraille('Lots lnk going lnk here lnk h2');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'NodeVsSubnode', async function() {
@@ -1394,8 +1394,8 @@
       .expectNextSpeechUtteranceIsNot('Internal link')
       .expectSpeech('es')
       .call(outputLinkRange(0, 4))
-      .expectSpeech('test', 'Internal link')
-      .replay();
+      .expectSpeech('test', 'Internal link');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'NativeFind', async function() {
@@ -1411,8 +1411,8 @@
       .expectSpeech('grape', 'Link')
       .call(press(KeyCode.BACK))
       .call(press(KeyCode.L))
-      .expectSpeech('pineapple', 'Link')
-      .replay();
+      .expectSpeech('pineapple', 'Link');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'EditableKeyCommand', async function() {
@@ -1442,9 +1442,9 @@
       .expectSpeech('Text area')
       .call(assertCurNode(textArea))
       .call(doCmd('previousObject'))
-      .call(assertCurNode(textField))
+      .call(assertCurNode(textField));
 
-      .replay();
+  await mockFeedback.replay();
 });
 
 // TODO(crbug.com/935678): Test times out flakily in MSAN builds.
@@ -1495,9 +1495,9 @@
 
             .call(doDefault(div))
             .expectSpeechWithQueueMode('interrupted', QueueMode.QUEUE)
-            .expectSpeechWithQueueMode('s', QueueMode.CATEGORY_FLUSH)
+            .expectSpeechWithQueueMode('s', QueueMode.CATEGORY_FLUSH);
 
-            .replay();
+        await mockFeedback.replay();
       })();
     });
 
@@ -1537,8 +1537,8 @@
       .call(doCmd('previousRow'))
       .expectSpeech('CA', 'row 2 column 2')
       .call(doCmd('previousRow'))
-      .expectSpeech('state', 'row 1 column 2')
-      .replay();
+      .expectSpeech('state', 'row 1 column 2');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F(
@@ -1567,8 +1567,8 @@
           .call(doDefault(group))
           .expectSpeech('Tree item', ' 2 of 2 ')
           .call(doDefault(group))
-          .expectSpeech('Tree item', 'Not selected', ' 1 of 2 ')
-          .replay();
+          .expectSpeech('Tree item', 'Not selected', ' 1 of 2 ');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'NavigationEscapesEdit', async function() {
@@ -1631,9 +1631,9 @@
       .call(press(40 /* ArrowDown */))
       .expectSpeech('test')
       .call(assertBeginning.bind(this, false))
-      .call(assertEnd.bind(this, true))
+      .call(assertEnd.bind(this, true));
 
-      .replay();
+  await mockFeedback.replay();
 
   // TODO: soft line breaks currently won't work in <textarea>.
 });
@@ -1665,8 +1665,8 @@
           .expectSpeech('apple', 'List item', ' 1 of 2 ')
           .call(
               () => assertEquals(
-                  select, ChromeVoxState.instance.currentRange.start.node))
-          .replay();
+                  select, ChromeVoxState.instance.currentRange.start.node));
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -1705,8 +1705,8 @@
           .call(doCmd('nextObject'))
           .expectEarcon(Earcon.WRAP)
           .call(doCmd('nextObject'))
-          .expectSpeech('before')
-          .replay();
+          .expectSpeech('before');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -1739,8 +1739,8 @@
           .call(doCmd('nextObject'))
           .expectEarcon(Earcon.WRAP)
           .call(doCmd('nextObject'))
-          .expectSpeech('before')
-          .replay();
+          .expectSpeech('before');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -1782,8 +1782,8 @@
       const root = await this.runWithLoadedTree(site);
       mockFeedback.call(doCmd('nextObject'))
           .expectSpeech('a ( y + m ) squared + b ( y + m ) + c = 0 .')
-          .expectSpeech('Press up, down, left, or right to explore math')
-          .replay();
+          .expectSpeech('Press up, down, left, or right to explore math');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'GestureGranularity', async function() {
@@ -1849,9 +1849,9 @@
       .call(doGesture(Gesture.SWIPE_RIGHT3))
       .expectSpeech('Form field control')
       .call(doGesture(Gesture.SWIPE_RIGHT3))
-      .expectSpeech('Character')
+      .expectSpeech('Character');
 
-      .replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'LinesFilterWhitespace', async function() {
@@ -1871,8 +1871,8 @@
       .call(doCmd('nextLine'))
       .expectSpeech('Munich')
       .expectNextSpeechUtteranceIsNot(' ')
-      .expectSpeech('London')
-      .replay();
+      .expectSpeech('London');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F(
@@ -1894,8 +1894,8 @@
           .call(() => {
             assertEquals(
                 'tab2', ChromeVoxState.instance.currentRange.start.node.name);
-          })
-          .replay();
+          });
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'ListName', async function() {
@@ -1911,7 +1911,8 @@
     </div>
   `;
   const root = await this.runWithLoadedTree(site);
-  mockFeedback.expectSpeech('Favorite Sports').replay();
+  mockFeedback.expectSpeech('Favorite Sports');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'LayoutTable', async function() {
@@ -1924,8 +1925,8 @@
       .call(doCmd('nextObject'))
       .expectNextSpeechUtteranceIsNot('row 1 column 1')
       .expectNextSpeechUtteranceIsNot('Table , 1 by 1')
-      .expectSpeech('end')
-      .replay();
+      .expectSpeech('end');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F(
@@ -1953,8 +1954,8 @@
           .call(doCmd('nextObject'))
           .call(doCmd('nextObject'))
           .call(doCmd('nextObject'))
-          .expectSpeech('end', 'Button')
-          .replay();
+          .expectSpeech('end', 'Button');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -1974,8 +1975,8 @@
       assertNotNullNorUndefined(buttonText);
       mockFeedback.call(simulateHitTestResult(buttonText))
           .expectSpeech('Jefferson')
-          .expectSpeech('Button')
-          .replay();
+          .expectSpeech('Button');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -1995,7 +1996,8 @@
       const root = await this.runWithLoadedTree(site);
       const slider = root.find({role: RoleType.SLIDER});
       assertNotNullNorUndefined(slider);
-      mockFeedback.call(doDefault(slider)).expectSpeech('51').replay();
+      mockFeedback.call(doDefault(slider)).expectSpeech('51');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -2019,8 +2021,8 @@
       mockFeedback.clearPendingOutput()
           .call(doDefault(slider))
           .expectNextSpeechUtteranceIsNot('51')
-          .expectSpeech('large')
-          .replay();
+          .expectSpeech('large');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'SelectValidityOutput', async function() {
@@ -2046,8 +2048,8 @@
       .expectSpeech('Edit text')
       .expectSpeech('Required')
       .expectNextSpeechUtteranceIsNot('Alert')
-      .expectSpeech('Please enter name')
-      .replay();
+      .expectSpeech('Please enter name');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'EventFromAction', async function() {
@@ -2118,7 +2120,7 @@
           .call(doCmd('nextEditText'))
           .call(doCmd('readPhoneticPronunciation'))
           .expectSpeech('No available text for this item');
-      mockFeedback.replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'SimilarItemNavigation', async function() {
@@ -2146,7 +2148,7 @@
       .call(doCmd('previousSimilarItem'))
       .expectSpeech('inner', 'Heading 3');
 
-  mockFeedback.replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'InvalidItemNavigation', async function() {
@@ -2185,7 +2187,7 @@
       .call(doCmd('previousInvalidItem'))
       .expectSpeech('this are', 'grammar error');
 
-  mockFeedback.replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F(
@@ -2208,7 +2210,7 @@
           .call(doCmd('previousInvalidItem'))
           .expectSpeech('No invalid item');
 
-      mockFeedback.replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'TableWithAriaRowCol', async function() {
@@ -2222,8 +2224,8 @@
   `;
   const root = await this.runWithLoadedTree(site);
   mockFeedback.call(doCmd('fullyDescribe'))
-      .expectSpeech('test', 'row 3 column 1', 'Table , 1 by 1')
-      .replay();
+      .expectSpeech('test', 'row 3 column 1', 'Table , 1 by 1');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F(
@@ -2239,8 +2241,8 @@
       mockFeedback.call(doCmd('nextHeading'))
           .expectSpeech('Heading inside dialog')
           .call(doCmd('previousHeading'))
-          .expectSpeech('Heading outside dialog')
-          .replay();
+          .expectSpeech('Heading outside dialog');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -2292,8 +2294,8 @@
               '■ Mandarins', 'List item', 'List end', 'nested level 3')
           .call(doCmd('nextObject'))
           // Nested level is not mentioned for level 1.
-          .expectSpeech('• Bananas', 'List item', 'List end')
-          .replay();
+          .expectSpeech('• Bananas', 'List item', 'List end');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -2312,8 +2314,8 @@
           .expectSpeech('◦ Raspberries', 'List item', 'List end')
           .call(doCmd('nextObject'))
           .expectSpeech('• Bananas', 'List item', 'List end')
-          .expectBraille('• Bananas lstitm lst end')
-          .replay();
+          .expectBraille('• Bananas lstitm lst end');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -2344,8 +2346,8 @@
           .expectSpeech('◦ Strawberries', '◦ Raspberries')
           .clearPendingOutput()
           .call(doCmd('previousGroup'))
-          .expectSpeech('• Oranges')
-          .replay();
+          .expectSpeech('• Oranges');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'NavigationByList', async function() {
@@ -2419,8 +2421,8 @@
       .call(doCmd('previousObject'))
       .expectSpeech('A random paragraph')
       .call(doCmd('previousList'))
-      .expectSpeech('Drinks', 'List', 'with 2 items')
-      .replay();
+      .expectSpeech('Drinks', 'List', 'with 2 items');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'NoListTest', async function() {
@@ -2430,7 +2432,7 @@
       .expectSpeech('No next list')
       .call(doCmd('previousList'))
       .expectSpeech('No previous list');
-  mockFeedback.replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'NavigateToLastHeading', async function() {
@@ -2444,8 +2446,8 @@
   mockFeedback.call(doCmd('jumpToTop'))
       .expectSpeech('First', 'Heading 1')
       .call(doCmd('previousHeading'))
-      .expectSpeech('Third', 'Heading 1')
-      .replay();
+      .expectSpeech('Third', 'Heading 1');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'ReadLinkURLTest', async function() {
@@ -2462,8 +2464,8 @@
       .call(doCmd('nextObject'))
       .expectSpeech('Not a link', 'Button', 'Press Search+Space to activate')
       .call(doCmd('readLinkURL'))
-      .expectSpeech('No URL found')
-      .replay();
+      .expectSpeech('No URL found');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'NoRepeatTitle', async function() {
@@ -2475,8 +2477,8 @@
   mockFeedback.expectSpeech('title')
       .expectSpeech('Button')
       .expectNextSpeechUtteranceIsNot('title')
-      .expectSpeech('Press Search+Space to activate')
-      .replay();
+      .expectSpeech('Press Search+Space to activate');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'PhoneticsAndCommands', async function() {
@@ -2505,7 +2507,7 @@
       .expectSpeechWithProperties(phonetics, 'o')
       .call(doCmd('jumpToBottom'))
       .expectSpeechWithProperties(noPhonetics, 'A');
-  mockFeedback.replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'ToggleScreen', async function() {
@@ -2518,8 +2520,8 @@
       .call(doCmd('toggleScreen'))
       .expectSpeech('Screen on')
       .call(doCmd('toggleScreen'))
-      .expectSpeech('Screen off')
-      .replay();
+      .expectSpeech('Screen off');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F(
@@ -2538,7 +2540,7 @@
           .expectSpeech(
               'No current ChromeVox focus. Press Alt+Shift+L to go to the ' +
               'launcher.');
-      mockFeedback.replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -2562,7 +2564,7 @@
               () => assertFalse(mockFeedback.utteranceInQueue(
                   'No current ChromeVox focus. ' +
                   'Press Alt+Shift+L to go to the launcher.')));
-      mockFeedback.replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -2597,8 +2599,8 @@
           .call(doCmd('previousLine'))
           .expectSpeech('Testing testing')
           .call(doCmd('previousLine'))
-          .expectSpeech('before')
-          .replay();
+          .expectSpeech('before');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'ReadWindowTitle', async function() {
@@ -2629,8 +2631,8 @@
       // This test may run against official builds, so match against
       // utterances starting with 'bar'. This should exclude any other
       // utterances that contain 'bar' e.g. data:...bar.. or the data url.
-      .expectSpeech(/^bar*/)
-      .replay();
+      .expectSpeech(/^bar*/);
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'OutputEmptyQueueMode', async function() {
@@ -2645,8 +2647,8 @@
   mockFeedback.clearPendingOutput()
       .call(output.go.bind(output))
       .expectSpeechWithQueueMode('', QueueMode.CATEGORY_FLUSH)
-      .expectSpeechWithQueueMode('test', QueueMode.CATEGORY_FLUSH)
-      .replay();
+      .expectSpeechWithQueueMode('test', QueueMode.CATEGORY_FLUSH);
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'SetAccessibilityFocus', async function() {
@@ -2677,8 +2679,8 @@
       .call(doCmd('nextObject'))
       .expectSpeech('Small, menu item radio button selected', ' 1 of 3 ')
       .call(doCmd('nextObject'))
-      .expectSpeech('Medium, menu item radio button unselected', ' 2 of 3 ')
-      .replay();
+      .expectSpeech('Medium, menu item radio button unselected', ' 2 of 3 ');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F(
@@ -2700,7 +2702,7 @@
           .call(doCmd('nextButton'))
           .expectSpeech('Action 2', 'Button');
 
-      mockFeedback.replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -2786,8 +2788,8 @@
           .expectSpeech('Button')
           .call(simulateHitTestResult(group))
           .expectSpeech('range cleared!')
-          .expectEarcon(Earcon.NO_POINTER_ANCHOR)
-          .replay();
+          .expectEarcon(Earcon.NO_POINTER_ANCHOR);
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'FocusOnUnknown', async function() {
@@ -2823,8 +2825,8 @@
   mockFeedback
       .call(DesktopAutomationInterface.instance.onFocus.bind(
           DesktopAutomationInterface.instance, evt1))
-      .expectSpeech('hello')
-      .replay();
+      .expectSpeech('hello');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'TimeDateCommand', async function() {
@@ -2832,8 +2834,8 @@
   const root = await this.runWithLoadedTree('<p></p>');
   mockFeedback.call(doCmd('speakTimeAndDate'))
       .expectSpeech(/(AM|PM)*(2)/)
-      .expectBraille(/(AM|PM)*(2)/)
-      .replay();
+      .expectBraille(/(AM|PM)*(2)/);
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'SwipeToScrollByPage', async function() {
@@ -2855,8 +2857,8 @@
       .call(doGesture(Gesture.SWIPE_DOWN3))
       .expectSpeech(/Page 2 of/)
       .call(doGesture(Gesture.SWIPE_DOWN3))
-      .expectSpeech(/Page 1 of/)
-      .replay();
+      .expectSpeech(/Page 1 of/);
+  await mockFeedback.replay();
 });
 
 AX_TEST_F(
@@ -2885,8 +2887,8 @@
           .expectEarcon(Earcon.NO_POINTER_ANCHOR)
           .clearPendingOutput()
           .call(simulateHitTestResult(button))
-          .expectSpeech('hi', 'Button')
-          .replay();
+          .expectSpeech('hi', 'Button');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'PopupButtonCollapsed', async function() {
@@ -2901,8 +2903,8 @@
   mockFeedback.call(doCmd('jumpToTop'))
       .expectSpeech(
           'Apple', 'Button', 'has pop up', 'Collapsed',
-          'Press Search+Space to activate')
-      .replay();
+          'Press Search+Space to activate');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'PopupButtonExpanded', async function() {
@@ -2926,8 +2928,8 @@
       // SetSize is only reported if popup button is expanded.
       .expectSpeech(
           'Click me', 'Button', 'has pop up', 'with 3 items', 'Expanded',
-          'Press Search+Space to activate')
-      .replay();
+          'Press Search+Space to activate');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'SortDirection', async function() {
@@ -2956,8 +2958,8 @@
       .call(doDefault(sortButton))
       .expectSpeech('Ascending sort')
       .call(doDefault(sortButton))
-      .expectSpeech('Descending sort')
-      .replay();
+      .expectSpeech('Descending sort');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'InlineLineNavigation', async function() {
@@ -2967,9 +2969,8 @@
     <p><strong>This</strong><b>is</b>a <em>test</em></p>
   `;
   const root = await this.runWithLoadedTree(site);
-  mockFeedback.call(doCmd('nextLine'))
-      .expectSpeech('This', 'is', 'a ', 'test')
-      .replay();
+  mockFeedback.call(doCmd('nextLine')).expectSpeech('This', 'is', 'a ', 'test');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'AudioVideo', async function() {
@@ -3005,8 +3006,8 @@
   mockFeedback.call(doCmd('nextObject'))
       .expectSpeech('Video')
       .call(doCmd('previousObject'))
-      .expectSpeech('Audio')
-      .replay();
+      .expectSpeech('Audio');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'AlertNoAnnouncement', async function() {
@@ -3022,8 +3023,8 @@
   mockFeedback
       .call(DesktopAutomationInterface.instance.onAlert.bind(
           DesktopAutomationInterface.instance, alertEvt))
-      .call(() => assertFalse(mockFeedback.utteranceInQueue('Alert')))
-      .replay();
+      .call(() => assertFalse(mockFeedback.utteranceInQueue('Alert')));
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'AlertAnnouncement', async function() {
@@ -3041,8 +3042,8 @@
       .call(DesktopAutomationInterface.instance.onAlert.bind(
           DesktopAutomationInterface.instance, alertEvt))
       .expectNextSpeechUtteranceIsNot('Alert')
-      .expectSpeech('hello world')
-      .replay();
+      .expectSpeech('hello world');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F(
@@ -3061,9 +3062,9 @@
           .call(doGesture(Gesture.SWIPE_LEFT4))
           .expectSpeech(/Calendar*/)
           .call(doGesture(Gesture.SWIPE_LEFT4))
-          .expectSpeech('Shelf', 'Tool bar')
+          .expectSpeech('Shelf', 'Tool bar');
 
-          .replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'SwipeLeftRight2', async function() {
@@ -3078,9 +3079,8 @@
   `;
   const root = await this.runWithLoadedTree(site);
   mockFeedback.call(doGesture(Gesture.SWIPE_RIGHT2)).expectSpeech('Enter');
-  mockFeedback.call(doGesture(Gesture.SWIPE_LEFT2))
-      .expectSpeech('Escape')
-      .replay();
+  mockFeedback.call(doGesture(Gesture.SWIPE_LEFT2)).expectSpeech('Escape');
+  await mockFeedback.replay();
 });
 
 // TODO(crbug.com/1228418) - Improve the generation of summaries across ChromeOS
@@ -3116,9 +3116,9 @@
           .call(doCmd('previousObject'))
           .expectSpeech(`Let's go`, 'Button')
           .expectSpeech('Setup')
-          .expectSpeech(`Welcome This is some introductory text Exit Let's go`)
+          .expectSpeech(`Welcome This is some introductory text Exit Let's go`);
 
-          .replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'ImageAnnotations', async function() {
@@ -3155,8 +3155,8 @@
       .expectSpeech('bar', 'Image')
       .call(doCmd('nextObject'))
       .expectNextSpeechUtteranceIsNot('bar')
-      .expectSpeech('foo', 'Image')
-      .replay();
+      .expectSpeech('foo', 'Image');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'VolumeChanges', async function() {
@@ -3168,8 +3168,8 @@
       .call(() => {
         // The bounds should not have changed.
         assertEquals(JSON.stringify(bounds), JSON.stringify(FocusBounds.get()));
-      })
-      .replay();
+      });
+  await mockFeedback.replay();
 });
 
 AX_TEST_F(
@@ -3185,8 +3185,8 @@
           .expectEarcon(Earcon.WRAP)
           .expectSpeech('Web Content')
           .call(doCmd('nextObject'))
-          .expectSpeech('start')
-          .replay();
+          .expectSpeech('start');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -3202,8 +3202,8 @@
       // and once during the 'read from here' command.
       mockFeedback.expectSpeech('start')
           .call(doCmd('readFromHere'))
-          .expectSpeech('start', 'end')
-          .replay();
+          .expectSpeech('start', 'end');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'ContainerButtons', async function() {
@@ -3228,8 +3228,8 @@
   mockFeedback.call(doCmd('nextObject'))
       .expectSpeech('Cat Video', 'Button')
       .call(doCmd('nextObject'))
-      .expectSpeech('4 minutes, Cat Video')
-      .replay();
+      .expectSpeech('4 minutes, Cat Video');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F(
@@ -3321,9 +3321,9 @@
       .call(
           () => assertEquals(
               RoleType.CHECK_BOX,
-              ChromeVoxState.instance.currentRange.start.node.role))
+              ChromeVoxState.instance.currentRange.start.node.role));
 
-      .replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'MarkedContent', async function() {
@@ -3376,8 +3376,8 @@
       .expectBraille(`Suggest Delete everyone's Delete end Suggest end`)
       .call(doCmd('nextObject'))
       .expectSpeech(' text.')
-      .expectBraille(' text.')
-      .replay();
+      .expectBraille(' text.');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F(
@@ -3407,8 +3407,8 @@
           .call(doCmd('nextObject'))
           .expectSpeech('more info')
           .call(doCmd('nextObject'))
-          .expectSpeech('end')
-          .replay();
+          .expectSpeech('end');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'TouchEditingState', async function() {
@@ -3426,8 +3426,8 @@
       .expectSpeech('Edit text', 'Double tap to start editing')
       .call(doGesture(
           chrome.accessibilityPrivate.Gesture.CLICK, bounds.left, bounds.top))
-      .expectSpeech('Edit text', 'is editing')
-      .replay();
+      .expectSpeech('Edit text', 'is editing');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F(
@@ -3449,8 +3449,8 @@
           .expectEarcon(Earcon.LINK)
           .call(doGesture(chrome.accessibilityPrivate.Gesture.SWIPE_LEFT1))
           .expectSpeech('ok', 'Button')
-          .expectEarcon(Earcon.BUTTON)
-          .replay();
+          .expectEarcon(Earcon.BUTTON);
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'Separator', async function() {
@@ -3469,8 +3469,8 @@
       .expectSpeech('Separator content should be read', 'Separator')
       .expectBraille('Separator content should be read seprtr')
       .call(doCmd('nextObject'))
-      .expectSpeech('World')
-      .replay();
+      .expectSpeech('World');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'FocusAfterClick', async function() {
@@ -3496,8 +3496,8 @@
         assertEquals(
             'Focus me',
             ChromeVoxState.instance.getCurrentRange().start.node.name);
-      })
-      .replay();
+      });
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'EarconPlayback', function() {
@@ -3609,9 +3609,10 @@
 
           .call(toggleTalkBack)
           .call(nextObjectKeyboard)
-          .call(() => assertTrue(Boolean(ChromeVoxState.instance.currentRange)))
+          .call(
+              () => assertTrue(Boolean(ChromeVoxState.instance.currentRange)));
 
-          .replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'DetailsChanged', async function() {
@@ -3634,8 +3635,8 @@
   const button = root.find({role: RoleType.BUTTON});
   mockFeedback.expectSpeech('ok')
       .call(doDefault(button))
-      .expectSpeech('Press Search+A, J to jump to details')
-      .replay();
+      .expectSpeech('Press Search+A, J to jump to details');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'PageLoadEarcons', function() {
@@ -3680,8 +3681,8 @@
   mockFeedback.call(doCmd('nextObject'))
       .expectSpeech('end')
       .call(press(KeyCode.T, {ctrl: true}))
-      .expectSpeech(/New Tab/)
-      .replay();
+      .expectSpeech(/New Tab/);
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'NestedMenuHints', async function() {
@@ -3699,8 +3700,8 @@
       .expectSpeech('Press left or right arrow to navigate; enter to activate')
       .call(
           () => assertFalse(mockFeedback.utteranceInQueue(
-              'Press up or down arrow to navigate; enter to activate')))
-      .replay();
+              'Press up or down arrow to navigate; enter to activate')));
+  await mockFeedback.replay();
 });
 
 AX_TEST_F(
@@ -3723,8 +3724,8 @@
           .call(doCmd('previousObject'))
           .expectSpeech('Enable speech logging', 'Check box')
           .call(doCmd('previousObject'))
-          .expectSpeech('start')
-          .replay();
+          .expectSpeech('start');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'Abbreviation', async function() {
@@ -3733,8 +3734,8 @@
     <abbr title="uniform resource locator">URL</abbr>
   `;
   await this.runWithLoadedTree(site);
-  mockFeedback.expectSpeech('URL', 'uniform resource locator', 'Abbreviation')
-      .replay();
+  mockFeedback.expectSpeech('URL', 'uniform resource locator', 'Abbreviation');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'EndOfText', async function() {
@@ -3776,9 +3777,9 @@
       .call(press(KeyCode.LEFT))
       .expectSpeech('b')
       .call(press(KeyCode.LEFT))
-      .expectSpeech('a')
+      .expectSpeech('a');
 
-      .replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F(
@@ -3789,7 +3790,8 @@
       const tabs = root.findAll({Role: RoleType.TAB});
       assertTrue(tabs.length > 0);
       tabs[0].showContextMenu();
-      mockFeedback.expectSpeech(/menu opened/).replay();
+      mockFeedback.expectSpeech(/menu opened/);
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'SelectWithOptGroup', async function() {
@@ -3812,8 +3814,8 @@
       .call(press(KeyCode.DOWN))
       .expectSpeech('Deinonychus')
       .call(press(KeyCode.UP))
-      .expectSpeech('Velociraptor')
-      .replay();
+      .expectSpeech('Velociraptor');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'GroupNavigation', async function() {
@@ -3830,8 +3832,8 @@
       .call(doCmd('nextObject'))
       .expectSpeech('bye', 'Link')
       .call(doCmd('previousGroup'))
-      .expectSpeech('hello', 'hi', 'Link', 'hey', 'Link')
-      .replay();
+      .expectSpeech('hello', 'hi', 'Link', 'hey', 'Link');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F(
@@ -3853,8 +3855,8 @@
       const button = root.find({role: RoleType.BUTTON});
       mockFeedback.expectSpeech('hello')
           .call(doDefault(button))
-          .expectSpeech('test title')
-          .replay();
+          .expectSpeech('test title');
+      await mockFeedback.replay();
     });
 
 TEST_F('ChromeVoxBackgroundTest', 'NewWindowWebSpeech', function() {
@@ -3957,8 +3959,9 @@
       .call(press(KeyCode.TAB))
       .expectSpeech('Listbox item 2', ' 2 of 3 ', 'Configuration 2', 'List box')
       .call(press(KeyCode.TAB))
-      .expectSpeech('Listbox item 3', ' 3 of 3 ', 'Configuration 3', 'List box')
-      .replay();
+      .expectSpeech(
+          'Listbox item 3', ' 3 of 3 ', 'Configuration 3', 'List box');
+  await mockFeedback.replay();
 });
 
 // Make sure linear navigation does not go inside ListBox's options.
@@ -3971,8 +3974,8 @@
           .call(doCmd('nextObject'))
           .expectSpeech('Click', 'Button')
           .call(doCmd('previousObject'))
-          .expectSpeech('Select an item', 'List box')
-          .replay();
+          .expectSpeech('Select an item', 'List box');
+      await mockFeedback.replay();
     });
 
 // Make sure navigation with Tab to ListBox lands on options.
@@ -3987,8 +3990,8 @@
           .call(doCmd('nextObject'))
           .expectSpeech('Listbox item two', ' 2 of 3 ')
           .call(doCmd('nextObject'))
-          .expectSpeech('Listbox item three', ' 3 of 3 ')
-          .replay();
+          .expectSpeech('Listbox item three', ' 3 of 3 ');
+      await mockFeedback.replay();
     });
 
 // Make sure navigation with touch to ListBox lands on options.
@@ -4009,8 +4012,8 @@
           .call(doGesture(chrome.accessibilityPrivate.Gesture.SWIPE_RIGHT1))
           .expectSpeech('Listbox item two', ' 2 of 3 ')
           .call(doGesture(chrome.accessibilityPrivate.Gesture.SWIPE_RIGHT1))
-          .expectSpeech('Listbox item three', ' 3 of 3 ')
-          .replay();
+          .expectSpeech('Listbox item three', ' 3 of 3 ');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -4072,9 +4075,9 @@
 
           // window2 -> window1.
           .call(doCmd('nextObject'))
-          .expectSpeech('second', 'Button', 'first, window')
+          .expectSpeech('second', 'Button', 'first, window');
 
-          .replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'GestureOnPopUpButton', async function() {
@@ -4089,8 +4092,8 @@
       .call(doGesture(Gesture.SWIPE_DOWN1))
       .expectSpeech('banana')
       .call(doGesture(Gesture.SWIPE_UP1))
-      .expectSpeech('apple')
-      .replay();
+      .expectSpeech('apple');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxBackgroundTest', 'NestedImages', async function() {
@@ -4124,6 +4127,6 @@
       .expectSpeech('third', 'Image')
 
       .call(doCmd('nextObject'))
-      .expectSpeech('end')
-      .replay();
+      .expectSpeech('end');
+  await mockFeedback.replay();
 });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/desktop_automation_handler_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/desktop_automation_handler_test.js
index 53e964fa..eec632c 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/desktop_automation_handler_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/desktop_automation_handler_test.js
@@ -81,9 +81,9 @@
           .call(() => this.handler_.onValueChanged(event))
 
           .expectNextSpeechUtteranceIsNot('70%')
-          .expectSpeech('80%')
+          .expectSpeech('80%');
 
-          .replay();
+      await mockFeedback.replay();
     });
 
 TEST_F(
@@ -105,9 +105,9 @@
             })
             // Make sure it doesn't repeat the previous line!
             .expectNextSpeechUtteranceIsNot('Browser')
-            .expectSpeech('row 3 column 1');
+            .expectSpeech('row 3 column 1')
 
-        mockFeedback.replay();
+            .replay();
       });
     });
 
@@ -138,8 +138,8 @@
               'S: sierra, e: echo, c: charlie, o: oscar, n: november, d: delta')
           .call(() => this.handler_.onSelection(selectFirst))
           .expectSpeech('First')
-          .expectSpeech(/foxtrot/)
-          .replay();
+          .expectSpeech(/foxtrot/);
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -164,8 +164,8 @@
             assertFalse(mockFeedback.utteranceInQueue('Hello world'));
             this.handler_.onAlert(event);
             assertFalse(mockFeedback.utteranceInQueue('Hello world'));
-          })
-          .replay();
+          });
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -197,6 +197,6 @@
           .expectBraille('bar lstitm 2/2 (x)')
           .call(press(KeyCode.UP))
           .expectSpeech('foo', 'List item', ' 1 of 2 ')
-          .expectBraille('foo lstitm 1/2 (x)')
-          .replay();
+          .expectBraille('foo lstitm 1/2 (x)');
+      await mockFeedback.replay();
     });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editable_line.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editable_line.js
index 5c508ee9..e7c0e8a 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editable_line.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editable_line.js
@@ -11,6 +11,7 @@
 import {AutomationUtil} from '../../../common/automation_util.js';
 import {Cursor, CURSOR_NODE_INDEX, CursorMovement, CursorUnit} from '../../../common/cursors/cursor.js';
 import {CursorRange} from '../../../common/cursors/range.js';
+import {RecoveryStrategy, TreePathRecoveryStrategy} from '../../../common/cursors/recovery_strategy.js';
 import {Spannable} from '../../common/spannable.js';
 import {LibLouis} from '../braille/liblouis.js';
 import {Output} from '../output/output.js';
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing_test.js
index 2a071f47..d48f5527 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editing_test.js
@@ -84,7 +84,7 @@
       .expectBraille(
           'textArea Line 1\nline 2\nline 3 mled', {startIndex: 9, endIndex: 9});
 
-  mockFeedback.replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxEditingTest', 'Multiline', async function() {
@@ -106,7 +106,7 @@
       .expectSpeech('line 2', 'selected')
       .expectBraille('line 2\n', {startIndex: 0, endIndex: 6});
 
-  mockFeedback.replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxEditingTest', 'TextButNoSelectionChange', async function() {
@@ -138,7 +138,7 @@
       .call(input.setSelection.bind(input, 5, 5))
       .expectBraille('text2 ed', {startIndex: 5, endIndex: 5});
 
-  mockFeedback.replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxEditingTest', 'RichTextMoveByLine', async function() {
@@ -189,8 +189,8 @@
       .expectBraille('\n')
       .call(moveByLine)
       .expectSpeech('hello', 'Heading 2')
-      .expectBraille('hello h2 mled')
-      .replay();
+      .expectBraille('hello h2 mled');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxEditingTest', 'RichTextMoveByCharacter', async function() {
@@ -262,9 +262,9 @@
 
       .call(moveByChar)
       .expectSpeech(' ')
-      .expectBraille(lineText, {startIndex: 9, endIndex: 9})
+      .expectBraille(lineText, {startIndex: 9, endIndex: 9});
 
-      .replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F(
@@ -428,9 +428,9 @@
           .expectSpeech('Black, 100% opacity.')
           .expectSpeech('Not link')
           .expectSpeech('Not underline')
-          .expectBraille(lineText, {startIndex: 35, endIndex: 35})
+          .expectBraille(lineText, {startIndex: 35, endIndex: 35});
 
-          .replay();
+      await mockFeedback.replay();
     });
 
 // Tests specifically for cursor workarounds.
@@ -472,8 +472,8 @@
           .expectBraille(lineText, {startIndex: 5, endIndex: 5})
           .call(moveByChar)
           .expectSpeech('w')
-          .expectBraille(lineText, {startIndex: 6, endIndex: 6})
-          .replay();
+          .expectBraille(lineText, {startIndex: 6, endIndex: 6});
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -508,9 +508,9 @@
           .expectBraille(lineText, {startIndex: 3, endIndex: 3})
           .call(moveByChar)
           .expectSpeech('End of text')
-          .expectBraille(lineText, {startIndex: 4, endIndex: 4})
+          .expectBraille(lineText, {startIndex: 4, endIndex: 4});
 
-          .replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxEditingTest', 'RichTextLinkOutput', async function() {
@@ -552,9 +552,9 @@
       .expectBraille(lineOnLinkText, {startIndex: 4, endIndex: 4})
       .call(moveByChar)
       .expectSpeech('t')
-      .expectBraille(lineOnLinkText, {startIndex: 5, endIndex: 5})
+      .expectBraille(lineOnLinkText, {startIndex: 5, endIndex: 5});
 
-      .replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F(
@@ -585,9 +585,9 @@
           .call(moveByChar)
           .expectSpeech('s', 'selected')
           .call(moveByChar)
-          .expectSpeech('t', 'selected')
+          .expectSpeech('t', 'selected');
 
-          .replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxEditingTest', 'RichTextImageByCharacter', async function() {
@@ -652,7 +652,7 @@
     mockFeedback.expectBraille.apply(mockFeedback, backItem.braille);
   }
 
-  mockFeedback.replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxEditingTest', 'RichTextSelectByLine', async function() {
@@ -760,9 +760,9 @@
       // Shrinking.
       .call(move)
       .expectSpeech('ne', '22222 li', 'unselected')
-      .expectBraille('22222 line\n', {startIndex: 8, endIndex: 11})
+      .expectBraille('22222 line\n', {startIndex: 8, endIndex: 11});
 
-      .replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F(
@@ -868,9 +868,9 @@
           .call(move)
           .expectSpeech('ine', 'Link')
           .expectSpeech('33333 li', 'List item', 'selected')
-          .expectBraille('11111 line h1', {startIndex: 7, endIndex: 10})
+          .expectBraille('11111 line h1', {startIndex: 7, endIndex: 10});
 
-          .replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -1232,8 +1232,8 @@
 
       // Deletion.
       .call(enterKey)
-      .expectSpeech('1')
-      .replay();
+      .expectSpeech('1');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxEditingTest', 'BackwardWordDelete', async function() {
@@ -1260,8 +1260,8 @@
       .expectSpeech('is , deleted')
       .expectBraille('this\u00a0mled', {startIndex: 5, endIndex: 5})
       .call(this.press(KeyCode.BACK, {ctrl: true}))
-      .expectBraille(' mled', {startIndex: 0, endIndex: 0})
-      .replay();
+      .expectBraille(' mled', {startIndex: 0, endIndex: 0});
+  await mockFeedback.replay();
 });
 
 AX_TEST_F(
@@ -1290,8 +1290,8 @@
           .call(this.press(KeyCode.BACK, {ctrl: true}))
           .expectSpeech('line, deleted')
           .call(this.press(KeyCode.BACK, {ctrl: true}))
-          .expectSpeech('first , deleted')
-          .replay();
+          .expectSpeech('first , deleted');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxEditingTest', 'GrammarErrors', async function() {
@@ -1330,9 +1330,9 @@
       .call(moveByChar)
       .expectSpeech('e')
       .call(moveByChar)
-      .expectSpeech(' ', 'Leaving grammar error')
+      .expectSpeech(' ', 'Leaving grammar error');
 
-      .replay();
+  await mockFeedback.replay();
 });
 
 // Flaky test, crbug.com/1098642.
@@ -1353,8 +1353,8 @@
           .call(this.press(KeyCode.RETURN))
           .expectSpeech('\n')
           .call(this.press(KeyCode.A))
-          .expectSpeech('a')
-          .replay();
+          .expectSpeech('a');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxEditingTest', 'SelectAll', async function() {
@@ -1379,8 +1379,8 @@
       .call(this.press(KeyCode.HOME, {ctrl: true}))
       .expectSpeech('first line')
       .call(this.press(KeyCode.A, {ctrl: true}))
-      .expectSpeech('first line', 'second line', 'third line', 'selected')
-      .replay();
+      .expectSpeech('first line', 'second line', 'third line', 'selected');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxEditingTest', 'TextAreaBrailleEmptyLine', async function() {
@@ -1393,9 +1393,8 @@
   mockFeedback.call(this.press(KeyCode.UP)).expectBraille('two\n');
   mockFeedback.call(this.press(KeyCode.UP)).expectBraille('one\n');
   mockFeedback.call(this.press(KeyCode.UP)).expectBraille('\n');
-  mockFeedback.call(this.press(KeyCode.UP))
-      .expectBraille('test\nmled')
-      .replay();
+  mockFeedback.call(this.press(KeyCode.UP)).expectBraille('test\nmled');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxEditingTest', 'MoveByCharacterIntent', async function() {
@@ -1419,8 +1418,8 @@
       .call(this.press(KeyCode.LEFT))
       .expectSpeech('\n')
       .call(this.press(KeyCode.LEFT))
-      .expectSpeech('3')
-      .replay();
+      .expectSpeech('3');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxEditingTest', 'MoveByLineIntent', async function() {
@@ -1441,8 +1440,8 @@
       .call(this.press(KeyCode.UP))
       .expectSpeech('456')
       .call(this.press(KeyCode.UP))
-      .expectSpeech('123')
-      .replay();
+      .expectSpeech('123');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxEditingTest', 'SelectAllBareTextContent', async function() {
@@ -1455,8 +1454,8 @@
   mockFeedback.call(this.press(KeyCode.END, {ctrl: true}))
       .expectSpeech('unread')
       .call(this.press(KeyCode.A, {ctrl: true}))
-      .expectSpeech('unread', 'selected')
-      .replay();
+      .expectSpeech('unread', 'selected');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxEditingTest', 'InputEvents', async function() {
@@ -1577,8 +1576,8 @@
       .call(this.press(KeyCode.DOWN))
       .expectSpeech('This is ')
       .expectSpeech(
-          'Suggest', 'Delete', `everyone's`, 'Delete end', 'Suggest end')
-      .replay();
+          'Suggest', 'Delete', `everyone's`, 'Delete end', 'Suggest end');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxEditingTest', 'NestedInsertionDeletion', async function() {
@@ -1601,8 +1600,8 @@
           'I ', 'Suggest', 'Username', 'Insert', 'was', 'Insert end', 'Delete',
           'am', 'Delete end', 'Suggest end', ' typing')
       .call(this.press(KeyCode.DOWN))
-      .expectSpeech('End')
-      .replay();
+      .expectSpeech('End');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxEditingTest', 'MoveByCharSuggestions', async function() {
@@ -1649,8 +1648,8 @@
       .call(this.press(KeyCode.LEFT))
       .expectSpeech('Suggest', 'Insert', 'w')
       .call(this.press(KeyCode.DOWN))
-      .expectSpeech('End')
-      .replay();
+      .expectSpeech('End');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxEditingTest', 'MoveByWordSuggestions', async function() {
@@ -1685,8 +1684,8 @@
       .call(this.press(KeyCode.LEFT, {ctrl: true}))
       .expectSpeech('I')
       .call(this.press(KeyCode.DOWN))
-      .expectSpeech('End')
-      .replay();
+      .expectSpeech('End');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F(
@@ -1746,8 +1745,8 @@
           // right arrow key.
           .call(doCmd('nativeNextWord'))
           .call(this.press(KeyCode.RIGHT, {ctrl: true}))
-          .expectSpeech('Suggest', 'Username', 'Insert', 'was', 'Insert end')
-          .replay();
+          .expectSpeech('Suggest', 'Username', 'Insert', 'was', 'Insert end');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxEditingTest', 'Separator', async function() {
@@ -1785,9 +1784,9 @@
       .call(this.press(KeyCode.LEFT))
       // Notice this reads the entire line which is generally undesirable
       // except for special cases like this.
-      .expectSpeech('Hello')
+      .expectSpeech('Hello');
 
-      .replay();
+  await mockFeedback.replay();
 });
 
 // Test for the issue in crbug.com/1203840. This case was causing an infinite
@@ -1820,8 +1819,8 @@
       mockFeedback.call(this.press(KeyCode.DOWN))
           .expectSpeech('This is a test')
           .call(this.press(KeyCode.DOWN))
-          .expectSpeech('End')
-          .replay();
+          .expectSpeech('End');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -1966,8 +1965,8 @@
           .call(ctrlDown)
           .expectSpeech('Another paragraph, number two.')
           .call(this.press(KeyCode.DOWN))
-          .expectSpeech('paragraph, ')
-          .replay();
+          .expectSpeech('paragraph, ');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -1989,8 +1988,8 @@
           .call(this.press(KeyCode.UP))
           .expectNextSpeechUtteranceIsNot('Article')
           .expectNextSpeechUtteranceIsNot('Article end')
-          .expectSpeech('hello')
-          .replay();
+          .expectSpeech('hello');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxEditingTest', 'TableNavigation', async function() {
@@ -2019,8 +2018,8 @@
       .expectSpeech('hello', 'world')
       .expectSpeech('row 1 column 1')
       .call(this.press(KeyCode.RIGHT))
-      .expectSpeech('e')
-      .replay();
+      .expectSpeech('e');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F(
@@ -2128,8 +2127,8 @@
       .call(doCmd('contextMenu'))
       .expectSpeech(' menu opened')
       .call(this.press(KeyCode.ESCAPE))
-      .expectSpeech('ab', 'selected')
-      .replay();
+      .expectSpeech('ab', 'selected');
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxEditingTest', 'NativeCharWordCommands', async function() {
@@ -2158,9 +2157,9 @@
       .call(this.press(KeyCode.LEFT, {ctrl: true}))
       .expectSpeech('is')
       .call(this.press(KeyCode.LEFT, {ctrl: true}))
-      .expectSpeech('This')
+      .expectSpeech('This');
 
-      .replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxEditingTest', 'TablesWithEmptyCells', async function() {
@@ -2212,9 +2211,9 @@
 
       .call(doCmd('nativeNextCharacter'))
       .call(() => cell22.setSelection(0, 0))
-      .expectSpeech('\u00a0', 'row 2 column 2')
+      .expectSpeech('\u00a0', 'row 2 column 2');
 
-      .replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F(
@@ -2284,9 +2283,9 @@
           .call(this.press(KeyCode.LEFT))
           .expectSpeech('\n')
           .call(this.press(KeyCode.LEFT))
-          .expectSpeech('e')
+          .expectSpeech('e');
 
-          .replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -2320,9 +2319,9 @@
           .call(doCmd('nextLink'))
           .expectSpeech('fourth', 'Internal link')
           .call(this.press(KeyCode.RIGHT, {shift: true, ctrl: true}))
-          .expectSpeech('fourth', 'Link', 'selected')
+          .expectSpeech('fourth', 'Link', 'selected');
 
-          .replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -2341,7 +2340,7 @@
           .call(this.press(KeyCode.A))
           .expectBraille('a mled', {startIndex: 1, endIndex: 1})
           .call(this.press(KeyCode.BACK))
-          .expectBraille(' mled', {startIndex: 0, endIndex: 0})
+          .expectBraille(' mled', {startIndex: 0, endIndex: 0});
 
-          .replay();
+      await mockFeedback.replay();
     });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/live_regions_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/live_regions_test.js
index ec35b29..8e82f2eb 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/live_regions_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/live_regions_test.js
@@ -47,7 +47,7 @@
   const go = rootNode.find({role: RoleType.BUTTON});
   mockFeedback.call(go.doDefault.bind(go))
       .expectCategoryFlushSpeech('Hello, world');
-  mockFeedback.replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F(
@@ -67,7 +67,7 @@
       go.doDefault();
       mockFeedback.expectCategoryFlushSpeech('removed:')
           .expectQueuedSpeech('Hello, world');
-      mockFeedback.replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -89,7 +89,7 @@
       const go = rootNode.find({role: RoleType.BUTTON});
       mockFeedback.call(go.doDefault.bind(go))
           .expectCategoryFlushSpeech('Alpha Bravo Charlie');
-      mockFeedback.replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -108,7 +108,7 @@
       const go = rootNode.find({role: RoleType.BUTTON});
       mockFeedback.call(go.doDefault.bind(go))
           .expectCategoryFlushSpeech('bar', 'Heading 1');
-      mockFeedback.replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -134,7 +134,7 @@
       const go = rootNode.find({role: RoleType.BUTTON});
       mockFeedback.call(go.doDefault.bind(go))
           .expectCategoryFlushSpeech('After');
-      mockFeedback.replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxLiveRegionsTest', 'LiveRegionThenFocus', async function() {
@@ -174,7 +174,7 @@
       .call(go.doDefault.bind(go))
       .expectSpeech(focusOrLive)
       .expectSpeech(focusOrLive);
-  mockFeedback.replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxLiveRegionsTest', 'FocusThenLiveRegion', async function() {
@@ -201,7 +201,7 @@
             (candidate.queueMode === QueueMode.CATEGORY_FLUSH ||
              candidate.queueMode === QueueMode.QUEUE);
       });
-  mockFeedback.replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F(
@@ -228,7 +228,7 @@
       mockFeedback.call(go.doDefault.bind(go))
           .expectCategoryFlushSpeech('Live1')
           .expectCategoryFlushSpeech('Live2');
-      mockFeedback.replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxLiveRegionsTest', 'SilentOnNodeChange', async function() {
@@ -255,7 +255,7 @@
       .expectSpeech('hello!')
       .expectNextSpeechUtteranceIsNot('hello!')
       .expectNextSpeechUtteranceIsNot('hello!');
-  mockFeedback.replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxLiveRegionsTest', 'SimulateTreeChanges', async function() {
@@ -287,7 +287,7 @@
       })
       .expectSpeech('hello')
       .expectSpeech('there');
-  mockFeedback.replay();
+  await mockFeedback.replay();
 });
 
 // Flaky: https://crbug.com/945199
@@ -322,8 +322,8 @@
           .clearPendingOutput()
           .call(clickInput)
           .expectNextSpeechUtteranceIsNot('bba')
-          .expectSpeech('a')
-          .replay();
+          .expectSpeech('a');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -346,8 +346,8 @@
   `);
       const button = root.find({role: chrome.automation.RoleType.BUTTON});
       mockFeedback.call(button.doDefault.bind(button))
-          .expectSpeech('Alert', 'hi')
-          .replay();
+          .expectSpeech('Alert', 'hi');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxLiveRegionsTest', 'ShouldIgnoreLiveRegion', function() {
@@ -395,6 +395,6 @@
       mockFeedback.call(button.doDefault.bind(button))
           .expectSpeech('hello')
           .call(button.doDefault.bind(button))
-          .expectSpeech('there')
-          .replay();
+          .expectSpeech('there');
+      await mockFeedback.replay();
     });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/loader.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/loader.js
index 479db39..91b2bee 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/loader.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/loader.js
@@ -7,13 +7,10 @@
  */
 
 goog.require('AbstractEarcons');
-goog.require('AncestryRecoveryStrategy');
 goog.require('AutomationPredicate');
 goog.require('JaPhoneticData');
 goog.require('PanelNodeMenuData');
 goog.require('PanelTabMenuItemData');
-goog.require('RecoveryStrategy');
-goog.require('TreePathRecoveryStrategy');
 
 goog.require('constants');
 goog.require('goog.i18n.MessageFormat');
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/logging/log_store.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/logging/log_store.js
index abc248d..34275ae 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/logging/log_store.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/logging/log_store.js
@@ -159,4 +159,4 @@
     () => LogStore.instance.clearLog());
 BridgeHelper.registerHandler(
     BridgeConstants.LogStore.TARGET, BridgeConstants.LogStore.Action.GET_LOGS,
-    () => LogStore.instance.getLogs());
+    () => LogStore.instance.getLogs().map(log => log.serialize()));
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/settings_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/settings_test.js
index ee66b34..813c2698 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/settings_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/settings_test.js
@@ -86,7 +86,7 @@
           .call(increaseRate)
           .expectSpeech('Rate 2 percent')
           .call(increaseRate)
-          .expectSpeech('Rate 4 percent')
+          .expectSpeech('Rate 4 percent');
 
-          .replay();
+      await mockFeedback.replay();
     });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/smart_sticky_mode_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/smart_sticky_mode_test.js
index 4659ceb0..54704be7 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/smart_sticky_mode_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/smart_sticky_mode_test.js
@@ -152,8 +152,8 @@
           .call(doCmd('previousObject'))
           .expectSpeech('Sticky mode disabled')
           .expectSpeech('Edit text')
-          .call(() => assertFalse(ChromeVox.isStickyModeOn()))
-          .replay();
+          .call(() => assertFalse(ChromeVox.isStickyModeOn()));
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -175,8 +175,8 @@
           .expectEarcon(Earcon.SMART_STICKY_MODE_ON)
           .expectSpeech('Sticky mode enabled')
           .expectSpeech('Button')
-          .call(() => assertTrue(ChromeVox.isStickyModeOn()))
-          .replay();
+          .call(() => assertTrue(ChromeVox.isStickyModeOn()));
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxSmartStickyModeTest', 'ContinuousRead', async function() {
@@ -198,6 +198,6 @@
       .call(doCmd('nextObject'))
       .expectNextSpeechUtteranceIsNot('Sticky mode enabled')
       .expectSpeech('Button')
-      .call(() => assertTrue(ChromeVox.isStickyModeOn()))
-      .replay();
+      .call(() => assertTrue(ChromeVox.isStickyModeOn()));
+  await mockFeedback.replay();
 });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/user_action_monitor_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/user_action_monitor_test.js
index a8f3d64..f4e3c143 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/user_action_monitor_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/user_action_monitor_test.js
@@ -225,7 +225,7 @@
         assertTrue(finished);
       })
       .expectSpeech('You did it!');
-  mockFeedback.replay();
+  await mockFeedback.replay();
 });
 
 // Tests that we can match a single key. Serves as an integration test
@@ -328,7 +328,7 @@
             assertTrue(finished);
           })
           .expectSpeech('You pressed the second sequence!');
-      mockFeedback.replay();
+      await mockFeedback.replay();
     });
 
 // Tests that we can provide expectations for ChromeVox commands and block
@@ -390,8 +390,8 @@
         keyboardHandler.onKeyUp(previousObject);
         assertEquals('Start', this.getRangeStart().name);
       })
-      .expectSpeech('Start')
-      .replay();
+      .expectSpeech('Start');
+  await mockFeedback.replay();
 });
 
 // Tests that a user can close ChromeVox (Ctrl + Alt + Z) when UserActionMonitor
@@ -488,6 +488,6 @@
             doGesture(Gesture.SWIPE_RIGHT1)();
             assertTrue(finished);
           })
-          .expectSpeech(/Battery at [0-9]+ percent/)
-          .replay();
+          .expectSpeech(/Battery at [0-9]+ percent/);
+      await mockFeedback.replay();
     });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/common/background_bridge.js b/chrome/browser/resources/chromeos/accessibility/chromevox/common/background_bridge.js
index 319ca14e..f43a5f6 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/common/background_bridge.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/common/background_bridge.js
@@ -9,7 +9,7 @@
 
 import {BridgeConstants} from './bridge_constants.js';
 import {BridgeHelper} from './bridge_helper.js';
-import {BaseLog} from './log_types.js';
+import {SerializableLog} from './log_types.js';
 
 export const BackgroundBridge = {};
 
@@ -188,7 +188,7 @@
   /**
    * Create logs in order.
    * This function is not currently optimized for speed.
-   * @return {!Promise<!Array<BaseLog>>}
+   * @return {!Promise<!Array<!SerializableLog>>}
    */
   async getLogs() {
     return BridgeHelper.sendMessage(
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/common/locale_output_helper_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/common/locale_output_helper_test.js
index 9924d7a3..17d403b 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/common/locale_output_helper_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/common/locale_output_helper_test.js
@@ -203,7 +203,7 @@
           .expectSpeechWithLocale('fr', 'français: Salut.');
       mockFeedback.call(doCmd('nextLine'))
           .expectSpeechWithLocale('it', 'italiano: Ciao amico.');
-      mockFeedback.replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -230,7 +230,7 @@
           .expectSpeechWithLocale('es', 'español: Hola.');
       mockFeedback.call(doCmd('nextLine'))
           .expectSpeechWithLocale('en', 'English: Goodbye.');
-      mockFeedback.replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -252,7 +252,7 @@
           .call(doCmd('nextObject'))
           .expectSpeechWithLocale('es', 'Este es un enlace.')
           .expectSpeechWithLocale(undefined, 'Link');
-      mockFeedback.replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -275,7 +275,7 @@
               'en-us',
               'Hello, my name is 太田あきひろ. It\'s a pleasure to meet' +
                   ' you. どうぞよろしくお願いします.');
-      mockFeedback.replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -291,7 +291,7 @@
               'en-us',
               'This text is written in English. 차에 한하여 중임할 수.' +
                   ' This text is also written in English.');
-      mockFeedback.replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -309,7 +309,7 @@
                   ' the following French passage: ' +
                   'salut mon ami! Ca va? Bien, et toi? It\'s hard to' +
                   ' differentiate between latin-based languages.');
-      mockFeedback.replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -322,7 +322,7 @@
       this.setAvailableVoices();
       mockFeedback.call(doCmd('jumpToTop'))
           .expectSpeechWithLocale('en-us', 'ど');
-      mockFeedback.replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -337,7 +337,7 @@
           .expectSpeechWithLocale(
               'en-us',
               '天気はいいですね. 右万諭全中結社原済権人点掲年難出面者会追');
-      mockFeedback.replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -358,7 +358,7 @@
           .expectSpeechWithLocale(
               'zh',
               '中文: 天気はいいですね. 右万諭全中結社原済権人点掲年難出面者会追');
-      mockFeedback.replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -375,7 +375,7 @@
               'ko',
               '한국어: 私は. 법률이 정하는 바에 의하여 대법관이 아닌 법관을 둘 수' +
                   ' 있다');
-      mockFeedback.replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -392,7 +392,7 @@
               'ast',
               'asturianu: Pretend that this text is Asturian. Testing' +
                   ' three-letter language code logic.');
-      mockFeedback.replay();
+      await mockFeedback.replay();
     });
 
 
@@ -413,7 +413,7 @@
           .expectSpeechWithLocale(undefined, 'Salut.')
           .call(doCmd('nextObject'))
           .expectSpeechWithLocale(undefined, 'Ciao amico.');
-      mockFeedback.replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -430,7 +430,7 @@
           .expectSpeechWithLocale('en-us', 'English (United States): Test')
           .call(doCmd('nextObject'))
           .expectSpeechWithLocale('en-us', 'Yikes');
-      mockFeedback.replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -447,7 +447,7 @@
           .call(doCmd('nextObject'))
           .expectSpeechWithLocale(
               'en-us', 'No voice available for language: Urdu');
-      mockFeedback.replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -497,8 +497,8 @@
           .call(doCmd('previousWord'))
           .expectSpeechWithLocale('en', `English: .`)
           .call(doCmd('previousWord'))
-          .expectSpeechWithLocale('en', `you`)
-          .replay();
+          .expectSpeechWithLocale('en', `you`);
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -542,8 +542,8 @@
           .call(doCmd('previousCharacter'))
           .expectSpeechWithLocale('fr', `e`)
           .call(doCmd('previousCharacter'))
-          .expectSpeechWithLocale('fr', `j`)
-          .replay();
+          .expectSpeechWithLocale('fr', `j`);
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -560,7 +560,7 @@
           .call(doCmd('nextLine'))
           .expectSpeechWithLocale(
               'zh-hant', '中文(繁體): Traditional Chinese');
-      mockFeedback.replay();
+      await mockFeedback.replay();
     });
 
 AX_TEST_F(
@@ -576,7 +576,7 @@
           .expectSpeechWithLocale('pt-br', 'português (Brasil): Brazil')
           .call(doCmd('nextLine'))
           .expectSpeechWithLocale('pt-pt', 'português (Portugal): Portugal');
-      mockFeedback.replay();
+      await mockFeedback.replay();
     });
 
 // Tests logic in shouldAnnounceLocale_(). We only announce the locale once when
@@ -602,6 +602,6 @@
           .call(doCmd('nextObject'))
           .expectSpeechWithLocale('en', 'Penultimate')
           .call(doCmd('nextObject'))
-          .expectSpeechWithLocale('en-ca', 'End')
-          .replay();
+          .expectSpeechWithLocale('en-ca', 'End');
+      await mockFeedback.replay();
     });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/common/log_types.js b/chrome/browser/resources/chromeos/accessibility/chromevox/common/log_types.js
index 1ed02fb..182327c8 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/common/log_types.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/common/log_types.js
@@ -25,6 +25,15 @@
   TREE: 'tree',
 };
 
+/**
+ * @typedef {{
+ *   logType: !LogType,
+ *   date: !Date,
+ *   value: string
+ * }}
+ */
+export let SerializableLog;
+
 export class BaseLog {
   constructor(logType) {
     /**
@@ -38,6 +47,12 @@
     this.date = new Date();
   }
 
+  /** @return {!SerializableLog} */
+  serialize() {
+    return /** @type {!SerializableLog} */ (
+        {logType: this.logType, date: this.date, value: this.toString()});
+  }
+
   /** @return {string} */
   toString() {
     return '';
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/learn_mode/learn_mode_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/learn_mode/learn_mode_test.js
index 917d1b4e..ff1f051 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/learn_mode/learn_mode_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/learn_mode/learn_mode_test.js
@@ -119,9 +119,9 @@
       .call(doKeyDown({keyCode: KeyCode.RIGHT, metaKey: true}))
       .expectSpeechWithQueueMode('Right arrow', QueueMode.CATEGORY_FLUSH)
       .expectSpeechWithQueueMode('Next Object', QueueMode.QUEUE)
-      .call(doKeyUp({keyCode: KeyCode.RIGHT, metaKey: true}))
+      .call(doKeyUp({keyCode: KeyCode.RIGHT, metaKey: true}));
 
-      .replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxLearnModeTest', 'KeyboardInputRepeat', async function() {
@@ -139,9 +139,9 @@
       .call(doKeyDown({keyCode: KeyCode.SEARCH, metaKey: true, repeat: true}))
       .call(doKeyDown({keyCode: KeyCode.CONTROL, ctrlKey: true}))
       .expectNextSpeechUtteranceIsNot('Search')
-      .expectSpeechWithQueueMode('Control', QueueMode.QUEUE)
+      .expectSpeechWithQueueMode('Control', QueueMode.QUEUE);
 
-      .replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxLearnModeTest', 'Gesture', async function() {
@@ -165,9 +165,9 @@
       .call(doLearnModeGesture(Gesture.SWIPE_LEFT2))
       .expectSpeechWithQueueMode(
           'Swipe two fingers left', QueueMode.CATEGORY_FLUSH)
-      .expectSpeechWithQueueMode('Escape', QueueMode.QUEUE)
+      .expectSpeechWithQueueMode('Escape', QueueMode.QUEUE);
 
-      .replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxLearnModeTest', 'Braille', async function() {
@@ -187,9 +187,9 @@
       .call(doBrailleKeyEvent(
           {command: BrailleKeyCommand.CHORD, brailleDots: 0b011001}))
       .expectSpeechWithQueueMode('dots 1-4-5 chord', QueueMode.CATEGORY_FLUSH)
-      .expectBraille('dots 1-4-5 chord')
+      .expectBraille('dots 1-4-5 chord');
 
-      .replay();
+  await mockFeedback.replay();
 });
 
 AX_TEST_F('ChromeVoxLearnModeTest', 'HardwareFunctionKeys', async function() {
@@ -213,7 +213,7 @@
       // Search+Volume Mute does though.
       .call(doKeyDown({keyCode: KeyCode.VOLUME_MUTE, metaKey: true}))
       .expectSpeechWithQueueMode('volume mute', QueueMode.CATEGORY_FLUSH)
-      .expectSpeechWithQueueMode('Toggle speech on or off', QueueMode.QUEUE)
+      .expectSpeechWithQueueMode('Toggle speech on or off', QueueMode.QUEUE);
 
-      .replay();
+  await mockFeedback.replay();
 });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/log_page/log.js b/chrome/browser/resources/chromeos/accessibility/chromevox/log_page/log.js
index 4b6ec3e..c013e7b 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/log_page/log.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/log_page/log.js
@@ -4,11 +4,10 @@
 
 /**
  * @fileoverview ChromeVox log page.
- *
  */
 
 import {BackgroundBridge} from '../common/background_bridge.js';
-import {BaseLog, LogType} from '../common/log_types.js';
+import {BaseLog, LogType, SerializableLog} from '../common/log_types.js';
 
 /**
  * Class to manage the log page.
@@ -107,7 +106,7 @@
 
   /**
    * Updates the log section.
-   * @param {Array<BaseLog>} log Array of speech.
+   * @param {Array<!SerializableLog>} log Array of logs to record.
    * @param {Element} div
    */
   static updateLog(log, div) {
@@ -125,7 +124,7 @@
       timeStamp.className = 'log-time-tag';
       /** textWrapper should be in block scope, not function scope. */
       const textWrapper = document.createElement('pre');
-      textWrapper.textContent = log[i].toString();
+      textWrapper.textContent = log[i].value;
       textWrapper.className = 'log-text';
 
       p.appendChild(typeName);
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/options/options_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/options/options_test.js
index f54fc37..2518a7d 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/options/options_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/options/options_test.js
@@ -79,9 +79,9 @@
           .expectSpeech('Digits', 'Collapsed')
           .call(() => {
             assertEquals('asDigits', localStorage['numberReadingStyle']);
-          })
+          });
 
-          .replay();
+      await mockFeedback.replay();
     });
 
 // TODO(crbug.com/1128926, crbug.com/1172387):
@@ -125,9 +125,9 @@
             assertEquals(
                 PUNCTUATION_ECHO_ALL,
                 localStorage[AbstractTts.PUNCTUATION_ECHO]);
-          })
+          });
 
-          .replay();
+      await mockFeedback.replay();
     });
 
 // TODO(crbug.com/1128926, crbug.com/1172387):
@@ -153,9 +153,9 @@
           'Check box', 'Not checked')
       .call(() => {
         assertEquals('false', localStorage['smartStickyMode']);
-      })
+      });
 
-      .replay();
+  await mockFeedback.replay();
 });
 
 // TODO(crbug.com/1169396, crbug.com/1172387):
@@ -230,5 +230,5 @@
       })
       .call(capitalStrategySelect.focus.bind(capitalStrategySelect))
       .expectSpeech('When reading capitals:', 'Increase pitch', 'Collapsed');
-  mockFeedback.replay();
+  await mockFeedback.replay();
 });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel.js b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel.js
index 571b280..762459d 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel.js
@@ -1271,7 +1271,8 @@
   const bkgnd = chrome.extension.getBackgroundPage();
 
   // Save the sticky state when a user first focuses the panel.
-  if (location.hash === '#fullscreen' || location.hash === '#focus') {
+  if (bkgnd['ChromeVox'] &&
+      (location.hash === '#fullscreen' || location.hash === '#focus')) {
     Panel.originalStickyState_ = bkgnd['ChromeVox']['isStickyPrefOn'];
   }
 
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel_loader.js b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel_loader.js
index 7e4823382..217d17f 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel_loader.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel_loader.js
@@ -6,12 +6,10 @@
  * @fileoverview Loads the panel script.
  */
 
-goog.require('AncestryRecoveryStrategy');
 goog.require('AutomationPredicate');
 goog.require('EarconDescription');
 goog.require('PanelNodeMenuData');
 goog.require('PanelNodeMenuItemData');
-goog.require('RecoveryStrategy');
 
 goog.require('constants');
 goog.require('goog.i18n.MessageFormat');
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/tutorial_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/tutorial_test.js
index 8835fe2..e029b105 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/tutorial_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/tutorial_test.js
@@ -112,8 +112,8 @@
       .call(doCmd('nextObject'))
       .expectSpeech('Resources', 'Link')
       .call(doCmd('nextObject'))
-      .expectSpeech('Exit tutorial', 'Button')
-      .replay();
+      .expectSpeech('Exit tutorial', 'Button');
+  await mockFeedback.replay();
 });
 
 // Tests that different lessons are shown when choosing an experience from the
@@ -148,8 +148,8 @@
       .call(doCmd('forceClickOnCurrentItem'))
       .expectSpeech(/Essential Keys Tutorial, [0-9]+ Lessons/)
       .call(doCmd('nextObject'))
-      .expectSpeech('On, Off, and Stop')
-      .replay();
+      .expectSpeech('On, Off, and Stop');
+  await mockFeedback.replay();
 });
 
 // Tests that a static lesson does not show the 'Practice area' button.
@@ -176,8 +176,8 @@
               ' Press Search + Right Arrow, or Search + Left Arrow to navigate ' +
                   'this lesson ')
           .call(doCmd('nextButton'))
-          .expectSpeech('Next lesson')
-          .replay();
+          .expectSpeech('Next lesson');
+      await mockFeedback.replay();
     });
 
 // Tests that an interactive lesson shows the 'Practice area' button.
@@ -203,8 +203,8 @@
           })
           .expectSpeech('Jump Commands', 'Heading 1')
           .call(doCmd('nextButton'))
-          .expectSpeech('Practice area')
-          .replay();
+          .expectSpeech('Practice area');
+      await mockFeedback.replay();
     });
 
 // Tests nudges given in the general tutorial context.
@@ -231,8 +231,8 @@
       .expectSpeech('Hint: Press Search + Space to activate the current item.')
       .call(giveNudge)
       .expectSpeech(
-          'Hint: Press Escape if you would like to exit this tutorial.')
-      .replay();
+          'Hint: Press Escape if you would like to exit this tutorial.');
+  await mockFeedback.replay();
 });
 
 // Tests nudges given in the practice area context. Note, each practice area
@@ -270,8 +270,8 @@
               'Try pressing Search + left/right arrow. The search key is ' +
               'directly above the shift key')
           .call(giveNudge)
-          .expectSpeech('Press Search + Space to activate the current item.')
-          .replay();
+          .expectSpeech('Press Search + Space to activate the current item.');
+      await mockFeedback.replay();
     });
 
 // Tests that the tutorial closes when the 'Exit tutorial' button is clicked.
@@ -285,8 +285,8 @@
       .call(doCmd('previousButton'))
       .expectSpeech('Exit tutorial')
       .call(doCmd('forceClickOnCurrentItem'))
-      .expectSpeech('Some web content')
-      .replay();
+      .expectSpeech('Some web content');
+  await mockFeedback.replay();
 });
 
 // Tests that the tutorial closes when Escape is pressed.
@@ -305,8 +305,8 @@
           stopPropagation: () => {},
         });
       })
-      .expectSpeech('Some web content')
-      .replay();
+      .expectSpeech('Some web content');
+  await mockFeedback.replay();
 });
 
 // Tests that the main menu button navigates the user to the main menu screen.
@@ -332,8 +332,8 @@
       .expectSpeech('Main menu')
       .call(doCmd('forceClickOnCurrentItem'))
       .expectSpeech('ChromeVox tutorial')
-      .call(this.assertActiveScreen.bind(this, 'main_menu'))
-      .replay();
+      .call(this.assertActiveScreen.bind(this, 'main_menu'));
+  await mockFeedback.replay();
 });
 
 // Tests that the all lessons button navigates the user to the lesson menu
@@ -366,8 +366,8 @@
           .expectSpeech('All lessons')
           .call(doCmd('forceClickOnCurrentItem'))
           .expectSpeech(/Essential Keys Tutorial, [0-9]+ Lessons/)
-          .call(this.assertActiveScreen.bind(this, 'lesson_menu'))
-          .replay();
+          .call(this.assertActiveScreen.bind(this, 'lesson_menu'));
+      await mockFeedback.replay();
     });
 
 // Tests that the next and previous lesson buttons navigate properly.
@@ -396,8 +396,8 @@
           .expectSpeech('Previous lesson')
           .call(doCmd('forceClickOnCurrentItem'))
           .expectSpeech('On, Off, and Stop', 'Heading 1')
-          .call(this.assertActiveLessonIndex.bind(this, 0))
-          .replay();
+          .call(this.assertActiveLessonIndex.bind(this, 0));
+      await mockFeedback.replay();
     });
 
 // Tests that the title of an interactive lesson is read when shown.
@@ -420,8 +420,8 @@
           'time, press the Escape key on the top left corner of the ' +
           'keyboard. To turn off ChromeVox, hold Control and Alt, and ' +
           `press Z. When you're ready, use the spacebar to move to the ` +
-          'next lesson.')
-      .replay();
+          'next lesson.');
+  await mockFeedback.replay();
 });
 
 // Tests that we read a hint for navigating a lesson when it is shown.
@@ -445,8 +445,8 @@
       .expectSpeech('On, Off, and Stop', 'Heading 1')
       .expectSpeech(
           ' Press Search + Right Arrow, or Search + Left Arrow to navigate' +
-          ' this lesson ')
-      .replay();
+          ' this lesson ');
+  await mockFeedback.replay();
 });
 
 // Tests for correct speech and earcons on the earcons lesson.
@@ -475,7 +475,7 @@
   nextObjectAndExpectSpeechAndEarcon(
       'A non modal alert', Earcon.ALERT_NONMODAL);
   nextObjectAndExpectSpeechAndEarcon('A button', Earcon.BUTTON);
-  mockFeedback.replay();
+  await mockFeedback.replay();
 });
 
 // Tests that a lesson from the quick orientation blocks ChromeVox execution
@@ -546,8 +546,8 @@
           .expectSpeech('Essential Keys: Shift')
           .call(() => {
             assertEquals(2, tutorial.activeLessonId);
-          })
-          .replay();
+          });
+      await mockFeedback.replay();
     });
 
 // Tests that tutorial nudges are restarted whenever the current range changes.
@@ -591,8 +591,8 @@
       .call(doCmd('nextObject'))
       .expectSpeech('ChromeVox Command Reference', 'Link')
       .call(doCmd('forceClickOnCurrentItem'))
-      .expectSpeech('support.google.com')
-      .replay();
+      .expectSpeech('support.google.com');
+  await mockFeedback.replay();
 });
 
 // Tests that choosing a curriculum with only 1 lesson automatically opens the
@@ -625,8 +625,8 @@
       .call(doCmd('nextButton'))
       .expectSpeech('Main menu')
       .call(doCmd('nextButton'))
-      .expectSpeech('Exit tutorial')
-      .replay();
+      .expectSpeech('Exit tutorial');
+  await mockFeedback.replay();
 });
 
 // Tests that interactive mode and UserActionMonitor are properly set when
@@ -699,8 +699,8 @@
       .call(doGesture(Gesture.SWIPE_LEFT1))
       .expectSpeech('Quick orientation', 'Link')
       .call(doGesture(Gesture.SWIPE_LEFT2))
-      .expectSpeech('Some web content')
-      .replay();
+      .expectSpeech('Some web content');
+  await mockFeedback.replay();
 });
 
 // Tests that touch orientation loads properly. Tests string content, but does
@@ -737,8 +737,8 @@
           .call(doGesture(Gesture.SWIPE_RIGHT4))
           .expectSpeech(/swiping with four fingers from right to left/)
           .call(doGesture(Gesture.SWIPE_LEFT4))
-          .expectSpeech('Touch tutorial complete')
-          .replay();
+          .expectSpeech('Touch tutorial complete');
+      await mockFeedback.replay();
     });
 
 AX_TEST_F('ChromeVoxTutorialTest', 'GeneralTouchNudges', async function() {
@@ -767,6 +767,6 @@
       .call(giveNudge)
       .expectSpeech(
           'Hint: Swipe from right to left with two fingers if you would ' +
-          'like to exit this tutorial.')
-      .replay();
+          'like to exit this tutorial.');
+  await mockFeedback.replay();
 });
diff --git a/chrome/browser/resources/chromeos/accessibility/common/cursors/cursor.js b/chrome/browser/resources/chromeos/accessibility/common/cursors/cursor.js
index ad5fb51..1e60fcdf 100644
--- a/chrome/browser/resources/chromeos/accessibility/common/cursors/cursor.js
+++ b/chrome/browser/resources/chromeos/accessibility/common/cursors/cursor.js
@@ -14,6 +14,7 @@
 
 import {StringUtil} from '../string_util.js';
 import {AutomationUtil} from '../automation_util.js';
+import {RecoveryStrategy, AncestryRecoveryStrategy} from './recovery_strategy.js';
 
 /**
  * The special index that represents a cursor pointing to a node without
diff --git a/chrome/browser/resources/chromeos/accessibility/common/cursors/recovery_strategy.js b/chrome/browser/resources/chromeos/accessibility/common/cursors/recovery_strategy.js
index 380fb1a..886c072d 100644
--- a/chrome/browser/resources/chromeos/accessibility/common/cursors/recovery_strategy.js
+++ b/chrome/browser/resources/chromeos/accessibility/common/cursors/recovery_strategy.js
@@ -6,15 +6,10 @@
  * @fileoverview Defines various strategies for recovering automation nodes.
  */
 
-goog.provide('AncestryRecoveryStrategy');
-goog.provide('RecoveryStrategy');
-goog.provide('TreePathRecoveryStrategy');
-
-goog.scope(function() {
 const AutomationNode = chrome.automation.AutomationNode;
 const RoleType = chrome.automation.RoleType;
 
-RecoveryStrategy = class {
+export class RecoveryStrategy {
   /**
    * @param {!AutomationNode} node
    */
@@ -48,13 +43,13 @@
   equalsWithoutRecovery(rhs) {
     return this.node_ === rhs.node_;
   }
-};
+}
 
 
 /**
  * A recovery strategy that uses the node's ancestors.
  */
-AncestryRecoveryStrategy = class extends RecoveryStrategy {
+export class AncestryRecoveryStrategy extends RecoveryStrategy {
   constructor(node) {
     super(node);
 
@@ -89,13 +84,13 @@
     }
     return 0;
   }
-};
+}
 
 
 /**
  * A recovery strategy that uses the node's tree path.
  */
-TreePathRecoveryStrategy = class extends AncestryRecoveryStrategy {
+export class TreePathRecoveryStrategy extends AncestryRecoveryStrategy {
   constructor(node) {
     super(node);
 
@@ -130,5 +125,4 @@
     }
     return node;
   }
-};
-});  // goog.scope
+}
diff --git a/chrome/browser/resources/chromeos/accessibility/common/cursors/recovery_strategy_test.js b/chrome/browser/resources/chromeos/accessibility/common/cursors/recovery_strategy_test.js
index 6c74d98..723c6320 100644
--- a/chrome/browser/resources/chromeos/accessibility/common/cursors/recovery_strategy_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/common/cursors/recovery_strategy_test.js
@@ -15,6 +15,18 @@
   constructor() {
     super();
   }
+
+  /** @override */
+  async setUpDeferred() {
+    await super.setUpDeferred();
+    await importModule(
+        [
+          'RecoveryStrategy',
+          'AncestryRecoveryStrategy',
+          'TreePathRecoveryStrategy',
+        ],
+        '/common/cursors/recovery_strategy.js');
+  }
 };
 
 
diff --git a/chrome/browser/resources/chromeos/crostini_installer/app.js b/chrome/browser/resources/chromeos/crostini_installer/app.js
index 932f1d7..b4e4c8a 100644
--- a/chrome/browser/resources/chromeos/crostini_installer/app.js
+++ b/chrome/browser/resources/chromeos/crostini_installer/app.js
@@ -4,7 +4,7 @@
 
 import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
 import 'chrome://resources/cr_elements/cr_slider/cr_slider.js';
-import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.js';
 import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
 import 'chrome://resources/cr_elements/icons.m.js';
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
diff --git a/chrome/browser/resources/chromeos/emulator/audio_settings.js b/chrome/browser/resources/chromeos/emulator/audio_settings.js
index 58ec2f7..8fe8225 100644
--- a/chrome/browser/resources/chromeos/emulator/audio_settings.js
+++ b/chrome/browser/resources/chromeos/emulator/audio_settings.js
@@ -8,7 +8,7 @@
 import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
 import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
 import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
-import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.js';
 import 'chrome://resources/cr_elements/shared_style_css.m.js';
 import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
 import './icons.js';
diff --git a/chrome/browser/resources/chromeos/emulator/battery_settings.js b/chrome/browser/resources/chromeos/emulator/battery_settings.js
index f2c37f0..734c508 100644
--- a/chrome/browser/resources/chromeos/emulator/battery_settings.js
+++ b/chrome/browser/resources/chromeos/emulator/battery_settings.js
@@ -7,7 +7,7 @@
 import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
 import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
 import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
-import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.js';
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
 import 'chrome://resources/cr_elements/md_select_css.m.js';
 import 'chrome://resources/polymer/v3_0/iron-collapse/iron-collapse.js';
diff --git a/chrome/browser/resources/chromeos/emulator/bluetooth_settings.js b/chrome/browser/resources/chromeos/emulator/bluetooth_settings.js
index cdaf2b5..7a60df1 100644
--- a/chrome/browser/resources/chromeos/emulator/bluetooth_settings.js
+++ b/chrome/browser/resources/chromeos/emulator/bluetooth_settings.js
@@ -8,7 +8,7 @@
 import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
 import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
 import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
-import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.js';
 import 'chrome://resources/cr_elements/shared_style_css.m.js';
 import 'chrome://resources/polymer/v3_0/iron-flex-layout/iron-flex-layout-classes.js';
 import './icons.js';
diff --git a/chrome/browser/resources/chromeos/login/oobe_auto_imports.gni b/chrome/browser/resources/chromeos/login/oobe_auto_imports.gni
index d348bc38..2ecd44dc 100644
--- a/chrome/browser/resources/chromeos/login/oobe_auto_imports.gni
+++ b/chrome/browser/resources/chromeos/login/oobe_auto_imports.gni
@@ -98,4 +98,5 @@
   "ui/webui/resources/cr_elements/cr_fingerprint/cr_fingerprint_progress_arc.html",
   "ui/webui/resources/cr_elements/cr_icon_button/cr_icon_button.html",
   "ui/webui/resources/cr_elements/cr_lazy_render/cr_lazy_render.html",
+  "ui/webui/resources/cr_elements/cr_radio_group/cr_radio_group.html",
 ]
diff --git a/chrome/browser/resources/chromeos/login/screens/common/BUILD.gn b/chrome/browser/resources/chromeos/login/screens/common/BUILD.gn
index 7064d52..13891f5b 100644
--- a/chrome/browser/resources/chromeos/login/screens/common/BUILD.gn
+++ b/chrome/browser/resources/chromeos/login/screens/common/BUILD.gn
@@ -874,6 +874,7 @@
   html_file = "user_creation.html"
   html_type = "dom-module"
   auto_imports = oobe_auto_imports
+  migrated_imports = oobe_migrated_imports
   namespace_rewrites = oobe_namespace_rewrites
 }
 
diff --git a/chrome/browser/resources/chromeos/network_ui/network_logs_ui.js b/chrome/browser/resources/chromeos/network_ui/network_logs_ui.js
index 10d74855..c9bc44a 100644
--- a/chrome/browser/resources/chromeos/network_ui/network_logs_ui.js
+++ b/chrome/browser/resources/chromeos/network_ui/network_logs_ui.js
@@ -5,7 +5,7 @@
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
-import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.js';
 import 'chrome://resources/cr_elements/shared_style_css.m.js';
 
 import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
diff --git a/chrome/browser/resources/chromeos/notification_tester/notification_tester.js b/chrome/browser/resources/chromeos/notification_tester/notification_tester.js
index 790fbbc..6878faf 100644
--- a/chrome/browser/resources/chromeos/notification_tester/notification_tester.js
+++ b/chrome/browser/resources/chromeos/notification_tester/notification_tester.js
@@ -4,7 +4,7 @@
 
 import './select_custom.js';
 import 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.js';
 import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
 import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
diff --git a/chrome/browser/resources/component_extension_resources.grd b/chrome/browser/resources/component_extension_resources.grd
index 9f73a001..e9f817d 100644
--- a/chrome/browser/resources/component_extension_resources.grd
+++ b/chrome/browser/resources/component_extension_resources.grd
@@ -69,6 +69,10 @@
         <include name="IDS_ARC_INPUT_OVERLAY_ONBOARDING_ILLUSTRATION" file="chromeos/arc_input_overlay/onboarding_illustration.png" type="BINDATA" />
         <include name="IDS_ARC_INPUT_OVERLAY_ONBOARDING_ILLUSTRATION_DARK" file="chromeos/arc_input_overlay/onboarding_illustration_dark.png" type="BINDATA" />
       </if>
+      <include name="IDS_READ_ANYTHING_DEFAULT_PNG" file="side_panel/images/read_anything_default.png" type="BINDATA" />
+      <include name="IDS_READ_ANYTHING_LIGHT_PNG" file="side_panel/images/read_anything_light.png" type="BINDATA" />
+      <include name="IDS_READ_ANYTHING_DARK_PNG" file="side_panel/images/read_anything_dark.png" type="BINDATA" />
+      <include name="IDS_READ_ANYTHING_YELLOW_PNG" file="side_panel/images/read_anything_yellow.png" type="BINDATA" />
       <include name="IDR_CRYPTOTOKEN_UTIL_JS" file="cryptotoken/util.js" type="BINDATA" />
       <include name="IDR_CRYPTOTOKEN_B64_JS" file="cryptotoken/b64.js" type="BINDATA" />
       <include name="IDR_CRYPTOTOKEN_COUNTDOWN_JS" file="cryptotoken/countdown.js" type="BINDATA" />
diff --git a/chrome/browser/resources/extensions/runtime_host_permissions.ts b/chrome/browser/resources/extensions/runtime_host_permissions.ts
index 1b7813f..0a2348b2c 100644
--- a/chrome/browser/resources/extensions/runtime_host_permissions.ts
+++ b/chrome/browser/resources/extensions/runtime_host_permissions.ts
@@ -6,7 +6,7 @@
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
-import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.js';
 import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
 import 'chrome://resources/cr_elements/icons.m.js';
 import 'chrome://resources/cr_elements/shared_style_css.m.js';
diff --git a/chrome/browser/resources/extensions/site_permissions_edit_permissions_dialog.ts b/chrome/browser/resources/extensions/site_permissions_edit_permissions_dialog.ts
index 77e261b1..3f69da0 100644
--- a/chrome/browser/resources/extensions/site_permissions_edit_permissions_dialog.ts
+++ b/chrome/browser/resources/extensions/site_permissions_edit_permissions_dialog.ts
@@ -5,7 +5,7 @@
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
-import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.js';
 import 'chrome://resources/cr_elements/shared_style_css.m.js';
 import './strings.m.js';
 
diff --git a/chrome/browser/resources/media_router/cast_feedback/cast_feedback_ui.ts b/chrome/browser/resources/media_router/cast_feedback/cast_feedback_ui.ts
index bc13a27e..cba1eec 100644
--- a/chrome/browser/resources/media_router/cast_feedback/cast_feedback_ui.ts
+++ b/chrome/browser/resources/media_router/cast_feedback/cast_feedback_ui.ts
@@ -8,7 +8,7 @@
 import '//resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import '//resources/cr_elements/cr_input/cr_input.m.js';
 import '//resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
-import '//resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import '//resources/cr_elements/cr_radio_group/cr_radio_group.js';
 import '//resources/cr_elements/shared_style_css.m.js';
 import '//resources/cr_elements/shared_vars_css.m.js';
 
diff --git a/chrome/browser/resources/nearby_share/shared/BUILD.gn b/chrome/browser/resources/nearby_share/shared/BUILD.gn
index 495922d..9f9ee47 100644
--- a/chrome/browser/resources/nearby_share/shared/BUILD.gn
+++ b/chrome/browser/resources/nearby_share/shared/BUILD.gn
@@ -124,7 +124,7 @@
     ":nearby_share_settings_behavior",
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_elements/cr_radio_button:cr_card_radio_button.m",
-    "//ui/webui/resources/cr_elements/cr_radio_group:cr_radio_group.m",
+    "//ui/webui/resources/cr_elements/cr_radio_group:cr_radio_group",
     "//ui/webui/resources/js:assert.m",
     "//ui/webui/resources/js:cr.m",
     "//ui/webui/resources/js:i18n_behavior.m",
diff --git a/chrome/browser/resources/nearby_share/shared/nearby_contact_visibility.js b/chrome/browser/resources/nearby_share/shared/nearby_contact_visibility.js
index 17e331e..d144415d 100644
--- a/chrome/browser/resources/nearby_share/shared/nearby_contact_visibility.js
+++ b/chrome/browser/resources/nearby_share/shared/nearby_contact_visibility.js
@@ -9,7 +9,7 @@
  */
 
 import 'chrome://resources/cr_elements/shared_style_css.m.js';
-import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.js';
 import 'chrome://resources/cr_elements/cr_radio_button/cr_card_radio_button.m.js';
 import 'chrome://resources/cr_elements/cr_icons_css.m.js';
 import 'chrome://resources/cr_elements/cr_toggle/cr_toggle.m.js';
diff --git a/chrome/browser/resources/new_tab_page/customize_modules.ts b/chrome/browser/resources/new_tab_page/customize_modules.ts
index 1d48086..01bf87c 100644
--- a/chrome/browser/resources/new_tab_page/customize_modules.ts
+++ b/chrome/browser/resources/new_tab_page/customize_modules.ts
@@ -5,7 +5,7 @@
 import 'chrome://resources/cr_elements/cr_icons_css.m.js';
 import 'chrome://resources/cr_elements/cr_toggle/cr_toggle.m.js';
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
-import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.js';
 import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
 import 'chrome://resources/cr_elements/policy/cr_policy_indicator.m.js';
 
diff --git a/chrome/browser/resources/safe_browsing/OWNERS b/chrome/browser/resources/safe_browsing/OWNERS
index 70e4b68..5225674 100644
--- a/chrome/browser/resources/safe_browsing/OWNERS
+++ b/chrome/browser/resources/safe_browsing/OWNERS
@@ -1,3 +1 @@
-drubery@chromium.org
-nparker@chromium.org
-vakh@chromium.org
+file://components/safe_browsing/OWNERS
diff --git a/chrome/browser/resources/settings/autofill_page/credit_card_list_entry.html b/chrome/browser/resources/settings/autofill_page/credit_card_list_entry.html
index 94db42b..b415aafa 100644
--- a/chrome/browser/resources/settings/autofill_page/credit_card_list_entry.html
+++ b/chrome/browser/resources/settings/autofill_page/credit_card_list_entry.html
@@ -4,11 +4,19 @@
         display: flex;
         flex: 1;
       }
+      .list-item {
+        margin-bottom: 8px;
+        margin-top: 8px;
+      }
 
       #creditCardExpiration {
         flex: 1;
       }
 
+      #summarySublabel {
+        color: var(--cr-secondary-text-color);
+      }
+
       .payments-label {
         color: var(--cr-secondary-text-color);
         margin-inline-start: 16px;
@@ -20,11 +28,17 @@
     </style>
     <div class="list-item">
       <div class="type-column">
-        <span id="creditCardLabel" class="ellipses">
-          [[creditCard.metadata.summaryLabel]]
-        </span>
+        <div class="summary-column">
+          <div id = "summaryLabel" class="ellipses">
+            [[creditCard.metadata.summaryLabel]]
+          </div>
+          <div id = "summarySublabel"
+            hidden$="[[!shouldShowSecondarySublabel_()]]" class="ellipses">
+            [[getSecondarySublabel_(creditCard.metadata)]]
+          </div>
+        </div>
         <span id="virtualCardLabel"
-            hidden$="[[!isVirtualCardEnrolled_(creditCard.metadata)]]">
+            hidden$="[[!shouldShowVirtualCardLabel_(creditCard.metadata)]]">
           $i18n{virtualCardEnabled}
         </span>
         <span class="payments-label"
diff --git a/chrome/browser/resources/settings/autofill_page/credit_card_list_entry.ts b/chrome/browser/resources/settings/autofill_page/credit_card_list_entry.ts
index 0b1d9395..4315681a 100644
--- a/chrome/browser/resources/settings/autofill_page/credit_card_list_entry.ts
+++ b/chrome/browser/resources/settings/autofill_page/credit_card_list_entry.ts
@@ -47,11 +47,23 @@
         },
         readOnly: true,
       },
+
+      /**
+       * Whether virtual card metadata on settings page is enabled.
+       */
+      virtualCardMetadataEnabled_: {
+        type: Boolean,
+        value() {
+          return loadTimeData.getBoolean('virtualCardMetadataEnabled');
+        },
+        readOnly: true,
+      },
     };
   }
 
   creditCard: chrome.autofillPrivate.CreditCardEntry;
   private virtualCardEnrollmentEnabled_: boolean;
+  private virtualCardMetadataEnabled_: boolean;
 
   /**
    * Opens the credit card action menu.
@@ -121,6 +133,32 @@
     return this.virtualCardEnrollmentEnabled_ &&
         this.creditCard.metadata!.isVirtualCardEnrolled!;
   }
+
+  private isVirtualCardMetadataEnabled_(): boolean {
+    return this.virtualCardMetadataEnabled_;
+  }
+
+  private shouldShowVirtualCardLabel_(): boolean {
+    return this.isVirtualCardEnrolled_() &&
+        !this.isVirtualCardMetadataEnabled_();
+  }
+
+  private shouldShowSecondarySublabel_(): boolean {
+    return !!(this.creditCard.metadata!.summarySublabel!.trim() !== '' ||
+              this.isVirtualCardEnrolled_() ||
+              this.isVirtualCardEnrollmentEligible_()) &&
+        this.isVirtualCardMetadataEnabled_();
+  }
+
+  private getSecondarySublabel_(): string {
+    if (this.isVirtualCardEnrolled_()) {
+      return this.i18nAdvanced('virtualCardTurnedOn');
+    }
+    if (this.isVirtualCardEnrollmentEligible_()) {
+      return this.i18nAdvanced('virtualCardAvailable');
+    }
+    return this.creditCard.metadata!.summarySublabel!;
+  }
 }
 
 customElements.define(
diff --git a/chrome/browser/resources/settings/chromeos/BUILD.gn b/chrome/browser/resources/settings/chromeos/BUILD.gn
index 24b68d93..184cc040 100644
--- a/chrome/browser/resources/settings/chromeos/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/BUILD.gn
@@ -433,12 +433,7 @@
     "os_privacy_page:web_components",
     "os_reset_page:web_components",
     "os_search_page:web_components",
-    "os_settings_main:web_components",
-    "os_settings_menu:web_components",
-    "os_settings_page:web_components",
     "os_settings_search_box:web_components",
-    "os_settings_ui:web_components",
-    "os_toolbar:web_components",
     "parental_controls_page:web_components",
     "personalization_page:web_components",
     "settings_scheduler_slider:web_components",
diff --git a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_subpage.js b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_subpage.js
index 151bb04..4903688 100644
--- a/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_subpage.js
+++ b/chrome/browser/resources/settings/chromeos/multidevice_page/multidevice_smartlock_subpage.js
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
-import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.js';
 import './multidevice_feature_toggle.js';
 import './multidevice_radio_button.js';
 import '../../settings_shared.css.js';
diff --git a/chrome/browser/resources/settings/chromeos/nearby_share_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/nearby_share_page/BUILD.gn
index c9c6b80..9e2a542 100644
--- a/chrome/browser/resources/settings/chromeos/nearby_share_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/nearby_share_page/BUILD.gn
@@ -53,7 +53,7 @@
     "//ui/webui/resources/cr_elements/cr_button:cr_button.m",
     "//ui/webui/resources/cr_elements/cr_dialog:cr_dialog.m",
     "//ui/webui/resources/cr_elements/cr_radio_button:cr_radio_button.m",
-    "//ui/webui/resources/cr_elements/cr_radio_group:cr_radio_group.m",
+    "//ui/webui/resources/cr_elements/cr_radio_group:cr_radio_group",
     "//ui/webui/resources/js:i18n_behavior.m",
   ]
 }
diff --git a/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_data_usage_dialog.js b/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_data_usage_dialog.js
index d370909a..c51300ce 100644
--- a/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_data_usage_dialog.js
+++ b/chrome/browser/resources/settings/chromeos/nearby_share_page/nearby_share_data_usage_dialog.js
@@ -11,7 +11,7 @@
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
-import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.js';
 
 import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/js/i18n_behavior.m.js';
 import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.ts b/chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.ts
index 533f5c53..aa43efc 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.ts
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/channel_switcher_dialog.ts
@@ -12,7 +12,7 @@
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
-import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.js';
 import 'chrome://resources/polymer/v3_0/iron-selector/iron-selector.js';
 import '../../settings_shared.css.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/os_about_page/edit_hostname_dialog.ts b/chrome/browser/resources/settings/chromeos/os_about_page/edit_hostname_dialog.ts
index fd7d408f..d0ff186 100644
--- a/chrome/browser/resources/settings/chromeos/os_about_page/edit_hostname_dialog.ts
+++ b/chrome/browser/resources/settings/chromeos/os_about_page/edit_hostname_dialog.ts
@@ -10,7 +10,7 @@
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
 import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
-import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.js';
 import 'chrome://resources/polymer/v3_0/iron-selector/iron-selector.js';
 import '../../settings_shared.css.js';
 
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/BUILD.gn
index 05c88a6..5ce48ec 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/BUILD.gn
@@ -252,7 +252,7 @@
     ":util",
     "../..:metrics_recorder",
     "//ui/webui/resources/cr_elements/cr_radio_button:cr_radio_button.m",
-    "//ui/webui/resources/cr_elements/cr_radio_group:cr_radio_group.m",
+    "//ui/webui/resources/cr_elements/cr_radio_group:cr_radio_group",
     "//ui/webui/resources/js/cr/ui:focus_without_ink.m",
   ]
 }
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/supported_links_item.js b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/supported_links_item.js
index 7f39fb4..9cf2106 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/supported_links_item.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/app_management_page/supported_links_item.js
@@ -6,7 +6,7 @@
 import './supported_links_dialog.js';
 import 'chrome://resources/cr_components/localized_link/localized_link.js';
 import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
-import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.js';
 
 import {AppManagementUserAction, AppType, WindowMode} from 'chrome://resources/cr_components/app_management/constants.js';
 import {recordAppManagementUserAction} from 'chrome://resources/cr_components/app_management/util.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.js b/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.js
index 1a12668..0c1a1e21 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.js
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.js
@@ -16,7 +16,7 @@
 
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
-import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.js';
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
 import 'chrome://resources/cr_elements/policy/cr_policy_indicator.m.js';
 import '../../controls/settings_toggle_button.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.gni b/chrome/browser/resources/settings/chromeos/os_settings.gni
index c0cc1ce..ded5bd0e 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.gni
+++ b/chrome/browser/resources/settings/chromeos/os_settings.gni
@@ -14,6 +14,12 @@
   "chromeos/os_about_page/edit_hostname_dialog.ts",
   "chromeos/os_about_page/os_about_page.ts",
   "chromeos/os_about_page/update_warning_dialog.ts",
+  "chromeos/os_settings_main/os_settings_main.js",
+  "chromeos/os_settings_menu/os_settings_menu.js",
+  "chromeos/os_settings_page/os_settings_page.js",
+  "chromeos/os_settings_page/settings_idle_load.js",
+  "chromeos/os_settings_ui/os_settings_ui.js",
+  "chromeos/os_toolbar/os_toolbar.js",
 ]
 
 # Files that are passed as input to html_to_wrapper().
@@ -327,14 +333,8 @@
   "chromeos/os_search_page/search_engine.js",
   "chromeos/os_search_page/search_subpage.js",
   "chromeos/os_settings_icons_css.js",
-  "chromeos/os_settings_main/os_settings_main.js",
-  "chromeos/os_settings_menu/os_settings_menu.js",
-  "chromeos/os_settings_page/os_settings_page.js",
-  "chromeos/os_settings_page/settings_idle_load.js",
   "chromeos/os_settings_search_box/os_search_result_row.js",
   "chromeos/os_settings_search_box/os_settings_search_box.js",
-  "chromeos/os_settings_ui/os_settings_ui.js",
-  "chromeos/os_toolbar/os_toolbar.js",
   "chromeos/parental_controls_page/parental_controls_page.js",
   "chromeos/personalization_page/change_picture.js",
   "chromeos/personalization_page/personalization_page.js",
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_main/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_settings_main/BUILD.gn
index 245e660..51f630b5 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_main/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_settings_main/BUILD.gn
@@ -3,7 +3,6 @@
 # found in the LICENSE file.
 
 import("//third_party/closure_compiler/compile_js.gni")
-import("//tools/polymer/html_to_js.gni")
 import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
@@ -23,7 +22,3 @@
     "//ui/webui/resources/js:assert.m",
   ]
 }
-
-html_to_js("web_components") {
-  js_files = [ "os_settings_main.js" ]
-}
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_main/os_settings_main.js b/chrome/browser/resources/settings/chromeos/os_settings_main/os_settings_main.js
index 85376aac..c66a4daa 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_main/os_settings_main.js
+++ b/chrome/browser/resources/settings/chromeos/os_settings_main/os_settings_main.js
@@ -17,7 +17,7 @@
 import '../../settings_shared.css.js';
 import '../../settings_vars.css.js';
 
-import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {loadTimeData} from '../../i18n_setup.js';
 import {Route, Router} from '../../router.js';
@@ -25,6 +25,8 @@
 import {routes} from '../os_route.js';
 import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
 
+import {getTemplate} from './os_settings_main.html.js';
+
 /**
  * @typedef {{about: boolean, settings: boolean}}
  */
@@ -45,7 +47,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_menu/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_settings_menu/BUILD.gn
index 2d79465..ab808f8 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_menu/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_settings_menu/BUILD.gn
@@ -3,7 +3,6 @@
 # found in the LICENSE file.
 
 import("//third_party/closure_compiler/compile_js.gni")
-import("//tools/polymer/html_to_js.gni")
 import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
@@ -21,7 +20,3 @@
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
   ]
 }
-
-html_to_js("web_components") {
-  js_files = [ "os_settings_menu.js" ]
-}
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.js b/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.js
index 48e2da0..ac583d06 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.js
+++ b/chrome/browser/resources/settings/chromeos/os_settings_menu/os_settings_menu.js
@@ -15,12 +15,14 @@
 import '../os_icons.js';
 
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
-import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Route, Router} from '../../router.js';
 import {routes} from '../os_route.js';
 import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
 
+import {getTemplate} from './os_settings_menu.html.js';
+
 /**
  * @constructor
  * @extends {PolymerElement}
@@ -36,7 +38,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_settings_page/BUILD.gn
index e19229a2..694b6f71 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_settings_page/BUILD.gn
@@ -3,7 +3,6 @@
 # found in the LICENSE file.
 
 import("//third_party/closure_compiler/compile_js.gni")
-import("//tools/polymer/html_to_js.gni")
 import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
@@ -61,10 +60,3 @@
     "//ui/webui/resources/js:assert.m",
   ]
 }
-
-html_to_js("web_components") {
-  js_files = [
-    "os_settings_page.js",
-    "settings_idle_load.js",
-  ]
-}
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.js b/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.js
index 3b8bd56..64c8c01 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_settings_page/os_settings_page.js
@@ -32,7 +32,7 @@
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {WebUIListenerBehavior, WebUIListenerBehaviorInterface} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
-import {beforeNextRender, html, microTask, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {beforeNextRender, microTask, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Route, Router} from '../../router.js';
 import {AndroidAppsBrowserProxyImpl, AndroidAppsInfo} from '../os_apps_page/android_apps_browser_proxy.js';
@@ -41,6 +41,7 @@
 import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
 
 import {MainPageBehavior, MainPageBehaviorInterface} from './main_page_behavior.js';
+import {getTemplate} from './os_settings_page.html.js';
 
 /**
  * @constructor
@@ -60,7 +61,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_page/settings_idle_load.js b/chrome/browser/resources/settings/chromeos/os_settings_page/settings_idle_load.js
index c205eea..585a277 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_page/settings_idle_load.js
+++ b/chrome/browser/resources/settings/chromeos/os_settings_page/settings_idle_load.js
@@ -10,10 +10,12 @@
  */
 
 import {assert} from 'chrome://resources/js/assert.m.js';
-import {html, PolymerElement, TemplateInstanceBase, templatize} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement, TemplateInstanceBase, templatize} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {ensureLazyLoaded} from '../ensure_lazy_loaded.js';
 
+import {getTemplate} from './settings_idle_load.html.js';
+
 /** @polymer */
 class SettingsIdleLoadElement extends PolymerElement {
   static get is() {
@@ -21,7 +23,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_ui/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_settings_ui/BUILD.gn
index 87b2bca..60f3e8f2 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_ui/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_settings_ui/BUILD.gn
@@ -3,7 +3,6 @@
 # found in the LICENSE file.
 
 import("//third_party/closure_compiler/compile_js.gni")
-import("//tools/polymer/html_to_js.gni")
 import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
@@ -33,7 +32,3 @@
     "//ui/webui/resources/cr_elements/cr_drawer/cr_drawer_externs.js",
   ]
 }
-
-html_to_js("web_components") {
-  js_files = [ "os_settings_ui.js" ]
-}
diff --git a/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.js b/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.js
index 81164921..757440b 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.js
+++ b/chrome/browser/resources/settings/chromeos/os_settings_ui/os_settings_ui.js
@@ -26,7 +26,7 @@
 import {FindShortcutBehavior, FindShortcutBehaviorInterface} from 'chrome://resources/cr_elements/find_shortcut_behavior.js';
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {listenOnce} from 'chrome://resources/js/util.m.js';
-import {Debouncer, html, microTask, mixinBehaviors, PolymerElement, timeOut} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {Debouncer, microTask, mixinBehaviors, PolymerElement, timeOut} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {loadTimeData} from '../../i18n_setup.js';
 import {SettingChangeValue} from '../../mojom-webui/search/user_action_recorder.mojom-webui.js';
@@ -38,6 +38,8 @@
 import {PrefToSettingMetricConverter} from '../pref_to_setting_metric_converter.js';
 import {RouteObserverBehavior, RouteObserverBehaviorInterface} from '../route_observer_behavior.js';
 
+import {getTemplate} from './os_settings_ui.html.js';
+
 /** Global defined when the main Settings script runs. */
 let defaultResourceLoaded = true;  // eslint-disable-line prefer-const
 
@@ -68,7 +70,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
diff --git a/chrome/browser/resources/settings/chromeos/os_toolbar/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_toolbar/BUILD.gn
index 9b8ca80..90205371 100644
--- a/chrome/browser/resources/settings/chromeos/os_toolbar/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_toolbar/BUILD.gn
@@ -3,7 +3,6 @@
 # found in the LICENSE file.
 
 import("//third_party/closure_compiler/compile_js.gni")
-import("//tools/polymer/html_to_js.gni")
 import("../os_settings.gni")
 
 js_type_check("closure_compile_module") {
@@ -21,7 +20,3 @@
   ]
   externs_list = [ "//ui/webui/resources/cr_elements/cr_toolbar/cr_toolbar_search_field_externs.js" ]
 }
-
-html_to_js("web_components") {
-  js_files = [ "os_toolbar.js" ]
-}
diff --git a/chrome/browser/resources/settings/chromeos/os_toolbar/os_toolbar.js b/chrome/browser/resources/settings/chromeos/os_toolbar/os_toolbar.js
index 3997681..634087c 100644
--- a/chrome/browser/resources/settings/chromeos/os_toolbar/os_toolbar.js
+++ b/chrome/browser/resources/settings/chromeos/os_toolbar/os_toolbar.js
@@ -11,7 +11,9 @@
 import '../os_settings_search_box/os_settings_search_box.js';
 import 'chrome://resources/cr_elements/cr_toolbar/cr_toolbar_search_field.js';
 
-import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {getTemplate} from './os_toolbar.html.js';
 
 /** @polymer */
 class OsToolbarElement extends PolymerElement {
@@ -20,7 +22,7 @@
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
diff --git a/chrome/browser/resources/settings/controls/settings_radio_group.ts b/chrome/browser/resources/settings/controls/settings_radio_group.ts
index 47b6760..6e7b75e 100644
--- a/chrome/browser/resources/settings/controls/settings_radio_group.ts
+++ b/chrome/browser/resources/settings/controls/settings_radio_group.ts
@@ -13,7 +13,7 @@
  *      </settings-radio-group>
  */
 import '//resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
-import '//resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import '//resources/cr_elements/cr_radio_group/cr_radio_group.js';
 import '../settings_shared.css.js';
 
 import {PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
diff --git a/chrome/browser/resources/settings/people_page/sync_controls.ts b/chrome/browser/resources/settings/people_page/sync_controls.ts
index 0eba1fc0..4b277bc88a 100644
--- a/chrome/browser/resources/settings/people_page/sync_controls.ts
+++ b/chrome/browser/resources/settings/people_page/sync_controls.ts
@@ -5,7 +5,7 @@
 import '//resources/js/util.m.js';
 import '//resources/cr_components/localized_link/localized_link.js';
 import '//resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
-import '//resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import '//resources/cr_elements/cr_radio_group/cr_radio_group.js';
 import '//resources/cr_elements/cr_toggle/cr_toggle.m.js';
 import '//resources/cr_elements/shared_style_css.m.js';
 import '//resources/cr_elements/shared_vars_css.m.js';
diff --git a/chrome/browser/resources/settings/people_page/sync_encryption_options.ts b/chrome/browser/resources/settings/people_page/sync_encryption_options.ts
index 34b9b74..ccbe493 100644
--- a/chrome/browser/resources/settings/people_page/sync_encryption_options.ts
+++ b/chrome/browser/resources/settings/people_page/sync_encryption_options.ts
@@ -5,14 +5,14 @@
 import '//resources/cr_elements/cr_button/cr_button.m.js';
 import '//resources/cr_elements/cr_input/cr_input.m.js';
 import '//resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
-import '//resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import '//resources/cr_elements/cr_radio_group/cr_radio_group.js';
 import '//resources/cr_elements/shared_style_css.m.js';
 import '../settings_shared.css.js';
 import '../settings_vars.css.js';
 
 import {CrInputElement} from '//resources/cr_elements/cr_input/cr_input.m.js';
 // <if expr="chromeos_ash">
-import {CrRadioGroupElement} from '//resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import {CrRadioGroupElement} from '//resources/cr_elements/cr_radio_group/cr_radio_group.js';
 // </if>
 
 import {assert} from '//resources/js/assert_ts.js';
diff --git a/chrome/browser/resources/settings/privacy_page/secure_dns.ts b/chrome/browser/resources/settings/privacy_page/secure_dns.ts
index 0882e65..31d98d9 100644
--- a/chrome/browser/resources/settings/privacy_page/secure_dns.ts
+++ b/chrome/browser/resources/settings/privacy_page/secure_dns.ts
@@ -15,14 +15,14 @@
  */
 
 import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
-import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.js';
 import 'chrome://resources/cr_elements/md_select_css.m.js';
 import '../controls/settings_toggle_button.js';
 import '../prefs/prefs.js';
 import '../settings_shared.css.js';
 import './secure_dns_input.js';
 
-import {CrRadioGroupElement} from 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import {CrRadioGroupElement} from 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.js';
 import {assertNotReached} from 'chrome://resources/js/assert_ts.js';
 import {WebUIListenerMixin} from 'chrome://resources/js/web_ui_listener_mixin.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
diff --git a/chrome/browser/resources/settings/settings.ts b/chrome/browser/resources/settings/settings.ts
index affedc8..1e1bf117 100644
--- a/chrome/browser/resources/settings/settings.ts
+++ b/chrome/browser/resources/settings/settings.ts
@@ -9,7 +9,7 @@
 export {CrDrawerElement} from 'chrome://resources/cr_elements/cr_drawer/cr_drawer.js';
 export {CrLinkRowElement} from 'chrome://resources/cr_elements/cr_link_row/cr_link_row.js';
 export {CrRadioButtonElement} from 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
-export {CrRadioGroupElement} from 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+export {CrRadioGroupElement} from 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.js';
 export {CrToggleElement} from 'chrome://resources/cr_elements/cr_toggle/cr_toggle.m.js';
 export {CrToolbarElement} from 'chrome://resources/cr_elements/cr_toolbar/cr_toolbar.js';
 export {CrToolbarSearchFieldElement} from 'chrome://resources/cr_elements/cr_toolbar/cr_toolbar_search_field.js';
diff --git a/chrome/browser/resources/settings/site_settings/protocol_handlers.ts b/chrome/browser/resources/settings/site_settings/protocol_handlers.ts
index e093fc4..c655183 100644
--- a/chrome/browser/resources/settings/site_settings/protocol_handlers.ts
+++ b/chrome/browser/resources/settings/site_settings/protocol_handlers.ts
@@ -10,7 +10,7 @@
 
 import 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.js';
 import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
-import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.js';
 import 'chrome://resources/cr_elements/cr_link_row/cr_link_row.js';
 import 'chrome://resources/cr_elements/cr_toggle/cr_toggle.m.js';
 import 'chrome://resources/cr_elements/shared_style_css.m.js';
diff --git a/chrome/browser/resources/side_panel/images/read_anything_dark.png b/chrome/browser/resources/side_panel/images/read_anything_dark.png
new file mode 100644
index 0000000..b8f9f73
--- /dev/null
+++ b/chrome/browser/resources/side_panel/images/read_anything_dark.png
Binary files differ
diff --git a/chrome/browser/resources/side_panel/images/read_anything_default.png b/chrome/browser/resources/side_panel/images/read_anything_default.png
new file mode 100644
index 0000000..b0b67a7
--- /dev/null
+++ b/chrome/browser/resources/side_panel/images/read_anything_default.png
Binary files differ
diff --git a/chrome/browser/resources/side_panel/images/read_anything_light.png b/chrome/browser/resources/side_panel/images/read_anything_light.png
new file mode 100644
index 0000000..7c1764b7
--- /dev/null
+++ b/chrome/browser/resources/side_panel/images/read_anything_light.png
Binary files differ
diff --git a/chrome/browser/resources/side_panel/images/read_anything_yellow.png b/chrome/browser/resources/side_panel/images/read_anything_yellow.png
new file mode 100644
index 0000000..5d218080
--- /dev/null
+++ b/chrome/browser/resources/side_panel/images/read_anything_yellow.png
Binary files differ
diff --git a/chrome/browser/resources/side_panel/read_anything/app.html b/chrome/browser/resources/side_panel/read_anything/app.html
index 21e59de6..186c7b8 100644
--- a/chrome/browser/resources/side_panel/read_anything/app.html
+++ b/chrome/browser/resources/side_panel/read_anything/app.html
@@ -1,9 +1,8 @@
 <style>
   #container {
-    background: var(--google-grey-900);
-    color: var(--google-grey-200);
-    font-size: 18px;
-    padding:  20px;
+    background-color: var(--background-color);
+    color:  var(--foreground-color);
+    padding:  20px;  
   }
 </style>
 <div id="container"
diff --git a/chrome/browser/resources/side_panel/read_anything/app.ts b/chrome/browser/resources/side_panel/read_anything/app.ts
index 1e64e1e..399d9638 100644
--- a/chrome/browser/resources/side_panel/read_anything/app.ts
+++ b/chrome/browser/resources/side_panel/read_anything/app.ts
@@ -5,7 +5,9 @@
 import '../strings.m.js';
 
 import {assert} from 'chrome://resources/js/assert_ts.js';
+import {skColorToRgba} from 'chrome://resources/js/color_utils.js';
 import {WebUIListenerMixin} from 'chrome://resources/js/web_ui_listener_mixin.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';
 
 import {getTemplate} from './app.html.js';
@@ -53,6 +55,8 @@
 
   private fontName_: string;
   private fontSize_: number;
+  private foregroundColor_: SkColor = new SkColor();
+  private backgroundColor_: SkColor = new SkColor();
 
   // Defines the valid font names that can be passed to front-end and maps
   // them to a corresponding class style in app.html. Must stay in-sync with
@@ -178,6 +182,13 @@
   updateTheme() {
     this.fontName_ = this.validatedFontName();
     this.fontSize_ = chrome.readAnything.fontSize;
+    this.foregroundColor_.value = chrome.readAnything.foregroundColor;
+    this.backgroundColor_.value = chrome.readAnything.backgroundColor;
+
+    this.updateStyles({
+      '--foreground-color': skColorToRgba(this.foregroundColor_),
+      '--background-color': skColorToRgba(this.backgroundColor_),
+    });
   }
 }
 
diff --git a/chrome/browser/resources/side_panel/read_anything/read_anything.d.ts b/chrome/browser/resources/side_panel/read_anything/read_anything.d.ts
index 6402a07..6282645 100644
--- a/chrome/browser/resources/side_panel/read_anything/read_anything.d.ts
+++ b/chrome/browser/resources/side_panel/read_anything/read_anything.d.ts
@@ -17,6 +17,8 @@
     // Items in the ReadAnythingTheme struct, see read_anything.mojom for info.
     let fontName: string;
     let fontSize: number;
+    let foregroundColor: number;
+    let backgroundColor: number;
 
     // Returns a list of AXNodeIDs corresponding to the unignored children of
     // the AXNode for the provided AXNodeID.
@@ -69,7 +71,9 @@
         snapshotLite: Object, contentNodeIds: number[]): void;
 
     // Set the theme. Used by tests only.
-    function setThemeForTesting(fontName: string, fontSize: number): void;
+    function setThemeForTesting(
+        fontName: string, fontSize: number, foregroundColor: number,
+        backgroundColor: number): void;
 
     ////////////////////////////////////////////////////////////////
     // Implemented in read_anything/app.ts and called by native c++.
diff --git a/chrome/browser/resources/signin/signin_email_confirmation/signin_email_confirmation_app.ts b/chrome/browser/resources/signin/signin_email_confirmation/signin_email_confirmation_app.ts
index c068727..3ce20c0 100644
--- a/chrome/browser/resources/signin/signin_email_confirmation/signin_email_confirmation_app.ts
+++ b/chrome/browser/resources/signin/signin_email_confirmation/signin_email_confirmation_app.ts
@@ -4,7 +4,7 @@
 
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
-import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.js';
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
 import './signin_shared.css.js';
 import './strings.m.js';
diff --git a/chrome/browser/resources/support_tool/pii_selection.ts b/chrome/browser/resources/support_tool/pii_selection.ts
index 26fb2de..a68643a9 100644
--- a/chrome/browser/resources/support_tool/pii_selection.ts
+++ b/chrome/browser/resources/support_tool/pii_selection.ts
@@ -6,7 +6,7 @@
 import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import 'chrome://resources/cr_elements/cr_expand_button/cr_expand_button.js';
 import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
-import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.js';
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
 import './support_tool_shared.css.js';
 
diff --git a/chrome/browser/resources/webui_gallery/demos/cr_radio_demo.html b/chrome/browser/resources/webui_gallery/demos/cr_radio_demo.html
index 21812dcb..2203dbc6 100644
--- a/chrome/browser/resources/webui_gallery/demos/cr_radio_demo.html
+++ b/chrome/browser/resources/webui_gallery/demos/cr_radio_demo.html
@@ -23,7 +23,7 @@
       </template>
     </dom-bind>
 
-    <script src="chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js"
+    <script src="chrome://resources/cr_elements/cr_radio_group/cr_radio_group.js"
         type="module"></script>
     <script src="chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js"
         type="module"></script>
diff --git a/chrome/browser/safe_browsing/OWNERS b/chrome/browser/safe_browsing/OWNERS
index 3e4d613..5225674 100644
--- a/chrome/browser/safe_browsing/OWNERS
+++ b/chrome/browser/safe_browsing/OWNERS
@@ -1,6 +1 @@
-drubery@chromium.org
-nparker@chromium.org
-vakh@chromium.org
-xinghuilu@chromium.org
-
-per-file *password_protection*=file://components/safe_browsing/content/password_protection/OWNERS
+file://components/safe_browsing/OWNERS
diff --git a/chrome/browser/safe_browsing/download_protection/OWNERS b/chrome/browser/safe_browsing/download_protection/OWNERS
deleted file mode 100644
index 70e4b68..0000000
--- a/chrome/browser/safe_browsing/download_protection/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-drubery@chromium.org
-nparker@chromium.org
-vakh@chromium.org
diff --git a/chrome/browser/signin/account_consistency_mode_manager_factory.cc b/chrome/browser/signin/account_consistency_mode_manager_factory.cc
index db15271..e5a81dc9 100644
--- a/chrome/browser/signin/account_consistency_mode_manager_factory.cc
+++ b/chrome/browser/signin/account_consistency_mode_manager_factory.cc
@@ -22,9 +22,8 @@
 }
 
 AccountConsistencyModeManagerFactory::AccountConsistencyModeManagerFactory()
-    : ProfileKeyedServiceFactory(
-          "AccountConsistencyModeManager",
-          ProfileSelections::BuildServicesForRegularProfile()) {}
+    : ProfileKeyedServiceFactory("AccountConsistencyModeManager",
+                                 ProfileSelections::BuildForRegularProfile()) {}
 
 AccountConsistencyModeManagerFactory::~AccountConsistencyModeManagerFactory() =
     default;
diff --git a/chrome/browser/storage_access_api/api_browsertest.cc b/chrome/browser/storage_access_api/api_browsertest.cc
index ea3a3e5..1a0e540 100644
--- a/chrome/browser/storage_access_api/api_browsertest.cc
+++ b/chrome/browser/storage_access_api/api_browsertest.cc
@@ -49,11 +49,32 @@
 
 enum class TestType { kFrame, kWorker };
 
-class StorageAccessAPIBrowserTest : public InProcessBrowserTest {
+std::string BoolToString(bool b) {
+  return b ? "true" : "false";
+}
+
+class StorageAccessAPIBaseBrowserTest : public InProcessBrowserTest {
  protected:
-  StorageAccessAPIBrowserTest()
-      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
-    feature_enable_.InitAndEnableFeature(net::features::kStorageAccessAPI);
+  StorageAccessAPIBaseBrowserTest(bool permission_grants_unpartitioned_storage,
+                                  bool is_storage_partitioned)
+      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS),
+        permission_grants_unpartitioned_storage_(
+            permission_grants_unpartitioned_storage),
+        is_storage_partitioned_(is_storage_partitioned) {
+    std::vector<base::test::ScopedFeatureList::FeatureAndParams> enabled({
+        {net::features::kStorageAccessAPI,
+         {{"storage-access-api-grants-unpartitioned-storage",
+           BoolToString(permission_grants_unpartitioned_storage)}}},
+    });
+    std::vector<base::Feature> disabled;
+
+    if (is_storage_partitioned) {
+      enabled.push_back({net::features::kThirdPartyStoragePartitioning, {}});
+    } else {
+      disabled.push_back(net::features::kThirdPartyStoragePartitioning);
+    }
+
+    features_.InitWithFeaturesAndParameters(enabled, disabled);
   }
 
   void SetUpOnMainThread() override {
@@ -148,14 +169,30 @@
 
   net::test_server::EmbeddedTestServer& https_server() { return https_server_; }
 
+  bool PermissionGrantsUnpartitionedStorage() const {
+    return permission_grants_unpartitioned_storage_;
+  }
+  bool IsStoragePartitioned() const { return is_storage_partitioned_; }
+
  private:
   net::test_server::EmbeddedTestServer https_server_;
-  base::test::ScopedFeatureList feature_enable_;
+  base::test::ScopedFeatureList features_;
+  bool permission_grants_unpartitioned_storage_;
+  bool is_storage_partitioned_;
+};
+
+class StorageAccessAPIBrowserTest
+    : public StorageAccessAPIBaseBrowserTest,
+      public testing::WithParamInterface<std::tuple<bool, bool>> {
+ public:
+  StorageAccessAPIBrowserTest()
+      : StorageAccessAPIBaseBrowserTest(std::get<0>(GetParam()),
+                                        std::get<1>(GetParam())) {}
 };
 
 // Validate that if an iframe requests access that cookies become unblocked for
 // just that top-level/third-party combination.
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
+IN_PROC_BROWSER_TEST_P(StorageAccessAPIBrowserTest,
                        ThirdPartyCookiesIFrameRequestsAccess) {
   SetBlockThirdPartyCookies(true);
   base::HistogramTester histogram_tester;
@@ -272,7 +309,7 @@
 
 // Validate that the Storage Access API does not override any explicit user
 // settings to block storage access.
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
+IN_PROC_BROWSER_TEST_P(StorageAccessAPIBrowserTest,
                        ThirdPartyCookiesIFrameThirdPartyExceptions) {
   SetBlockThirdPartyCookies(true);
 
@@ -322,43 +359,8 @@
   EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));
 }
 
-// Test third-party cookie blocking of features that allow to communicate
-// between tabs such as SharedWorkers.
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, MultiTabTest) {
-  NavigateToPageWithFrame("a.com");
-  NavigateFrameTo("b.com", "/browsing_data/site_data.html");
-
-  storage::test::ExpectCrossTabInfoForFrame(GetFrame(), false);
-  storage::test::SetCrossTabInfoForFrame(GetFrame());
-  storage::test::ExpectCrossTabInfoForFrame(GetFrame(), true);
-  EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
-
-  // Create a second tab to test communication between tabs.
-  NavigateToNewTabWithFrame("a.com");
-  NavigateFrameTo("b.com", "/browsing_data/site_data.html");
-  storage::test::ExpectCrossTabInfoForFrame(GetFrame(), true);
-  EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
-
-  SetBlockThirdPartyCookies(true);
-
-  NavigateToPageWithFrame("a.com");
-  NavigateFrameTo("b.com", "/browsing_data/site_data.html");
-  storage::test::ExpectCrossTabInfoForFrame(GetFrame(), false);
-  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
-
-  // Allow all requests to b.com to access cookies.
-  // Allow all requests to b.com on a.com to access storage.
-  EXPECT_TRUE(storage::test::RequestStorageAccessForFrame(GetFrame()));
-  EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
-
-  NavigateToPageWithFrame("a.com");
-  NavigateFrameTo("b.com", "/browsing_data/site_data.html");
-  storage::test::ExpectCrossTabInfoForFrame(GetFrame(), true);
-  EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
-}
-
 // Validates that once a grant is removed access is also removed.
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
+IN_PROC_BROWSER_TEST_P(StorageAccessAPIBrowserTest,
                        ThirdPartyGrantsDeletedAccess) {
   SetBlockThirdPartyCookies(true);
 
@@ -393,7 +395,7 @@
   EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
 }
 
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, OpaqueOriginRejects) {
+IN_PROC_BROWSER_TEST_P(StorageAccessAPIBrowserTest, OpaqueOriginRejects) {
   SetBlockThirdPartyCookies(true);
 
   NavigateToPageWithFrame("a.com");
@@ -407,7 +409,7 @@
   EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
 }
 
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
+IN_PROC_BROWSER_TEST_P(StorageAccessAPIBrowserTest,
                        MissingSandboxTokenRejects) {
   SetBlockThirdPartyCookies(true);
 
@@ -422,7 +424,7 @@
   EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
 }
 
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, SandboxTokenResolves) {
+IN_PROC_BROWSER_TEST_P(StorageAccessAPIBrowserTest, SandboxTokenResolves) {
   SetBlockThirdPartyCookies(true);
 
   NavigateToPageWithFrame("a.com");
@@ -438,7 +440,7 @@
 }
 
 // Validates that expiry data is transferred over IPC to the Network Service.
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
+IN_PROC_BROWSER_TEST_P(StorageAccessAPIBrowserTest,
                        ThirdPartyGrantsExpireOverIPC) {
   SetBlockThirdPartyCookies(true);
 
@@ -503,14 +505,23 @@
   EXPECT_EQ(ReadCookiesViaJS(GetNestedFrame()), "thirdparty=c");
 }
 
+INSTANTIATE_TEST_CASE_P(/* no prefix */,
+                        StorageAccessAPIBrowserTest,
+                        testing::Combine(testing::Bool(), testing::Bool()));
+
 class StorageAccessAPIStorageBrowserTest
-    : public StorageAccessAPIBrowserTest,
-      public testing::WithParamInterface<TestType> {
+    : public StorageAccessAPIBaseBrowserTest,
+      public testing::WithParamInterface<std::tuple<TestType, bool, bool>> {
  public:
+  StorageAccessAPIStorageBrowserTest()
+      : StorageAccessAPIBaseBrowserTest(std::get<1>(GetParam()),
+                                        std::get<2>(GetParam())) {}
+
   void ExpectStorage(content::RenderFrameHost* frame, bool expected) {
     switch (GetTestType()) {
       case TestType::kFrame:
-        storage::test::ExpectStorageForFrame(frame, expected);
+        storage::test::ExpectStorageForFrame(frame, /*include_cookies=*/false,
+                                             expected);
         return;
       case TestType::kWorker:
         storage::test::ExpectStorageForWorker(frame, expected);
@@ -521,7 +532,7 @@
   void SetStorage(content::RenderFrameHost* frame) {
     switch (GetTestType()) {
       case TestType::kFrame:
-        storage::test::SetStorageForFrame(frame);
+        storage::test::SetStorageForFrame(frame, /*include_cookies=*/false);
         return;
       case TestType::kWorker:
         storage::test::SetStorageForWorker(frame);
@@ -529,8 +540,12 @@
     }
   }
 
+  bool DoesPermissionGrantStorage() const {
+    return IsStoragePartitioned() || PermissionGrantsUnpartitionedStorage();
+  }
+
  private:
-  TestType GetTestType() const { return GetParam(); }
+  TestType GetTestType() const { return std::get<0>(GetParam()); }
 };
 
 // Validate that the Storage Access API will unblock other types of storage
@@ -558,7 +573,7 @@
 
   NavigateToPageWithFrame("a.com");
   NavigateFrameTo("b.com", "/browsing_data/site_data.html");
-  ExpectStorage(GetFrame(), true);
+  ExpectStorage(GetFrame(), DoesPermissionGrantStorage());
   EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
 }
 
@@ -587,12 +602,51 @@
   NavigateToPageWithFrame("a.com");
   NavigateFrameTo("b.com", "/iframe.html");
   NavigateNestedFrameTo("c.com", "/browsing_data/site_data.html");
-  ExpectStorage(GetNestedFrame(), true);
+  ExpectStorage(GetNestedFrame(), DoesPermissionGrantStorage());
   EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetNestedFrame()));
 }
 
+// Test third-party cookie blocking of features that allow to communicate
+// between tabs such as SharedWorkers.
+IN_PROC_BROWSER_TEST_P(StorageAccessAPIStorageBrowserTest, MultiTabTest) {
+  NavigateToPageWithFrame("a.com");
+  NavigateFrameTo("b.com", "/browsing_data/site_data.html");
+
+  storage::test::ExpectCrossTabInfoForFrame(GetFrame(), false);
+  storage::test::SetCrossTabInfoForFrame(GetFrame());
+  storage::test::ExpectCrossTabInfoForFrame(GetFrame(), true);
+  EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
+
+  // Create a second tab to test communication between tabs.
+  NavigateToNewTabWithFrame("a.com");
+  NavigateFrameTo("b.com", "/browsing_data/site_data.html");
+  storage::test::ExpectCrossTabInfoForFrame(GetFrame(), true);
+  EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
+
+  SetBlockThirdPartyCookies(true);
+
+  NavigateToPageWithFrame("a.com");
+  NavigateFrameTo("b.com", "/browsing_data/site_data.html");
+  storage::test::ExpectCrossTabInfoForFrame(GetFrame(), false);
+  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
+
+  // Allow all requests to b.com to access cookies.
+  // Allow all requests to b.com on a.com to access storage.
+  EXPECT_TRUE(storage::test::RequestStorageAccessForFrame(GetFrame()));
+  EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
+
+  NavigateToPageWithFrame("a.com");
+  NavigateFrameTo("b.com", "/browsing_data/site_data.html");
+  storage::test::ExpectCrossTabInfoForFrame(GetFrame(),
+                                            DoesPermissionGrantStorage());
+  EXPECT_TRUE(storage::test::HasStorageAccessForFrame(GetFrame()));
+}
+
 INSTANTIATE_TEST_SUITE_P(/*no prefix*/,
                          StorageAccessAPIStorageBrowserTest,
-                         testing::Values(TestType::kFrame, TestType::kWorker));
+                         testing::Combine(testing::Values(TestType::kFrame,
+                                                          TestType::kWorker),
+                                          testing::Bool(),
+                                          testing::Bool()));
 
 }  // namespace
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_file_system_delegate.cc b/chrome/browser/ui/ash/holding_space/holding_space_file_system_delegate.cc
index 2adad6e1..a83af60 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_file_system_delegate.cc
+++ b/chrome/browser/ui/ash/holding_space/holding_space_file_system_delegate.cc
@@ -20,6 +20,7 @@
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
 #include "base/threading/sequenced_task_runner_handle.h"
+#include "base/time/time.h"
 #include "chrome/browser/ash/arc/arc_util.h"
 #include "chrome/browser/ash/drive/drive_integration_service.h"
 #include "chrome/browser/ash/file_manager/path_util.h"
@@ -126,8 +127,12 @@
  private:
   void OnFilePathChanged(const base::FilePath& file_path, bool error) {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    content::GetUIThreadTaskRunner({})->PostTask(
-        FROM_HERE, base::BindOnce(callback_, file_path, error));
+    // In tests `OnFilePathChanged()` events are sometimes propagated before
+    // `OnFileMoved()` events. Delay propagation of `OnFilePathChanged()`
+    // events to give `OnFileMoved()` events time to propagate.
+    content::GetUIThreadTaskRunner({})->PostDelayedTask(
+        FROM_HERE, base::BindOnce(callback_, file_path, error),
+        base::Milliseconds(1));
   }
 
   SEQUENCE_CHECKER(sequence_checker_);
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_unittest.cc b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_unittest.cc
index e5b2974..b43e663 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_unittest.cc
+++ b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_unittest.cc
@@ -786,11 +786,9 @@
             persisted_holding_space_items);
 }
 
-// TODO(crbug.com/1315122): Fix flakes and re-enable.
 // Verifies that when a file backing a holding space item is moved, the holding
 // space item is updated in place and persistence storage is updated.
-TEST_F(HoldingSpaceKeyedServiceTest,
-       DISABLED_UpdatePersistentStorageAfterMove) {
+TEST_F(HoldingSpaceKeyedServiceTest, UpdatePersistentStorageAfterMove) {
   // Create a file system mount point.
   std::unique_ptr<ScopedTestMountPoint> downloads_mount =
       ScopedTestMountPoint::CreateAndMountDownloads(GetProfile());
@@ -912,12 +910,11 @@
   }
 }
 
-// TODO(crbug.com/1170667): Fix flakes and re-enable.
 // Tests that holding space item's image representation gets updated when the
 // backing file is changed using move operation. Furthermore, verifies that
 // conflicts caused by moving a holding space item file to another path present
 // in the holding space get resolved.
-TEST_F(HoldingSpaceKeyedServiceTest, DISABLED_UpdateItemsOverwrittenByMove) {
+TEST_F(HoldingSpaceKeyedServiceTest, UpdateItemsOverwrittenByMove) {
   // Create a file system mount point.
   std::unique_ptr<ScopedTestMountPoint> downloads_mount =
       ScopedTestMountPoint::CreateAndMountDownloads(GetProfile());
diff --git a/chrome/browser/ui/page_info/chrome_page_info_delegate.cc b/chrome/browser/ui/page_info/chrome_page_info_delegate.cc
index 18706b7..1fe1e918 100644
--- a/chrome/browser/ui/page_info/chrome_page_info_delegate.cc
+++ b/chrome/browser/ui/page_info/chrome_page_info_delegate.cc
@@ -32,8 +32,11 @@
 #include "components/security_interstitials/content/stateful_ssl_host_state_delegate.h"
 #include "components/subresource_filter/content/browser/subresource_filter_content_settings_manager.h"
 #include "components/subresource_filter/content/browser/subresource_filter_profile_context.h"
+#include "content/public/browser/permission_controller.h"
+#include "content/public/browser/permission_result.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_features.h"
+#include "url/origin.h"
 
 #if BUILDFLAG(FULL_SAFE_BROWSING)
 #include "chrome/browser/safe_browsing/chrome_password_protection_service.h"
@@ -151,14 +154,14 @@
 }
 #endif
 
-permissions::PermissionResult ChromePageInfoDelegate::GetPermissionStatus(
-    ContentSettingsType type,
-    const GURL& site_url) {
-  // TODO(raymes): Use GetPermissionStatus() to retrieve information
-  // about *all* permissions once it has default behaviour implemented for
-  // ContentSettingTypes that aren't permissions.
-  return PermissionManagerFactory::GetForProfile(GetProfile())
-      ->GetPermissionStatusForDisplayOnSettingsUI(type, site_url);
+permissions::PermissionResult ChromePageInfoDelegate::GetPermissionResult(
+    blink::PermissionType permission,
+    const url::Origin& origin) {
+  content::PermissionResult permission_result =
+      GetProfile()
+          ->GetPermissionController()
+          ->GetPermissionResultForOriginWithoutContext(permission, origin);
+  return permissions::PermissionUtil::ToPermissionResult(permission_result);
 }
 
 #if !BUILDFLAG(IS_ANDROID)
diff --git a/chrome/browser/ui/page_info/chrome_page_info_delegate.h b/chrome/browser/ui/page_info/chrome_page_info_delegate.h
index 5e719b7..349bf17 100644
--- a/chrome/browser/ui/page_info/chrome_page_info_delegate.h
+++ b/chrome/browser/ui/page_info/chrome_page_info_delegate.h
@@ -48,9 +48,9 @@
   void OnUserActionOnPasswordUi(safe_browsing::WarningAction action) override;
   std::u16string GetWarningDetailText() override;
 #endif
-  permissions::PermissionResult GetPermissionStatus(
-      ContentSettingsType type,
-      const GURL& site_url) override;
+  permissions::PermissionResult GetPermissionResult(
+      blink::PermissionType permission,
+      const url::Origin& origin) override;
 
 #if !BUILDFLAG(IS_ANDROID)
   bool CreateInfoBarDelegate() override;
diff --git a/chrome/browser/ui/page_info/chrome_page_info_ui_delegate.cc b/chrome/browser/ui/page_info/chrome_page_info_ui_delegate.cc
index 9cfc9ab7..53bc206 100644
--- a/chrome/browser/ui/page_info/chrome_page_info_ui_delegate.cc
+++ b/chrome/browser/ui/page_info/chrome_page_info_ui_delegate.cc
@@ -20,6 +20,8 @@
 #include "components/permissions/permissions_client.h"
 #include "components/prefs/pref_service.h"
 #include "components/strings/grit/components_strings.h"
+#include "content/public/browser/permission_controller.h"
+#include "content/public/browser/permission_result.h"
 #include "content/public/browser/web_contents.h"
 #include "net/base/url_util.h"
 #include "services/network/public/cpp/is_potentially_trustworthy.h"
@@ -206,10 +208,14 @@
 }
 #endif
 
-permissions::PermissionResult ChromePageInfoUiDelegate::GetPermissionStatus(
-    ContentSettingsType type) {
-  return PermissionManagerFactory::GetForProfile(GetProfile())
-      ->GetPermissionStatusForDisplayOnSettingsUI(type, site_url_);
+permissions::PermissionResult ChromePageInfoUiDelegate::GetPermissionResult(
+    blink::PermissionType permission) {
+  content::PermissionResult permission_result =
+      GetProfile()
+          ->GetPermissionController()
+          ->GetPermissionResultForOriginWithoutContext(
+              permission, url::Origin::Create(site_url_));
+  return permissions::PermissionUtil::ToPermissionResult(permission_result);
 }
 
 absl::optional<permissions::PermissionResult>
diff --git a/chrome/browser/ui/page_info/chrome_page_info_ui_delegate.h b/chrome/browser/ui/page_info/chrome_page_info_ui_delegate.h
index af8e733b..8d0c2ec3 100644
--- a/chrome/browser/ui/page_info/chrome_page_info_ui_delegate.h
+++ b/chrome/browser/ui/page_info/chrome_page_info_ui_delegate.h
@@ -68,8 +68,8 @@
   bool IsBlockAutoPlayEnabled() override;
   bool IsMultipleTabsOpen() override;
 #endif  // !BUILDFLAG(IS_ANDROID)
-  permissions::PermissionResult GetPermissionStatus(
-      ContentSettingsType type) override;
+  permissions::PermissionResult GetPermissionResult(
+      blink::PermissionType permission) override;
   absl::optional<permissions::PermissionResult> GetEmbargoResult(
       ContentSettingsType type) override;
 
diff --git a/chrome/browser/ui/views/accessibility/caption_bubble_controller_views_browsertest.cc b/chrome/browser/ui/views/accessibility/caption_bubble_controller_views_browsertest.cc
index 6afe1fc..efbc3c6e 100644
--- a/chrome/browser/ui/views/accessibility/caption_bubble_controller_views_browsertest.cc
+++ b/chrome/browser/ui/views/accessibility/caption_bubble_controller_views_browsertest.cc
@@ -9,6 +9,7 @@
 #include "base/test/scoped_mock_time_message_loop_task_runner.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/browser_tabstrip.h"
@@ -17,8 +18,10 @@
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/interactive_test_utils.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "components/live_caption/pref_names.h"
 #include "components/live_caption/views/caption_bubble.h"
 #include "components/live_caption/views/caption_bubble_controller_views.h"
+#include "components/prefs/pref_service.h"
 #include "content/public/browser/browser_accessibility_state.h"
 #include "content/public/test/browser_test.h"
 #include "media/mojo/mojom/speech_recognition_service.mojom.h"
@@ -50,7 +53,8 @@
 
   CaptionBubbleControllerViews* GetController() {
     if (!controller_)
-      controller_ = std::make_unique<CaptionBubbleControllerViews>();
+      controller_ = std::make_unique<CaptionBubbleControllerViews>(
+          browser()->profile()->GetPrefs());
     return controller_.get();
   }
 
@@ -101,6 +105,16 @@
                        : nullptr;
   }
 
+  views::Button* GetPinButton() {
+    return controller_ ? controller_->caption_bubble_->pin_button_.get()
+                       : nullptr;
+  }
+
+  views::Button* GetUnpinButton() {
+    return controller_ ? controller_->caption_bubble_->unpin_button_.get()
+                       : nullptr;
+  }
+
   views::View* GetErrorMessage() {
     return controller_
                ? controller_->caption_bubble_->generic_error_message_.get()
@@ -862,6 +876,8 @@
 
 IN_PROC_BROWSER_TEST_F(CaptionBubbleControllerViewsTest, ExpandsAndCollapses) {
   int line_height = 24;
+  EXPECT_FALSE(browser()->profile()->GetPrefs()->GetBoolean(
+      prefs::kLiveCaptionBubbleExpanded));
 
   OnPartialTranscription("Seahorses are monogamous");
   EXPECT_TRUE(GetExpandButton()->GetVisible());
@@ -872,6 +888,8 @@
   EXPECT_TRUE(GetCollapseButton()->GetVisible());
   EXPECT_FALSE(GetExpandButton()->GetVisible());
   EXPECT_EQ(7 * line_height, GetLabel()->GetBoundsInScreen().height());
+  EXPECT_TRUE(browser()->profile()->GetPrefs()->GetBoolean(
+      prefs::kLiveCaptionBubbleExpanded));
 
   // Switch media. The bubble should remain expanded.
   auto media_1 = CaptionBubbleContextBrowser::Create(
@@ -899,6 +917,29 @@
   EXPECT_EQ(line_height, GetLabel()->GetBoundsInScreen().height());
 }
 
+IN_PROC_BROWSER_TEST_F(CaptionBubbleControllerViewsTest, PinAndUnpin) {
+  base::ScopedMockTimeMessageLoopTaskRunner test_task_runner;
+  SetTickClockForTesting(test_task_runner->GetMockTickClock());
+  EXPECT_FALSE(browser()->profile()->GetPrefs()->GetBoolean(
+      prefs::kLiveCaptionBubblePinned));
+
+  OnPartialTranscription(
+      "Sea otters have the densest fur of any mammal at about 1 million hairs "
+      "per square inch.");
+  EXPECT_TRUE(GetPinButton()->GetVisible());
+  EXPECT_FALSE(GetUnpinButton()->GetVisible());
+
+  ClickButton(GetPinButton());
+  EXPECT_FALSE(GetPinButton()->GetVisible());
+  EXPECT_TRUE(GetUnpinButton()->GetVisible());
+  EXPECT_TRUE(browser()->profile()->GetPrefs()->GetBoolean(
+      prefs::kLiveCaptionBubblePinned));
+
+  ASSERT_TRUE(GetBubble()->GetInactivityTimerForTesting()->IsRunning());
+  test_task_runner->FastForwardBy(base::Seconds(15));
+  EXPECT_TRUE(IsWidgetVisible());
+}
+
 IN_PROC_BROWSER_TEST_F(CaptionBubbleControllerViewsTest, NonAsciiCharacter) {
   OnPartialTranscription("犬は最高です");
   EXPECT_EQ("犬は最高です", GetLabelText());
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 ada56943..cf7743c 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
@@ -500,43 +500,41 @@
 }
 
 void DownloadBubbleRowView::UpdateButtons() {
-  if (download::IsDownloadBubbleV2Enabled(browser_->profile())) {
-    resume_action_->SetVisible(false);
-    pause_action_->SetVisible(false);
-    open_when_complete_action_->SetVisible(false);
-    cancel_action_->SetVisible(false);
-    show_in_folder_action_->SetVisible(false);
-    open_when_complete_action_->SetVisible(false);
-    for (const auto& action : ui_info_.quick_actions) {
-      views::ImageButton* action_button =
-          GetActionButtonForCommand(action.command);
-      action_button->SetImageModel(views::Button::STATE_NORMAL,
-                                   ui::ImageModel::FromVectorIcon(
-                                       *(action.icon), ui::kColorIcon,
+  resume_action_->SetVisible(false);
+  pause_action_->SetVisible(false);
+  open_when_complete_action_->SetVisible(false);
+  cancel_action_->SetVisible(false);
+  show_in_folder_action_->SetVisible(false);
+  open_when_complete_action_->SetVisible(false);
+  for (const auto& action : ui_info_.quick_actions) {
+    views::ImageButton* action_button =
+        GetActionButtonForCommand(action.command);
+    action_button->SetImageModel(
+        views::Button::STATE_NORMAL,
+        ui::ImageModel::FromVectorIcon(*(action.icon), ui::kColorIcon,
                                        GetLayoutConstant(DOWNLOAD_ICON_SIZE)));
-      action_button->SetAccessibleName(action.hover_text);
-      action_button->SetTooltipText(action.hover_text);
-      action_button->SetVisible(true);
-    }
-  } else {
-    cancel_button_->SetVisible(ui_info_.primary_button_command ==
-                               DownloadCommands::CANCEL);
-    discard_button_->SetVisible(ui_info_.primary_button_command ==
-                                DownloadCommands::DISCARD);
-    keep_button_->SetVisible(ui_info_.primary_button_command ==
-                             DownloadCommands::KEEP);
-    scan_button_->SetVisible(ui_info_.primary_button_command ==
-                             DownloadCommands::DEEP_SCAN);
-    open_now_button_->SetVisible(ui_info_.primary_button_command ==
-                                 DownloadCommands::BYPASS_DEEP_SCANNING);
-    resume_button_->SetVisible(ui_info_.primary_button_command ==
-                               DownloadCommands::RESUME);
-    review_button_->SetVisible(ui_info_.primary_button_command ==
-                               DownloadCommands::REVIEW);
-    retry_button_->SetVisible(ui_info_.primary_button_command ==
-                              DownloadCommands::RETRY);
+    action_button->SetAccessibleName(action.hover_text);
+    action_button->SetTooltipText(action.hover_text);
+    action_button->SetVisible(true);
   }
 
+  cancel_button_->SetVisible(ui_info_.primary_button_command ==
+                             DownloadCommands::CANCEL);
+  discard_button_->SetVisible(ui_info_.primary_button_command ==
+                              DownloadCommands::DISCARD);
+  keep_button_->SetVisible(ui_info_.primary_button_command ==
+                           DownloadCommands::KEEP);
+  scan_button_->SetVisible(ui_info_.primary_button_command ==
+                           DownloadCommands::DEEP_SCAN);
+  open_now_button_->SetVisible(ui_info_.primary_button_command ==
+                               DownloadCommands::BYPASS_DEEP_SCANNING);
+  resume_button_->SetVisible(ui_info_.primary_button_command ==
+                             DownloadCommands::RESUME);
+  review_button_->SetVisible(ui_info_.primary_button_command ==
+                             DownloadCommands::REVIEW);
+  retry_button_->SetVisible(ui_info_.primary_button_command ==
+                            DownloadCommands::RETRY);
+
   subpage_icon_->SetVisible(ui_info_.has_subpage);
   subpage_icon_->SetBorder(views::CreateEmptyBorder(
       gfx::Insets(ui_info_.has_subpage ? kDownloadSubpageIconMargin : 0)));
diff --git a/chrome/browser/ui/views/frame/webui_tab_strip_interactive_uitest.cc b/chrome/browser/ui/views/frame/webui_tab_strip_interactive_uitest.cc
index 66aa6342..1bd4401 100644
--- a/chrome/browser/ui/views/frame/webui_tab_strip_interactive_uitest.cc
+++ b/chrome/browser/ui/views/frame/webui_tab_strip_interactive_uitest.cc
@@ -2,13 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "base/callback_forward.h"
 #include "base/strings/utf_string_conversions.h"
-#include "base/test/bind.h"
 #include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
-#include "chrome/browser/ui/browser_element_identifiers.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/view_ids.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
@@ -20,18 +17,8 @@
 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/interactive_test_utils.h"
-#include "chrome/test/interaction/interaction_test_util_browser.h"
-#include "chrome/test/interaction/webui_interaction_test_util.h"
 #include "content/public/test/browser_test.h"
-#include "ui/aura/client/drag_drop_client.h"
-#include "ui/base/dragdrop/drag_drop_types.h"
-#include "ui/base/interaction/element_tracker.h"
-#include "ui/base/interaction/expect_call_in_scope.h"
-#include "ui/base/interaction/interaction_sequence.h"
-#include "ui/base/page_transition_types.h"
 #include "ui/base/pointer/touch_ui_controller.h"
-#include "ui/base/test/ui_controls.h"
-#include "ui/gfx/geometry/point.h"
 #include "ui/views/controls/webview/webview.h"
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
@@ -201,258 +188,4 @@
   EXPECT_TRUE(immersive_mode_controller->IsRevealed());
 }
 
-// Regression test for crbug.com/1286203.
-//
-// The original bug was a UAF that happened when a tab closed itself (e.g. via
-// javascript) during a drag from the WebUI tabstrip; not all references to the
-// tab were properly cleaned up.
-//
-// There is already a proposed regression test for this bug using existing
-// technology; see:
-//   https://chromium-review.googlesource.com/c/chromium/src/+/3588859
-//
-// This is a proof-of-concept for regression testing using InteractionSequence,
-// which demonstrates that:
-//  - tests can be written without arbitrary (and often flaky) delays
-//  - tests can be end-to-end interacting with both native and WebUI code
-//  - tests can be written to reproduce very specific test cases
-//
-// This framework can be used to handle many similar types of bugs, for both
-// WebUI and Views elements. These tests, while more verbose, can be made very
-// specific and are declarative and event-driven. This particular test performs
-// the following steps:
-//  1. opens a second tab in the browser
-//  2. clicks the tab counter button to open the WebUI tabstrip
-//  3. drags the second tab out of the WebUI tabstrip
-//  4. without finishing the drag, closes the tab via script
-//  5. verifies the tab actually closed
-//  6. completes the drag
-//
-// This sequence of events would crash without the associated bugfix. More
-// detail is provided in the actual test sequence.
-// TODO(crbug.com/1352040): Fix consistent failures.
-#if BUILDFLAG(IS_LINUX) &&                                      \
-    (defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
-     defined(THREAD_SANITIZER))
-#define MAYBE_CloseTabDuringDrag DISABLED_CloseTabDuringDrag
-#else
-#define MAYBE_CloseTabDuringDrag CloseTabDuringDrag
-#endif
-IN_PROC_BROWSER_TEST_F(WebUITabStripInteractiveTest, MAYBE_CloseTabDuringDrag) {
-  // Add a second tab and set up an object to instrument that tab.
-  ASSERT_TRUE(AddTabAtIndex(-1, GURL("about:blank"), ui::PAGE_TRANSITION_LINK));
-  DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kSecondTabElementId);
-  std::unique_ptr<WebUIInteractionTestUtil> second_tab =
-      WebUIInteractionTestUtil::ForExistingTabInBrowser(browser(),
-                                                        kSecondTabElementId, 1);
-
-  // The WebUI for the tabstrip will be instrumented only after it is guaranteed
-  // to have been created.
-  DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kWebUiTabStripElementId);
-  std::unique_ptr<WebUIInteractionTestUtil> tab_strip;
-
-  // This is the DeepQuery path to the second tab element in the WebUI tabstrip.
-  // If the structure of the WebUI page changes greatly, it may need to be
-  // modified to reflect a new page structure.
-  const WebUIInteractionTestUtil::DeepQuery kSecondTabQuery{
-      "tabstrip-tab-list", "tabstrip-tab + tabstrip-tab"};
-
-  // Some custom events used to advance the test sequence.
-  DEFINE_LOCAL_CUSTOM_ELEMENT_EVENT_TYPE(kTabPopulatedCustomEvent);
-  DEFINE_LOCAL_CUSTOM_ELEMENT_EVENT_TYPE(kMouseDragCompleteCustomEvent);
-  DEFINE_LOCAL_CUSTOM_ELEMENT_EVENT_TYPE(kMouseUpCustomEvent);
-
-  // This is just a convenience function for sending custom events so this code
-  // doesn't have to be duplicated in a bunch of places.
-  auto send_custom_event = [&](ui::CustomElementEventType event_type) {
-    auto* const target =
-        ui::ElementTracker::GetElementTracker()->GetUniqueElement(
-            kWebUiTabStripElementId, browser()->window()->GetElementContext());
-    ASSERT_NE(nullptr, target);
-    ui::ElementTracker::GetFrameworkDelegate()->NotifyCustomEvent(target,
-                                                                  event_type);
-  };
-
-  // Performs a drag by sending mouse events.
-  //
-  // Moves the cursor to `start` and begins a drag to `end` in screen
-  // coordinates (but does not release the mouse button). When the mouse reaches
-  // `end`, an event is sent.
-  //
-  // This can probably be turned into a common utility method for testing things
-  // that happen in the middle of a drag.
-  auto perform_drag = [&](gfx::Point start, gfx::Point end) {
-    ASSERT_TRUE(ui_controls::SendMouseMoveNotifyWhenDone(
-        start.x(), start.y(), base::BindLambdaForTesting([&]() {
-          ASSERT_TRUE(ui_controls::SendMouseEventsNotifyWhenDone(
-              ui_controls::LEFT, ui_controls::DOWN,
-              base::BindLambdaForTesting([&]() {
-                ASSERT_TRUE(ui_controls::SendMouseMoveNotifyWhenDone(
-                    end.x(), end.y(), base::BindLambdaForTesting([&]() {
-                      send_custom_event(kMouseDragCompleteCustomEvent);
-                    })));
-              })));
-        })));
-  };
-
-  // These are needed to determine the sequence didn't fail. They're boilerplate
-  // and will probably be exchanged in the future for a smarter version of
-  // InteractionSequence::RunSynchronouslyForTesting().
-  UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::CompletedCallback, completed);
-  UNCALLED_MOCK_CALLBACK(ui::InteractionSequence::AbortedCallback, aborted);
-
-  // This object contains the sequence of expected stets in the test.
-  auto sequence =
-      ui::InteractionSequence::Builder()
-          .SetContext(browser()->window()->GetElementContext())
-          .SetCompletedCallback(completed.Get())
-          .SetAbortedCallback(aborted.Get())
-
-          // Wait until the second tab has fully loaded. This is advisable since
-          // later the destruction of the tab needs to be observed.
-          .AddStep(ui::InteractionSequence::StepBuilder()
-                       .SetType(ui::InteractionSequence::StepType::kShown)
-                       .SetElementID(kSecondTabElementId)
-                       .Build())
-
-          // Click the tab counter button to display the WebUI tabstrip and
-          // make sure the tabstrip appears.
-          .AddStep(
-              ui::InteractionSequence::StepBuilder()
-                  .SetType(ui::InteractionSequence::StepType::kShown)
-                  .SetElementID(kTabCounterButtonElementId)
-                  .SetStartCallback(base::BindLambdaForTesting(
-                      [&](ui::InteractionSequence*,
-                          ui::TrackedElement* element) {
-                        const auto test_util = CreateInteractionTestUtil();
-                        test_util->PressButton(element);
-
-                        // The WebUI tabstrip can be created dynamically, so
-                        // wait until the button is pressed and the browser is
-                        // re-laid-out to bind the associated WebUI.
-                        auto* const browser_view =
-                            BrowserView::GetBrowserViewForBrowser(browser());
-                        browser_view->GetWidget()->LayoutRootViewIfNecessary();
-                        auto* const web_view = browser_view->webui_tab_strip()
-                                                   ->web_view_for_testing();
-                        tab_strip = WebUIInteractionTestUtil::ForNonTabWebView(
-                            web_view, kWebUiTabStripElementId);
-                      }))
-                  .Build())
-
-          // Wait for the WebUI tabstrip to become fully loaded, and then wait
-          // for the tab data to load and render.
-          .AddStep(
-              ui::InteractionSequence::StepBuilder()
-                  .SetType(ui::InteractionSequence::StepType::kShown)
-                  .SetElementID(kWebUiTabStripElementId)
-                  .SetStartCallback(base::BindLambdaForTesting(
-                      [&](ui::InteractionSequence*,
-                          ui::TrackedElement* element) {
-                        // At this point the new tab has been fully loaded and
-                        // its onLoad() called.
-                        EXPECT_EQ(2, browser()->tab_strip_model()->count());
-
-                        // It takes a while for tab data to be filled out in the
-                        // tabstrip. Before it is fully loaded the tabs have
-                        // zero visible size, so wait until they are the
-                        // expected size.
-                        WebUIInteractionTestUtil::StateChange change;
-                        change.event = kTabPopulatedCustomEvent;
-                        change.where = kSecondTabQuery;
-                        change.type = WebUIInteractionTestUtil::StateChange::
-                            Type::kExistsAndConditionTrue;
-                        change.test_function =
-                            "el => (el.getBoundingClientRect().width > 0)";
-                        tab_strip->SendEventOnStateChange(std::move(change));
-                      }))
-                  .Build())
-
-          // Now that the tab is properly rendered, drag it out of the tabstrip.
-          .AddStep(ui::InteractionSequence::StepBuilder()
-                       .SetType(ui::InteractionSequence::StepType::kCustomEvent,
-                                kTabPopulatedCustomEvent)
-                       .SetElementID(kWebUiTabStripElementId)
-                       .SetStartCallback(base::BindLambdaForTesting(
-                           [&](ui::InteractionSequence*,
-                               ui::TrackedElement* element) {
-                             // Starting point of drag is the center of the
-                             // second tab in the WebUI tabstrip.
-                             const gfx::Point start =
-                                 tab_strip
-                                     ->GetElementBoundsInScreen(kSecondTabQuery)
-                                     .CenterPoint();
-
-                             // Endpoint is center of the main webcontents, so
-                             // guaranteed to be outside the tabstrip.
-                             const gfx::Point end = browser()
-                                                        ->tab_strip_model()
-                                                        ->GetActiveWebContents()
-                                                        ->GetContainerBounds()
-                                                        .CenterPoint();
-
-                             // Perform but do not complete the drag.
-                             perform_drag(start, end);
-                           }))
-                       .Build())
-
-          // Wait for the drag to finish and close the tab without releasing the
-          // mouse and actually ending the drag.
-          .AddStep(ui::InteractionSequence::StepBuilder()
-                       .SetType(ui::InteractionSequence::StepType::kCustomEvent,
-                                kMouseDragCompleteCustomEvent)
-                       .SetElementID(kWebUiTabStripElementId)
-                       .SetStartCallback(base::BindLambdaForTesting(
-                           [&](ui::InteractionSequence*,
-                               ui::TrackedElement* element) {
-                             // For WebUI tab drag, the tab isn't actually
-                             // removed from the tabstrip until the drag
-                             // completes.
-                             EXPECT_EQ(2,
-                                       browser()->tab_strip_model()->count());
-
-                             // Close the new tab.
-                             second_tab->Execute("() => window.close()");
-                           }))
-                       .Build())
-
-          // Wait for the dragged tab to be closed, verify it is closed, and
-          // release the mouse to finish the drag.
-          //
-          // SetTransitionOnlyOnEvent(true) means the test will fail if the tab
-          // goes away before this step is queued; it will only succeed if the
-          // tab disappears specifically in response to the previous step.
-          .AddStep(
-              ui::InteractionSequence::StepBuilder()
-                  .SetType(ui::InteractionSequence::StepType::kHidden)
-                  .SetElementID(kSecondTabElementId)
-                  .SetTransitionOnlyOnEvent(true)
-                  .SetStartCallback(base::BindLambdaForTesting(
-                      [&](ui::InteractionSequence*,
-                          ui::TrackedElement* element) {
-                        // The tab should now be removed from the tabstrip
-                        // because it was closed; the drag has not yet
-                        // finished.
-                        EXPECT_EQ(1, browser()->tab_strip_model()->count());
-
-                        // Complete the drag by releasing the mouse.
-                        EXPECT_TRUE(ui_controls::SendMouseEventsNotifyWhenDone(
-                            ui_controls::LEFT, ui_controls::UP,
-                            base::BindLambdaForTesting([&]() {
-                              send_custom_event(kMouseUpCustomEvent);
-                            })));
-                      }))
-                  .Build())
-
-          // Wait for the mouse-up action to complete before ending the sequence
-          // so the browser is in a known input state for the next test.
-          .AddStep(ui::InteractionSequence::StepBuilder()
-                       .SetType(ui::InteractionSequence::StepType::kCustomEvent,
-                                kMouseUpCustomEvent)
-                       .SetElementID(kWebUiTabStripElementId))
-          .Build();
-
-  EXPECT_CALL_IN_SCOPE(completed, Run, sequence->RunSynchronouslyForTesting());
-}
-
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/browser/ui/views/location_bar/cookie_controls_icon_view.cc b/chrome/browser/ui/views/location_bar/cookie_controls_icon_view.cc
index 5428a7a..810b83e 100644
--- a/chrome/browser/ui/views/location_bar/cookie_controls_icon_view.cc
+++ b/chrome/browser/ui/views/location_bar/cookie_controls_icon_view.cc
@@ -15,6 +15,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "components/content_settings/browser/ui/cookie_controls_controller.h"
 #include "content/public/browser/browser_context.h"
+#include "content/public/browser/web_contents.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/gfx/geometry/size.h"
diff --git a/chrome/browser/ui/views/safe_browsing/OWNERS b/chrome/browser/ui/views/safe_browsing/OWNERS
index 70e4b68..5225674 100644
--- a/chrome/browser/ui/views/safe_browsing/OWNERS
+++ b/chrome/browser/ui/views/safe_browsing/OWNERS
@@ -1,3 +1 @@
-drubery@chromium.org
-nparker@chromium.org
-vakh@chromium.org
+file://components/safe_browsing/OWNERS
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_constants.h b/chrome/browser/ui/views/side_panel/read_anything/read_anything_constants.h
index 8da26fc3..d7425f1d 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_constants.h
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_constants.h
@@ -5,6 +5,8 @@
 #ifndef CHROME_BROWSER_UI_VIEWS_SIDE_PANEL_READ_ANYTHING_READ_ANYTHING_CONSTANTS_H_
 #define CHROME_BROWSER_UI_VIEWS_SIDE_PANEL_READ_ANYTHING_READ_ANYTHING_CONSTANTS_H_
 
+#include "third_party/skia/include/core/SkColor.h"
+
 // Various constants used throughout the Read Anything feature.
 namespace {
 
@@ -15,6 +17,7 @@
 const int kButtonPadding = 8;
 const int kSmallIconSize = 18;
 const int kLargeIconSize = 20;
+const int kColorsIconSize = 24;
 
 const char kReadAnythingDefaultFontName[] = "Standard font";
 
@@ -26,6 +29,11 @@
 const double kReadAnythingMinimumFontScale = 0.2;
 const double kReadAnythingMaximumFontScale = 5.0;
 
+// Custom feature colors.
+constexpr SkColor kReadAnythingDarkBackground = SkColorSetRGB(0x33, 0x36, 0x39);
+constexpr SkColor kReadAnythingYellowForeground =
+    SkColorSetRGB(0x4E, 0x3F, 0x6C);
+
 }  // namespace
 
 #endif  // CHROME_BROWSER_UI_VIEWS_SIDE_PANEL_READ_ANYTHING_READ_ANYTHING_CONSTANTS_H_
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_controller.cc b/chrome/browser/ui/views/side_panel/read_anything/read_anything_controller.cc
index d71e4ca..b19df309 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_controller.cc
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_controller.cc
@@ -29,6 +29,10 @@
   DistillAXTree();
 }
 
+///////////////////////////////////////////////////////////////////////////////
+// ReadAnythingFontCombobox::Delegate:
+///////////////////////////////////////////////////////////////////////////////
+
 void ReadAnythingController::OnFontChoiceChanged(int new_choice) {
   model_->SetSelectedFontByIndex(new_choice);
 
@@ -38,9 +42,13 @@
 }
 
 ui::ComboboxModel* ReadAnythingController::GetFontComboboxModel() {
-  return static_cast<ui::ComboboxModel*>(model_->GetFontModel());
+  return model_->GetFontModel();
 }
 
+///////////////////////////////////////////////////////////////////////////////
+// ReadAnythingToolbarView::Delegate:
+///////////////////////////////////////////////////////////////////////////////
+
 void ReadAnythingController::OnFontSizeChanged(bool increase) {
   if (increase) {
     model_->IncreaseTextSize();
@@ -52,6 +60,20 @@
       prefs::kAccessibilityReadAnythingFontScale, model_->GetFontScale());
 }
 
+void ReadAnythingController::OnColorsChanged(int new_index) {
+  model_->SetSelectedColorsByIndex(new_index);
+
+  // TODO(crbug.com/1266555): Save color choice to prefs here.
+}
+
+ui::ComboboxModel* ReadAnythingController::GetColorsModel() {
+  return model_->GetColorsModel();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// ReadAnythingPageHandler::Delegate:
+///////////////////////////////////////////////////////////////////////////////
+
 void ReadAnythingController::OnUIReady() {
   ui_ready_ = true;
   DistillAXTree();
@@ -61,6 +83,10 @@
   ui_ready_ = false;
 }
 
+///////////////////////////////////////////////////////////////////////////////
+// TabStripModelObserver:
+///////////////////////////////////////////////////////////////////////////////
+
 void ReadAnythingController::OnTabStripModelChanged(
     TabStripModel* tab_strip_model,
     const TabStripModelChange& change,
@@ -79,6 +105,10 @@
   browser_ = nullptr;
 }
 
+///////////////////////////////////////////////////////////////////////////////
+// content::WebContentsObserver:
+///////////////////////////////////////////////////////////////////////////////
+
 void ReadAnythingController::DidStopLoading() {
   DistillAXTree();
 }
@@ -87,6 +117,8 @@
   active_contents_ = nullptr;
 }
 
+///////////////////////////////////////////////////////////////////////////////
+
 void ReadAnythingController::DistillAXTree() {
   DCHECK(browser_);
   if (!active_ || !ui_ready_)
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_controller.h b/chrome/browser/ui/views/side_panel/read_anything/read_anything_controller.h
index 99392ff..9585e0b5 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_controller.h
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_controller.h
@@ -52,13 +52,15 @@
  private:
   friend class ReadAnythingControllerTest;
 
-  // ReadAnythingToolbarView::Delegate:
-  void OnFontSizeChanged(bool increase) override;
-
   // ReadAnythingFontCombobox::Delegate:
   void OnFontChoiceChanged(int new_choice) override;
   ui::ComboboxModel* GetFontComboboxModel() override;
 
+  // ReadAnythingToolbarView::Delegate:
+  void OnFontSizeChanged(bool increase) override;
+  void OnColorsChanged(int new_index) override;
+  ui::ComboboxModel* GetColorsModel() override;
+
   // ReadAnythingPageHandler::Delegate:
   void OnUIReady() override;
   void OnUIDestroyed() override;
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator.cc b/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator.cc
index e4cc294..54fd25f 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator.cc
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator.cc
@@ -43,6 +43,8 @@
   prefs_font_scale = browser->profile()->GetPrefs()->GetDouble(
       prefs::kAccessibilityReadAnythingFontScale);
 
+  // TODO(crbug.com/1266555): Add initial color values fetched from Prefs.
+
   model_->Init(
       /* font name = */ prefs_font_name,
       /* font scale = */ prefs_font_scale);
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_model.cc b/chrome/browser/ui/views/side_panel/read_anything/read_anything_model.cc
index f0d6533..d1317d4 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_model.cc
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_model.cc
@@ -8,17 +8,24 @@
 #include <utility>
 
 #include "base/check.h"
-#include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/ui/views/side_panel/read_anything/read_anything_constants.h"
+#include "chrome/grit/component_extension_resources.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/base/models/image_model.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/color_palette.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/image/image_skia_operations.h"
 
 using read_anything::mojom::ReadAnythingTheme;
 
 ReadAnythingModel::ReadAnythingModel()
     : font_name_(kReadAnythingDefaultFontName),
       font_scale_(kReadAnythingDefaultFontScale),
-      font_model_(std::make_unique<ReadAnythingFontModel>()) {}
+      font_model_(std::make_unique<ReadAnythingFontModel>()),
+      colors_model_(std::make_unique<ReadAnythingColorsModel>()) {}
 
 ReadAnythingModel::~ReadAnythingModel() = default;
 
@@ -31,6 +38,8 @@
   }
 
   font_scale_ = font_scale;
+  foreground_color_ = SkColors::kBlack.toSkColor();
+  background_color_ = SkColors::kWhite.toSkColor();
 }
 
 void ReadAnythingModel::AddObserver(Observer* obs) {
@@ -52,6 +61,17 @@
   NotifyThemeChanged();
 }
 
+void ReadAnythingModel::SetSelectedColorsByIndex(size_t new_index) {
+  // Check that the index is valid.
+  DCHECK(colors_model_->IsValidColorsIndex(new_index));
+
+  auto& new_colors = colors_model_->GetColorsAt(new_index);
+  foreground_color_ = new_colors.foreground;
+  background_color_ = new_colors.background;
+
+  NotifyThemeChanged();
+}
+
 void ReadAnythingModel::SetDistilledAXTree(
     ui::AXTreeUpdate snapshot,
     std::vector<ui::AXNodeID> content_node_ids) {
@@ -87,10 +107,15 @@
 void ReadAnythingModel::NotifyThemeChanged() {
   for (Observer& obs : observers_) {
     obs.OnThemeChanged(ReadAnythingTheme::New(
-        font_name_, kReadAnythingDefaultFontSize * font_scale_));
+        font_name_, kReadAnythingDefaultFontSize * font_scale_,
+        foreground_color_, background_color_));
   }
 }
 
+///////////////////////////////////////////////////////////////////////////////
+// ReadAnythingFontModel
+///////////////////////////////////////////////////////////////////////////////
+
 ReadAnythingFontModel::ReadAnythingFontModel() {
   font_choices_.emplace_back(u"Standard font");
   font_choices_.emplace_back(u"Sans-serif");
@@ -155,3 +180,70 @@
 }
 
 ReadAnythingFontModel::~ReadAnythingFontModel() = default;
+
+///////////////////////////////////////////////////////////////////////////////
+// ReadAnythingColorsModel
+///////////////////////////////////////////////////////////////////////////////
+
+ReadAnythingColorsModel::ReadAnythingColorsModel() {
+  // Define the possible sets of colors available to the user.
+  // TODO (crbug.com/1266555): Define default colors from system theme.
+  ColorInfo kDefaultColors = {u"Default", IDS_READ_ANYTHING_DEFAULT_PNG,
+                              SkColors::kBlack.toSkColor(),
+                              SkColors::kWhite.toSkColor()};
+
+  ColorInfo kLightColors = {u"Light", IDS_READ_ANYTHING_LIGHT_PNG,
+                            gfx::kGoogleGrey900, gfx::kGoogleGrey050};
+
+  ColorInfo kDarkColors = {u"Dark", IDS_READ_ANYTHING_DARK_PNG,
+                           gfx::kGoogleGrey200, kReadAnythingDarkBackground};
+
+  ColorInfo kYellowColors = {u"Yellow", IDS_READ_ANYTHING_YELLOW_PNG,
+                             kReadAnythingYellowForeground,
+                             gfx::kGoogleYellow200};
+
+  colors_choices_.emplace_back(kDefaultColors);
+  colors_choices_.emplace_back(kLightColors);
+  colors_choices_.emplace_back(kDarkColors);
+  colors_choices_.emplace_back(kYellowColors);
+  colors_choices_.shrink_to_fit();
+}
+bool ReadAnythingColorsModel::IsValidColorsIndex(size_t index) {
+  return index < GetItemCount();
+}
+
+ReadAnythingColorsModel::ColorInfo& ReadAnythingColorsModel::GetColorsAt(
+    size_t index) {
+  return colors_choices_[index];
+}
+
+absl::optional<size_t> ReadAnythingColorsModel::GetDefaultIndex() const {
+  return default_index_;
+}
+
+size_t ReadAnythingColorsModel::GetItemCount() const {
+  return colors_choices_.size();
+}
+
+ui::ImageModel ReadAnythingColorsModel::GetIconAt(size_t index) const {
+  const gfx::ImageSkia* icon_skia_asset =
+      ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
+          colors_choices_[index].icon_asset);
+  DCHECK(icon_skia_asset);
+
+  return ui::ImageModel::FromImageSkia(
+      gfx::ImageSkiaOperations::CreateResizedImage(
+          *icon_skia_asset, skia::ImageOperations::ResizeMethod::RESIZE_GOOD,
+          gfx::Size(kColorsIconSize, kColorsIconSize)));
+}
+
+std::u16string ReadAnythingColorsModel::GetItemAt(size_t index) const {
+  // Only display the icon choice in the toolbar, so suppress name here.
+  return u"";
+}
+
+std::u16string ReadAnythingColorsModel::GetDropDownTextAt(size_t index) const {
+  return colors_choices_[index].name;
+}
+
+ReadAnythingColorsModel::~ReadAnythingColorsModel() = default;
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_model.h b/chrome/browser/ui/views/side_panel/read_anything/read_anything_model.h
index add6e27..97b2696 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_model.h
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_model.h
@@ -12,6 +12,7 @@
 #include "base/observer_list.h"
 #include "base/observer_list_types.h"
 #include "chrome/common/accessibility/read_anything.mojom.h"
+#include "third_party/skia/include/core/SkColor.h"
 #include "ui/accessibility/ax_node_id_forward.h"
 #include "ui/accessibility/ax_tree_update.h"
 #include "ui/base/models/combobox_model.h"
@@ -52,6 +53,54 @@
 };
 
 ///////////////////////////////////////////////////////////////////////////////
+// ReadAnythingColorsModel
+//
+//  A class that stores the data for the colors combobox.
+//  This class is owned by the ReadAnythingModel and has the same lifetime as
+//  the browser.
+//
+class ReadAnythingColorsModel : public ui::ComboboxModel {
+ public:
+  ReadAnythingColorsModel();
+  ReadAnythingColorsModel(const ReadAnythingColorsModel&) = delete;
+  ReadAnythingColorsModel& operator=(const ReadAnythingColorsModel&) = delete;
+  ~ReadAnythingColorsModel() override;
+
+  // Simple struct to hold the various colors to keep code cleaner.
+  struct ColorInfo {
+    // The name of the colors, e.g. Default, Light, Dark.
+    std::u16string name;
+
+    // The resources value/identifier for the icon image asset.
+    int icon_asset;
+
+    // The foreground color, used for text and icon hints.
+    SkColor foreground;
+
+    // The background color, used for text background.
+    SkColor background;
+  };
+
+  bool IsValidColorsIndex(size_t index);
+  ColorInfo& GetColorsAt(size_t index);
+
+ protected:
+  // ui::Combobox implementation:
+  absl::optional<size_t> GetDefaultIndex() const override;
+  size_t GetItemCount() const override;
+  ui::ImageModel GetIconAt(size_t index) const override;
+  std::u16string GetItemAt(size_t index) const override;
+  std::u16string GetDropDownTextAt(size_t index) const override;
+
+ private:
+  // Individual combobox choices for colors presented in front-end.
+  std::vector<ColorInfo> colors_choices_;
+
+  // Default index for drop down, either zero or populated from prefs.
+  size_t default_index_ = 0;
+};
+
+///////////////////////////////////////////////////////////////////////////////
 // ReadAnythingModel
 //
 //  A class that stores data for the Read Anything feature.
@@ -85,9 +134,11 @@
   void SetSelectedFontByIndex(size_t new_index);
   void DecreaseTextSize();
   void IncreaseTextSize();
+  void SetSelectedColorsByIndex(size_t new_index);
 
   ReadAnythingFontModel* GetFontModel() { return font_model_.get(); }
   double GetFontScale() { return font_scale_; }
+  ReadAnythingColorsModel* GetColorsModel() { return colors_model_.get(); }
 
  private:
   void NotifyAXTreeDistilled();
@@ -97,6 +148,8 @@
 
   // Members of read_anything::mojom::ReadAnythingTheme:
   std::string font_name_;
+  SkColor foreground_color_;
+  SkColor background_color_;
 
   // A scale multiplier for font size (internal use only, not shown to user).
   float font_scale_;
@@ -108,6 +161,7 @@
 
   base::ObserverList<Observer> observers_;
   const std::unique_ptr<ReadAnythingFontModel> font_model_;
+  const std::unique_ptr<ReadAnythingColorsModel> colors_model_;
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_SIDE_PANEL_READ_ANYTHING_READ_ANYTHING_MODEL_H_
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_model_unittest.cc b/chrome/browser/ui/views/side_panel/read_anything/read_anything_model_unittest.cc
index d3de682..c8f85ec2 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_model_unittest.cc
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_model_unittest.cc
@@ -126,6 +126,14 @@
   EXPECT_NEAR(model_->GetFontScale(), 1.2, 0.01);
 }
 
+TEST_F(ReadAnythingModelTest, NotificationsOnSetSelectedColorsIndex) {
+  model_->AddObserver(&model_observer_1_);
+
+  EXPECT_CALL(model_observer_1_, OnThemeChanged(_)).Times(1);
+
+  model_->SetSelectedColorsByIndex(2);
+}
+
 TEST_F(ReadAnythingModelTest, MinimumFontScaleIsEnforced) {
   std::string font_name;
   model_->Init(font_name, 0.3);
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_toolbar_view.cc b/chrome/browser/ui/views/side_panel/read_anything/read_anything_toolbar_view.cc
index 3638e98d..250d702c 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_toolbar_view.cc
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_toolbar_view.cc
@@ -20,6 +20,7 @@
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/views/background.h"
 #include "ui/views/controls/button/image_button_factory.h"
+#include "ui/views/controls/combobox/combobox.h"
 #include "ui/views/controls/highlight_path_generator.h"
 #include "ui/views/controls/separator.h"
 #include "ui/views/layout/box_layout.h"
@@ -42,7 +43,8 @@
 
   SetLayoutManager(std::move(layout));
 
-  // Create a font selection combobox for the toolbar.
+  // Create a font selection combobox for the toolbar. The font combobox uses
+  // a custom MenuModel, so we have a separate View for it for convenience.
   auto combobox =
       std::make_unique<ReadAnythingFontCombobox>(font_combobox_delegate);
 
@@ -63,12 +65,23 @@
       l10n_util::GetStringUTF16(
           IDS_READ_ANYTHING_INCREASE_FONT_SIZE_BUTTON_LABEL));
 
+  // Create theme selection combobox.
+  auto colors_combobox = std::make_unique<views::Combobox>();
+  colors_combobox->SetModel(delegate_->GetColorsModel());
+  colors_combobox->SetTooltipTextAndAccessibleName(
+      l10n_util::GetStringUTF16(IDS_READ_ANYTHING_COLORS_COMBOBOX_LABEL));
+  colors_combobox->SetSizeToLargestLabel(true);
+  colors_combobox->SetCallback(
+      base::BindRepeating(&ReadAnythingToolbarView::ChangeColorsCallback,
+                          weak_pointer_factory_.GetWeakPtr()));
+
   // Add all views as children.
   font_combobox_ = AddChildView(std::move(combobox));
   AddChildView(Separator());
   decrease_text_size_button_ = AddChildView(std::move(decrease_size_button));
   increase_text_size_button_ = AddChildView(std::move(increase_size_button));
   AddChildView(Separator());
+  colors_combobox_ = AddChildView(std::move(colors_combobox));
 }
 
 void ReadAnythingToolbarView::DecreaseFontSizeCallback() {
@@ -81,6 +94,12 @@
     delegate_->OnFontSizeChanged(/* increase = */ true);
 }
 
+void ReadAnythingToolbarView::ChangeColorsCallback() {
+  if (delegate_)
+    delegate_->OnColorsChanged(
+        colors_combobox_->GetSelectedIndex().value_or(0));
+}
+
 void ReadAnythingToolbarView::OnCoordinatorDestroyed() {
   // When the coordinator that created |this| is destroyed, clean up pointers.
   coordinator_ = nullptr;
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_toolbar_view.h b/chrome/browser/ui/views/side_panel/read_anything/read_anything_toolbar_view.h
index 1238329..c01f769 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_toolbar_view.h
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_toolbar_view.h
@@ -27,6 +27,8 @@
   class Delegate {
    public:
     virtual void OnFontSizeChanged(bool increase) = 0;
+    virtual void OnColorsChanged(int new_index) = 0;
+    virtual ui::ComboboxModel* GetColorsModel() = 0;
   };
 
   ReadAnythingToolbarView(
@@ -45,6 +47,7 @@
 
   void DecreaseFontSizeCallback();
   void IncreaseFontSizeCallback();
+  void ChangeColorsCallback();
 
   // views::View:
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
@@ -54,6 +57,8 @@
   raw_ptr<views::Combobox> font_combobox_;
   raw_ptr<ReadAnythingButtonView> decrease_text_size_button_;
   raw_ptr<ReadAnythingButtonView> increase_text_size_button_;
+  raw_ptr<views::Combobox> colors_combobox_;
+
   raw_ptr<ReadAnythingToolbarView::Delegate> delegate_;
   raw_ptr<ReadAnythingCoordinator> coordinator_;
 
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_toolbar_view_browsertest.cc b/chrome/browser/ui/views/side_panel/read_anything/read_anything_toolbar_view_browsertest.cc
index a7ce10d..22e4bf4 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_toolbar_view_browsertest.cc
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_toolbar_view_browsertest.cc
@@ -17,6 +17,8 @@
     : public ReadAnythingToolbarView::Delegate {
  public:
   MOCK_METHOD(void, OnFontSizeChanged, (bool increase), (override));
+  MOCK_METHOD(void, OnColorsChanged, (int new_index), (override));
+  MOCK_METHOD(ui::ComboboxModel*, GetColorsModel, (), (override));
 };
 
 class MockReadAnythingFontComboboxDelegate
@@ -65,6 +67,8 @@
 
   void IncreaseFontSizeCallback() { toolbar_view_->IncreaseFontSizeCallback(); }
 
+  void ChangeColorsCallback() { toolbar_view_->ChangeColorsCallback(); }
+
  protected:
   MockReadAnythingToolbarViewDelegate toolbar_delegate_;
   MockReadAnythingFontComboboxDelegate font_combobox_delegate_;
@@ -87,3 +91,9 @@
 
   IncreaseFontSizeCallback();
 }
+
+IN_PROC_BROWSER_TEST_F(ReadAnythingToolbarViewTest, ChangeColorsCallback) {
+  EXPECT_CALL(toolbar_delegate_, OnColorsChanged(0)).Times(1);
+
+  ChangeColorsCallback();
+}
diff --git a/chrome/browser/ui/views/translate/partial_translate_bubble_view.cc b/chrome/browser/ui/views/translate/partial_translate_bubble_view.cc
index 7138187..f8390d8 100644
--- a/chrome/browser/ui/views/translate/partial_translate_bubble_view.cc
+++ b/chrome/browser/ui/views/translate/partial_translate_bubble_view.cc
@@ -433,6 +433,11 @@
     tabbed_pane_->GetTabAt(1)->SetTitleText(target_language_name);
     model_->Translate();
     tabbed_pane_->SelectTabAt(1);
+
+    // Update max width of text selection label to match width of bubble, which
+    // changes with the lengths of the languages displayed in the tabbed pane.
+    partial_text_label_->SizeToFit(
+        tab_view_top_row_->GetPreferredSize().width());
     SwitchView(PartialTranslateBubbleModel::VIEW_STATE_AFTER_TRANSLATE);
     if (from_source_language_view) {
       translate::ReportPartialTranslateBubbleUiAction(
@@ -495,54 +500,18 @@
   auto inner_view = std::make_unique<views::View>();
   inner_view->SetLayoutManager(std::make_unique<views::FlexLayout>())
       ->SetOrientation(views::LayoutOrientation::kHorizontal);
-  auto* horizontal_view = view->AddChildView(std::move(inner_view));
-
-  auto partial_text_row = std::make_unique<views::View>();
-  partial_text_row->SetLayoutManager(std::make_unique<views::FlexLayout>());
-  auto partial_text_label = std::make_unique<views::Label>(
-      text_selection_, views::style::CONTEXT_DIALOG_BODY_TEXT,
-      views::style::STYLE_PRIMARY);
-  const int vertical_spacing =
-      provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_VERTICAL);
-  partial_text_label->SetLineHeight(vertical_spacing * 5);
-  partial_text_label->SetHorizontalAlignment(
-      gfx::HorizontalAlignment::ALIGN_LEFT);
-  partial_text_label->SetProperty(
-      views::kFlexBehaviorKey,
-      views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
-                               views::MaximumFlexSizeRule::kUnbounded));
-  partial_text_label->SetProperty(views::kMarginsKey,
-                                  gfx::Insets::TLBR(0, 0, vertical_spacing, 0));
-  partial_text_row->AddChildView(std::move(partial_text_label));
-  view->AddChildView(std::move(partial_text_row));
-
-  // Button to trigger full page translation.
-  auto button_row = std::make_unique<views::BoxLayoutView>();
-  button_row->SetMainAxisAlignment(views::BoxLayout::MainAxisAlignment::kEnd);
-  auto full_page_button = std::make_unique<views::MdTextButton>(
-      base::BindRepeating(&PartialTranslateBubbleView::TranslateFullPage,
-                          base::Unretained(this)),
-      l10n_util::GetStringUTF16(
-          IDS_PARTIAL_TRANSLATE_BUBBLE_TRANSLATE_FULL_PAGE));
-  full_page_button->SetID(BUTTON_ID_FULL_PAGE_TRANSLATE);
-  button_row->AddChildView(std::move(full_page_button));
-  button_row->SetProperty(
-      views::kMarginsKey,
-      gfx::Insets::TLBR(0, 0, 0,
-                        provider->GetDistanceMetric(
-                            views::DISTANCE_RELATED_CONTROL_HORIZONTAL)));
-  view->AddChildView(std::move(button_row));
+  tab_view_top_row_ = view->AddChildView(std::move(inner_view));
 
   views::View* icon = nullptr;
   if (!UseGoogleTranslateBranding()) {
-    icon = horizontal_view->AddChildView(CreateTranslateIcon());
+    icon = tab_view_top_row_->AddChildView(CreateTranslateIcon());
   }
 
   // Tabbed pane for language selection. Can't use unique_ptr because
   // tabs have to be added after the tabbed_pane is added to the parent,
   // when we release ownership of the unique_ptr.
   auto tabbed_pane = std::make_unique<views::TabbedPane>();
-  tabbed_pane_ = horizontal_view->AddChildView(std::move(tabbed_pane));
+  tabbed_pane_ = tab_view_top_row_->AddChildView(std::move(tabbed_pane));
 
   // NOTE: Panes must be added after |tabbed_pane| has been added to its
   // parent.
@@ -559,9 +528,10 @@
   tabbed_pane_->set_listener(this);
 
   auto* padding_view =
-      horizontal_view->AddChildView(std::make_unique<views::View>());
-  auto* options_menu = horizontal_view->AddChildView(CreateOptionsMenuButton());
-  horizontal_view->AddChildView(CreateCloseButton());
+      tab_view_top_row_->AddChildView(std::make_unique<views::View>());
+  auto* options_menu =
+      tab_view_top_row_->AddChildView(CreateOptionsMenuButton());
+  tab_view_top_row_->AddChildView(CreateCloseButton());
 
   if (icon) {
     icon->SetProperty(
@@ -585,6 +555,39 @@
       gfx::Insets::VH(0, provider->GetDistanceMetric(
                              views::DISTANCE_RELATED_BUTTON_HORIZONTAL)));
 
+  // Text selection.
+  auto partial_text_label = std::make_unique<views::Label>(
+      text_selection_, views::style::CONTEXT_DIALOG_BODY_TEXT,
+      views::style::STYLE_PRIMARY);
+  partial_text_label->SetMultiLine(true);
+  partial_text_label->SizeToFit(tab_view_top_row_->GetPreferredSize().width());
+
+  const int vertical_spacing =
+      provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_VERTICAL);
+  const int horizontal_spacing =
+      provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_HORIZONTAL);
+  partial_text_label->SetHorizontalAlignment(
+      gfx::HorizontalAlignment::ALIGN_LEFT);
+  partial_text_label->SetProperty(
+      views::kMarginsKey,
+      gfx::Insets::TLBR(vertical_spacing, 0, vertical_spacing,
+                        horizontal_spacing));
+  partial_text_label_ = view->AddChildView(std::move(partial_text_label));
+
+  // Button to trigger full page translation.
+  auto button_row = std::make_unique<views::BoxLayoutView>();
+  button_row->SetMainAxisAlignment(views::BoxLayout::MainAxisAlignment::kEnd);
+  auto full_page_button = std::make_unique<views::MdTextButton>(
+      base::BindRepeating(&PartialTranslateBubbleView::TranslateFullPage,
+                          base::Unretained(this)),
+      l10n_util::GetStringUTF16(
+          IDS_PARTIAL_TRANSLATE_BUBBLE_TRANSLATE_FULL_PAGE));
+  full_page_button->SetID(BUTTON_ID_FULL_PAGE_TRANSLATE);
+  button_row->AddChildView(std::move(full_page_button));
+  button_row->SetProperty(views::kMarginsKey,
+                          gfx::Insets::TLBR(0, 0, 0, horizontal_spacing));
+  view->AddChildView(std::move(button_row));
+
   return view;
 }
 
@@ -774,16 +777,18 @@
     std::unique_ptr<views::Button> advanced_reset_button,
     std::unique_ptr<views::Button> advanced_done_button) {
   const ChromeLayoutProvider* provider = ChromeLayoutProvider::Get();
-  auto view = std::make_unique<AdvancedViewContainer>();
-  auto* layout = view->SetLayoutManager(std::make_unique<views::BoxLayout>());
-  layout->set_between_child_spacing(
-      provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_HORIZONTAL));
-
-  std::unique_ptr<views::ImageView> language_icon = CreateTranslateIcon();
   const int vertical_spacing =
       provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_VERTICAL);
+  const int horizontal_spacing =
+      provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_HORIZONTAL);
+
+  auto view = std::make_unique<AdvancedViewContainer>();
+  auto* layout = view->SetLayoutManager(std::make_unique<views::BoxLayout>());
+  layout->set_between_child_spacing(horizontal_spacing);
+
+  std::unique_ptr<views::ImageView> language_icon = CreateTranslateIcon();
   if (!UseGoogleTranslateBranding()) {
-    // If the bottom branding isn't showing, display the leading translate
+    // If the bottom branding isn't showing, display the leading Translate
     // icon otherwise it's not obvious what the bubble is about. This should
     // only happen on non-Chrome-branded builds.
     auto* icon_view =
@@ -794,6 +799,10 @@
                            gfx::Insets::VH(vertical_spacing, 0));
   }
   auto* form_view = view->AddChildView(std::make_unique<views::View>());
+  // Stretch |form_view| to fit the rest of bubble's width. Note that because no
+  // other view has flex set, the flex argument here can be any positive
+  // integer.
+  layout->SetFlexForView(form_view, 1);
   form_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
       views::BoxLayout::Orientation::kVertical, gfx::Insets(),
       vertical_spacing));
@@ -801,37 +810,43 @@
   language_title_label->SetProperty(
       views::kMarginsKey,
       gfx::Insets::TLBR(vertical_spacing, 0, vertical_spacing,
-                        provider->GetDistanceMetric(
-                            views::DISTANCE_RELATED_CONTROL_HORIZONTAL) *
-                            4));
+                        horizontal_spacing * 4));
   language_title_label->SetProperty(views::kCrossAxisAlignmentKey,
                                     views::LayoutAlignment::kStart);
   auto* title_row = form_view->AddChildView(std::make_unique<views::View>());
   title_row->SetLayoutManager(std::make_unique<views::FlexLayout>());
-  title_row->AddChildView(std::move(language_title_label));
+  auto* title_label = title_row->AddChildView(std::move(language_title_label));
+  auto* padding_view = title_row->AddChildView(std::make_unique<views::View>());
   title_row->AddChildView(CreateCloseButton())
       ->SetProperty(views::kCrossAxisAlignmentKey,
                     views::LayoutAlignment::kStart);
+  // Set flex specifications for |title_row| views.
+  title_label->SetProperty(
+      views::kFlexBehaviorKey,
+      views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToMinimum,
+                               views::MaximumFlexSizeRule::kPreferred));
+  // |padding_view| is unbounded so that the close button stays right aligned
+  // when the bubble expands.
+  padding_view->SetProperty(
+      views::kFlexBehaviorKey,
+      views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
+                               views::MaximumFlexSizeRule::kUnbounded)
+          .WithOrder(2));
 
   form_view->AddChildView(std::move(combobox))
-      ->SetProperty(
-          views::kMarginsKey,
-          gfx::Insets::TLBR(0, 0, 0,
-                            provider->GetDistanceMetric(
-                                views::DISTANCE_RELATED_CONTROL_HORIZONTAL)));
+      ->SetProperty(views::kMarginsKey,
+                    gfx::Insets::TLBR(0, 0, 0, horizontal_spacing));
 
   auto button_row = std::make_unique<views::BoxLayoutView>();
   button_row->SetProperty(views::kMarginsKey,
                           gfx::Insets::TLBR(2 * vertical_spacing, 0, 0, 0));
 
+  button_row->SetProperty(
+      views::kMarginsKey,
+      gfx::Insets::TLBR(2 * vertical_spacing, 0, 0, horizontal_spacing));
   button_row->SetMainAxisAlignment(views::BoxLayout::MainAxisAlignment::kEnd);
   button_row->SetBetweenChildSpacing(
       provider->GetDistanceMetric(views::DISTANCE_RELATED_BUTTON_HORIZONTAL));
-  button_row->SetProperty(
-      views::kMarginsKey,
-      gfx::Insets::TLBR(0, 0, 0,
-                        provider->GetDistanceMetric(
-                            views::DISTANCE_RELATED_CONTROL_HORIZONTAL)));
   button_row->AddChildView(std::move(advanced_reset_button));
   button_row->AddChildView(std::move(advanced_done_button));
   form_view->AddChildView(std::move(button_row));
diff --git a/chrome/browser/ui/views/translate/partial_translate_bubble_view.h b/chrome/browser/ui/views/translate/partial_translate_bubble_view.h
index 20ab7b6..65f31c28 100644
--- a/chrome/browser/ui/views/translate/partial_translate_bubble_view.h
+++ b/chrome/browser/ui/views/translate/partial_translate_bubble_view.h
@@ -227,6 +227,8 @@
   raw_ptr<views::Combobox> target_language_combobox_ = nullptr;
 
   raw_ptr<views::TabbedPane> tabbed_pane_ = nullptr;
+  raw_ptr<views::View> tab_view_top_row_ = nullptr;
+  raw_ptr<views::Label> partial_text_label_ = nullptr;
 
   raw_ptr<views::LabelButton> advanced_reset_button_source_ = nullptr;
   raw_ptr<views::LabelButton> advanced_reset_button_target_ = nullptr;
diff --git a/chrome/browser/ui/views/translate/translate_bubble_view.cc b/chrome/browser/ui/views/translate/translate_bubble_view.cc
index 1597648..3ee0eb4c 100644
--- a/chrome/browser/ui/views/translate/translate_bubble_view.cc
+++ b/chrome/browser/ui/views/translate/translate_bubble_view.cc
@@ -875,16 +875,18 @@
     std::unique_ptr<views::Button> advanced_done_button,
     std::unique_ptr<views::Checkbox> advanced_always_translate_checkbox) {
   const ChromeLayoutProvider* provider = ChromeLayoutProvider::Get();
-  auto view = std::make_unique<AdvancedViewContainer>();
-  auto* layout = view->SetLayoutManager(std::make_unique<views::BoxLayout>());
-  layout->set_between_child_spacing(
-      provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_HORIZONTAL));
-
-  std::unique_ptr<views::ImageView> language_icon = CreateTranslateIcon();
   const int vertical_spacing =
       provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_VERTICAL);
+  const int horizontal_spacing =
+      provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_HORIZONTAL);
+
+  auto view = std::make_unique<AdvancedViewContainer>();
+  auto* layout = view->SetLayoutManager(std::make_unique<views::BoxLayout>());
+  layout->set_between_child_spacing(horizontal_spacing);
+
+  std::unique_ptr<views::ImageView> language_icon = CreateTranslateIcon();
   if (!UseGoogleTranslateBranding()) {
-    // If the bottom branding isn't showing, display the leading translate
+    // If the bottom branding isn't showing, display the leading Translate
     // icon otherwise it's not obvious what the bubble is about. This should
     // only happen on non-Chrome-branded builds.
     auto* icon_view =
@@ -895,7 +897,9 @@
                            gfx::Insets::VH(vertical_spacing, 0));
   }
   auto* form_view = view->AddChildView(std::make_unique<views::View>());
-  // Stretch `form_view` to fit the rest of bubble's width.
+  // Stretch |form_view| to fit the rest of bubble's width. Note that because no
+  // other view has flex set, the flex argument here can be any positive
+  // integer.
   layout->SetFlexForView(form_view, 1);
   form_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
       views::BoxLayout::Orientation::kVertical, gfx::Insets(),
@@ -904,44 +908,49 @@
   language_title_label->SetProperty(
       views::kMarginsKey,
       gfx::Insets::TLBR(vertical_spacing, 0, vertical_spacing,
-                        provider->GetDistanceMetric(
-                            views::DISTANCE_RELATED_CONTROL_HORIZONTAL) *
-                            4));
+                        horizontal_spacing * 4));
   language_title_label->SetProperty(views::kCrossAxisAlignmentKey,
                                     views::LayoutAlignment::kStart);
   auto* title_row = form_view->AddChildView(std::make_unique<views::View>());
   title_row->SetLayoutManager(std::make_unique<views::FlexLayout>());
-  title_row->AddChildView(std::move(language_title_label));
+  auto* title_label = title_row->AddChildView(std::move(language_title_label));
+  auto* padding_view = title_row->AddChildView(std::make_unique<views::View>());
   title_row->AddChildView(CreateCloseButton())
       ->SetProperty(views::kCrossAxisAlignmentKey,
                     views::LayoutAlignment::kStart);
+  // Set flex specifications for |title_row| views.
+  title_label->SetProperty(
+      views::kFlexBehaviorKey,
+      views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToMinimum,
+                               views::MaximumFlexSizeRule::kPreferred));
+  // |padding_view| is unbounded so that the close button stays right aligned
+  // when the bubble expands.
+  padding_view->SetProperty(
+      views::kFlexBehaviorKey,
+      views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
+                               views::MaximumFlexSizeRule::kUnbounded)
+          .WithOrder(2));
 
   form_view->AddChildView(std::move(combobox))
-      ->SetProperty(
-          views::kMarginsKey,
-          gfx::Insets::TLBR(0, 0, 0,
-                            provider->GetDistanceMetric(
-                                views::DISTANCE_RELATED_CONTROL_HORIZONTAL)));
+      ->SetProperty(views::kMarginsKey,
+                    gfx::Insets::TLBR(0, 0, 0, horizontal_spacing));
 
   auto button_row = std::make_unique<views::BoxLayoutView>();
   if (advanced_always_translate_checkbox) {
     advanced_always_translate_checkbox_ =
         form_view->AddChildView(std::move(advanced_always_translate_checkbox));
-    button_row->SetProperty(views::kMarginsKey,
-                            gfx::Insets::TLBR(vertical_spacing, 0, 0, 0));
+    button_row->SetProperty(
+        views::kMarginsKey,
+        gfx::Insets::TLBR(vertical_spacing, 0, 0, horizontal_spacing));
   } else {
-    button_row->SetProperty(views::kMarginsKey,
-                            gfx::Insets::TLBR(2 * vertical_spacing, 0, 0, 0));
+    button_row->SetProperty(
+        views::kMarginsKey,
+        gfx::Insets::TLBR(2 * vertical_spacing, 0, 0, horizontal_spacing));
   }
 
   button_row->SetMainAxisAlignment(views::BoxLayout::MainAxisAlignment::kEnd);
   button_row->SetBetweenChildSpacing(
       provider->GetDistanceMetric(views::DISTANCE_RELATED_BUTTON_HORIZONTAL));
-  button_row->SetProperty(
-      views::kMarginsKey,
-      gfx::Insets::TLBR(0, 0, 0,
-                        provider->GetDistanceMetric(
-                            views::DISTANCE_RELATED_CONTROL_HORIZONTAL)));
   button_row->AddChildView(std::move(advanced_reset_button));
   button_row->AddChildView(std::move(advanced_done_button));
   form_view->AddChildView(std::move(button_row));
diff --git a/chrome/browser/ui/webui/favicon_source_unittest.cc b/chrome/browser/ui/webui/favicon_source_unittest.cc
index e876c54..64fadb2 100644
--- a/chrome/browser/ui/webui/favicon_source_unittest.cc
+++ b/chrome/browser/ui/webui/favicon_source_unittest.cc
@@ -48,8 +48,6 @@
 
 }  // namespace
 
-void Noop(scoped_refptr<base::RefCountedMemory>) {}
-
 class MockHistoryUiFaviconRequestHandler
     : public favicon::HistoryUiFaviconRequestHandler {
  public:
@@ -178,14 +176,14 @@
   SetDarkMode(true);
   EXPECT_CALL(*source(), LoadIconBytes(_, IDR_DEFAULT_FAVICON_DARK));
   source()->StartDataRequest(GURL(kDummyPrefix), test_web_contents_getter_,
-                             base::BindOnce(&Noop));
+                             base::DoNothing());
 }
 
 TEST_F(FaviconSourceTestWithLegacyFormat, LightDefault) {
   SetDarkMode(false);
   EXPECT_CALL(*source(), LoadIconBytes(_, IDR_DEFAULT_FAVICON));
   source()->StartDataRequest(GURL(kDummyPrefix), test_web_contents_getter_,
-                             base::BindOnce(&Noop));
+                             base::DoNothing());
 }
 
 TEST_F(FaviconSourceTestWithLegacyFormat,
@@ -199,7 +197,7 @@
 
   source()->StartDataRequest(
       GURL(base::StrCat({kDummyPrefix, "size/16@1x/https://www.google.com"})),
-      test_web_contents_getter_, base::BindOnce(&Noop));
+      test_web_contents_getter_, base::DoNothing());
 }
 
 TEST_F(FaviconSourceTestWithLegacyFormat,
@@ -232,7 +230,7 @@
   base::HistogramTester tester;
   source()->StartDataRequest(
       GURL(base::StrCat({kDummyPrefix, "size/16@1x/https://www.google.com"})),
-      test_web_contents_getter_, base::BindOnce(&Noop));
+      test_web_contents_getter_, base::DoNothing());
   std::unique_ptr<base::HistogramSamples> samples(
       tester.GetHistogramSamplesSinceCreation(
           "Extensions.FaviconResourceUsed"));
@@ -244,14 +242,14 @@
   SetDarkMode(true);
   EXPECT_CALL(*source(), LoadIconBytes(_, IDR_DEFAULT_FAVICON_DARK));
   source()->StartDataRequest(GURL(kDummyPrefix), test_web_contents_getter_,
-                             base::BindOnce(&Noop));
+                             base::DoNothing());
 }
 
 TEST_F(FaviconSourceTestWithFavicon2Format, LightDefault) {
   SetDarkMode(false);
   EXPECT_CALL(*source(), LoadIconBytes(_, IDR_DEFAULT_FAVICON));
   source()->StartDataRequest(GURL(kDummyPrefix), test_web_contents_getter_,
-                             base::BindOnce(&Noop));
+                             base::DoNothing());
 }
 
 TEST_F(FaviconSourceTestWithFavicon2Format,
@@ -268,7 +266,7 @@
           {kDummyPrefix,
            "?size=16&scale_factor=1x&pageUrl=https%3A%2F%2Fwww.google."
            "com&allowGoogleServerFallback=0"})),
-      test_web_contents_getter_, base::BindOnce(&Noop));
+      test_web_contents_getter_, base::DoNothing());
 }
 
 TEST_F(FaviconSourceTestWithFavicon2Format,
@@ -285,7 +283,7 @@
           {kDummyPrefix,
            "?size=16&scale_factor=1x&pageUrl=https%3A%2F%2Fwww.google."
            "com&allowGoogleServerFallback=1"})),
-      test_web_contents_getter_, base::BindOnce(&Noop));
+      test_web_contents_getter_, base::DoNothing());
 }
 
 TEST_F(
@@ -303,7 +301,7 @@
           {kDummyPrefix,
            "?size=16&scale_factor=1x&pageUrl=https%3A%2F%2Fwww.google."
            "com&allowGoogleServerFallback=1"})),
-      test_web_contents_getter_, base::BindOnce(&Noop));
+      test_web_contents_getter_, base::DoNothing());
 }
 
 TEST_F(FaviconSourceTestWithFavicon2Format,
@@ -317,5 +315,5 @@
   // 100x scale factor runs into the max cap.
   source()->StartDataRequest(
       GURL(base::StrCat({kDummyPrefix, "size/16@100x/https://www.google.com"})),
-      test_web_contents_getter_, base::BindOnce(&Noop));
+      test_web_contents_getter_, base::DoNothing());
 }
diff --git a/chrome/browser/ui/webui/reset_password/OWNERS b/chrome/browser/ui/webui/reset_password/OWNERS
index 6a739ea7..929be25 100644
--- a/chrome/browser/ui/webui/reset_password/OWNERS
+++ b/chrome/browser/ui/webui/reset_password/OWNERS
@@ -1,6 +1,4 @@
 per-file *.mojom=set noparent
 per-file *.mojom=file://ipc/SECURITY_OWNERS
 
-drubery@chromium.org
-nparker@chromium.org
-vakh@chromium.org
+file://components/safe_browsing/OWNERS
diff --git a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
index 9202fc2..9931520 100644
--- a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
@@ -1287,6 +1287,11 @@
               autofill::payments::GetVirtualCardEnrollmentSupportUrl()
                   .spec())));
 
+  html_source->AddBoolean(
+      "virtualCardMetadataEnabled",
+      base::FeatureList::IsEnabled(
+          autofill::features::kAutofillEnableVirtualCardMetadata));
+
   html_source->AddLocalizedStrings(kLocalizedStrings);
 }
 
diff --git a/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc b/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc
index 5775a96..ce1991c 100644
--- a/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/site_settings_handler_unittest.cc
@@ -60,6 +60,7 @@
 #include "components/permissions/permission_decision_auto_blocker.h"
 #include "components/permissions/permission_uma_util.h"
 #include "components/permissions/test/object_permission_context_base_mock_permission_observer.h"
+#include "components/permissions/test/permission_test_util.h"
 #include "components/services/app_service/public/cpp/app_registry_cache.h"
 #include "components/services/app_service/public/cpp/app_types.h"
 #include "components/services/app_service/public/cpp/app_update.h"
@@ -183,10 +184,7 @@
  public:
   ContentSettingSourceSetter(TestingProfile* profile,
                              ContentSettingsType content_type)
-      : prefs_(profile->GetTestingPrefService()),
-        host_content_settings_map_(
-            HostContentSettingsMapFactory::GetForProfile(profile)),
-        content_type_(content_type) {}
+      : prefs_(profile->GetTestingPrefService()), content_type_(content_type) {}
   ContentSettingSourceSetter(const ContentSettingSourceSetter&) = delete;
   ContentSettingSourceSetter& operator=(const ContentSettingSourceSetter&) =
       delete;
@@ -209,7 +207,6 @@
 
  private:
   raw_ptr<sync_preferences::TestingPrefServiceSyncable> prefs_;
-  raw_ptr<HostContentSettingsMap> host_content_settings_map_;
   ContentSettingsType content_type_;
 };
 
@@ -251,7 +248,11 @@
     mock_privacy_sandbox_service_ = static_cast<MockPrivacySandboxService*>(
         PrivacySandboxServiceFactory::GetInstance()->SetTestingFactoryAndUse(
             profile(), base::BindRepeating(&BuildMockPrivacySandboxService)));
-    handler_ = std::make_unique<SiteSettingsHandler>(profile_.get());
+
+    profile()->SetPermissionControllerDelegate(
+        permissions::GetPermissionControllerDelegate(profile()));
+
+    handler_ = std::make_unique<SiteSettingsHandler>(profile());
     handler()->set_web_ui(web_ui());
     handler()->AllowJavascript();
     // AllowJavascript() adds a callback to create leveldb_env::ChromiumEnv
diff --git a/chrome/browser/ui/webui/settings/site_settings_helper.cc b/chrome/browser/ui/webui/settings/site_settings_helper.cc
index eab4779..af1fdcf 100644
--- a/chrome/browser/ui/webui/settings/site_settings_helper.cc
+++ b/chrome/browser/ui/webui/settings/site_settings_helper.cc
@@ -23,7 +23,6 @@
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/hid/hid_chooser_context.h"
 #include "chrome/browser/hid/hid_chooser_context_factory.h"
-#include "chrome/browser/permissions/permission_manager_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/serial/serial_chooser_context.h"
 #include "chrome/browser/serial/serial_chooser_context_factory.h"
@@ -40,7 +39,6 @@
 #include "components/permissions/contexts/bluetooth_chooser_context.h"
 #include "components/permissions/object_permission_context_base.h"
 #include "components/permissions/permission_decision_auto_blocker.h"
-#include "components/permissions/permission_manager.h"
 #include "components/permissions/permission_result.h"
 #include "components/permissions/permission_util.h"
 #include "components/permissions/permissions_client.h"
@@ -49,11 +47,14 @@
 #include "components/subresource_filter/content/browser/subresource_filter_profile_context.h"
 #include "components/subresource_filter/core/browser/subresource_filter_features.h"
 #include "components/url_formatter/url_formatter.h"
+#include "content/public/browser/permission_controller.h"
+#include "content/public/browser/permission_result.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/common/url_utils.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/common/constants.h"
+#include "third_party/blink/public/common/permissions/permission_utils.h"
 #include "url/origin.h"
 
 namespace site_settings {
@@ -733,9 +734,14 @@
   if (permissions::PermissionDecisionAutoBlocker::IsEnabledForContentSetting(
           content_type)) {
     if (permissions::PermissionUtil::IsPermission(content_type)) {
+      content::PermissionResult permission_result =
+          profile->GetPermissionController()
+              ->GetPermissionResultForOriginWithoutContext(
+                  permissions::PermissionUtil::
+                      ContentSettingTypeToPermissionType(content_type),
+                  url::Origin::Create(origin));
       result =
-          PermissionManagerFactory::GetForProfile(profile)
-              ->GetPermissionStatusForDisplayOnSettingsUI(content_type, origin);
+          permissions::PermissionUtil::ToPermissionResult(permission_result);
     } else {
       permissions::PermissionDecisionAutoBlocker* auto_blocker =
           permissions::PermissionsClient::Get()
diff --git a/chrome/browser/ui/webui/settings/site_settings_helper_unittest.cc b/chrome/browser/ui/webui/settings/site_settings_helper_unittest.cc
index 68f6e5f..e856371 100644
--- a/chrome/browser/ui/webui/settings/site_settings_helper_unittest.cc
+++ b/chrome/browser/ui/webui/settings/site_settings_helper_unittest.cc
@@ -26,6 +26,7 @@
 #include "components/permissions/object_permission_context_base.h"
 #include "components/permissions/permission_decision_auto_blocker.h"
 #include "components/permissions/permissions_client.h"
+#include "components/permissions/test/permission_test_util.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/test/browser_task_environment.h"
 #include "extensions/browser/extension_registry.h"
@@ -416,6 +417,8 @@
 // default, user-set pattern, user-set origin setting, extension, and policy.
 TEST_F(SiteSettingsHelperTest, ContentSettingSource) {
   TestingProfile profile;
+  profile.SetPermissionControllerDelegate(
+      permissions::GetPermissionControllerDelegate(&profile));
   HostContentSettingsMap* map =
       HostContentSettingsMapFactory::GetForProfile(&profile);
 
diff --git a/chrome/browser/vr/ui_host/vr_ui_host_impl.cc b/chrome/browser/vr/ui_host/vr_ui_host_impl.cc
index 2c842dc..365ba93 100644
--- a/chrome/browser/vr/ui_host/vr_ui_host_impl.cc
+++ b/chrome/browser/vr/ui_host/vr_ui_host_impl.cc
@@ -9,7 +9,6 @@
 #include "build/build_config.h"
 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
 #include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
-#include "chrome/browser/permissions/permission_manager_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
@@ -17,13 +16,13 @@
 #include "chrome/browser/vr/vr_tab_helper.h"
 #include "chrome/browser/vr/win/vr_browser_renderer_thread_win.h"
 #include "components/content_settings/browser/page_specific_content_settings.h"
-#include "components/permissions/permission_manager.h"
-#include "components/permissions/permission_result.h"
 #include "content/public/browser/device_service.h"
 #include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/permission_controller.h"
 #include "content/public/browser/xr_runtime_manager.h"
 #include "device/base/features.h"
 #include "device/vr/public/mojom/vr_service.mojom.h"
+#include "third_party/blink/public/common/permissions/permission_utils.h"
 #include "ui/base/l10n/l10n_util.h"
 
 namespace vr {
@@ -352,33 +351,28 @@
   potential_capturing_ = g_default_capturing_state;
 
   DCHECK(web_contents_);
-  permissions::PermissionManager* permission_manager =
-      PermissionManagerFactory::GetForProfile(
-          Profile::FromBrowserContext(web_contents_->GetBrowserContext()));
+  content::PermissionController* permission_controller =
+      web_contents_->GetBrowserContext()->GetPermissionController();
   potential_capturing_.audio_capture_enabled =
-      permission_manager
-          ->GetPermissionStatusForCurrentDocument(
-              ContentSettingsType::MEDIASTREAM_MIC,
-              web_contents_->GetPrimaryMainFrame())
-          .content_setting == CONTENT_SETTING_ALLOW;
+      permission_controller->GetPermissionStatusForCurrentDocument(
+          blink::PermissionType::AUDIO_CAPTURE,
+          web_contents_->GetPrimaryMainFrame()) ==
+      blink::mojom::PermissionStatus::GRANTED;
   potential_capturing_.video_capture_enabled =
-      permission_manager
-          ->GetPermissionStatusForCurrentDocument(
-              ContentSettingsType::MEDIASTREAM_CAMERA,
-              web_contents_->GetPrimaryMainFrame())
-          .content_setting == CONTENT_SETTING_ALLOW;
+      permission_controller->GetPermissionStatusForCurrentDocument(
+          blink::PermissionType::VIDEO_CAPTURE,
+          web_contents_->GetPrimaryMainFrame()) ==
+      blink::mojom::PermissionStatus::GRANTED;
   potential_capturing_.location_access_enabled =
-      permission_manager
-          ->GetPermissionStatusForCurrentDocument(
-              ContentSettingsType::GEOLOCATION,
-              web_contents_->GetPrimaryMainFrame())
-          .content_setting == CONTENT_SETTING_ALLOW;
+      permission_controller->GetPermissionStatusForCurrentDocument(
+          blink::PermissionType::GEOLOCATION,
+          web_contents_->GetPrimaryMainFrame()) ==
+      blink::mojom::PermissionStatus::GRANTED;
   potential_capturing_.midi_connected =
-      permission_manager
-          ->GetPermissionStatusForCurrentDocument(
-              ContentSettingsType::MIDI_SYSEX,
-              web_contents_->GetPrimaryMainFrame())
-          .content_setting == CONTENT_SETTING_ALLOW;
+      permission_controller->GetPermissionStatusForCurrentDocument(
+          blink::PermissionType::MIDI_SYSEX,
+          web_contents_->GetPrimaryMainFrame()) ==
+      blink::mojom::PermissionStatus::GRANTED;
 
   indicators_shown_start_time_ = base::Time::Now();
   indicators_visible_ = false;
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index bf3803f..2edfc19d 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1660219080-5c2d21bf4dd331c9b4e976e9165d27d27759ab52.profdata
+chrome-linux-main-1660240770-a632af6c039f2ad4413e996af2f98bfc7f9434f2.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index c1f08ae3..1a3409c 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1660193600-2b7fa3816328527a7e00a736424cbd7cbf96c851.profdata
+chrome-mac-main-1660240770-77ae20bf4e2aad589798cef67e057a2c4c231c49.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 64c1c351..b63ff69 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1660219080-817777645bb5a96bb8c86e7fa2a9003a46078df2.profdata
+chrome-win32-main-1660229884-9bd506015e7ee89f769e0301f1c300238928a0c9.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index cf6749fe..661bf81 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1660229884-63764646f5a984f02a3c0edd8fa1017e1d45b5cf.profdata
+chrome-win64-main-1660240770-0f7eb5b3d8c1500481b67b9d706ad42b10d14b1e.profdata
diff --git a/chrome/common/accessibility/read_anything.mojom b/chrome/common/accessibility/read_anything.mojom
index 76b7fb0..1876292 100644
--- a/chrome/common/accessibility/read_anything.mojom
+++ b/chrome/common/accessibility/read_anything.mojom
@@ -5,6 +5,7 @@
 // A module for a prototype of the Read Anything feature.
 module read_anything.mojom;
 
+import "skia/public/mojom/skcolor.mojom";
 import "ui/accessibility/mojom/ax_tree_update.mojom";
 
 // Used to represent the current user choices for the Read Anything visual
@@ -15,6 +16,10 @@
 
   // The px value of the user's font size.
   float font_size;
+
+  // The various colors of the user's chosen theme.
+  skia.mojom.SkColor foreground_color;
+  skia.mojom.SkColor background_color;
 };
 
 // Used by the WebUI page to bootstrap bidirectional communication.
diff --git a/chrome/common/safe_browsing/OWNERS b/chrome/common/safe_browsing/OWNERS
index 89d2255..dcfaa52 100644
--- a/chrome/common/safe_browsing/OWNERS
+++ b/chrome/common/safe_browsing/OWNERS
@@ -1,10 +1,4 @@
-drubery@chromium.org
-nparker@chromium.org
-vakh@chromium.org
-
-# This is for the common case of adding or renaming files. If you're doing
-# structural changes, use usual OWNERS rules.
-per-file BUILD.gn=*
+file://components/safe_browsing/OWNERS
 
 per-file *_messages*.h=set noparent
 per-file *_messages*.h=file://ipc/SECURITY_OWNERS
diff --git a/chrome/renderer/accessibility/read_anything_app_controller.cc b/chrome/renderer/accessibility/read_anything_app_controller.cc
index dd0ed1c..ed197399 100644
--- a/chrome/renderer/accessibility/read_anything_app_controller.cc
+++ b/chrome/renderer/accessibility/read_anything_app_controller.cc
@@ -203,6 +203,8 @@
 void ReadAnythingAppController::OnThemeChanged(ReadAnythingThemePtr new_theme) {
   font_name_ = new_theme->font_name;
   font_size_ = new_theme->font_size;
+  foreground_color_ = new_theme->foreground_color;
+  background_color_ = new_theme->background_color;
 
   // TODO(abigailbklein): Use v8::Function rather than javascript. If possible,
   // replace this function call with firing an event.
@@ -217,6 +219,10 @@
       .SetProperty("contentNodeIds", &ReadAnythingAppController::ContentNodeIds)
       .SetProperty("fontName", &ReadAnythingAppController::FontName)
       .SetProperty("fontSize", &ReadAnythingAppController::FontSize)
+      .SetProperty("foregroundColor",
+                   &ReadAnythingAppController::ForegroundColor)
+      .SetProperty("backgroundColor",
+                   &ReadAnythingAppController::BackgroundColor)
       .SetMethod("getChildren", &ReadAnythingAppController::GetChildren)
       .SetMethod("getHeadingLevel", &ReadAnythingAppController::GetHeadingLevel)
       .SetMethod("getTextContent", &ReadAnythingAppController::GetTextContent)
@@ -244,6 +250,14 @@
   return font_size_;
 }
 
+SkColor ReadAnythingAppController::ForegroundColor() {
+  return foreground_color_;
+}
+
+SkColor ReadAnythingAppController::BackgroundColor() {
+  return background_color_;
+}
+
 std::vector<ui::AXNodeID> ReadAnythingAppController::GetChildren(
     ui::AXNodeID ax_node_id) {
   std::vector<ui::AXNodeID> child_ids;
@@ -318,8 +332,11 @@
 }
 
 void ReadAnythingAppController::SetThemeForTesting(const std::string& font_name,
-                                                   float font_size) {
-  OnThemeChanged(ReadAnythingTheme::New(font_name, font_size));
+                                                   float font_size,
+                                                   SkColor foreground_color,
+                                                   SkColor background_color) {
+  OnThemeChanged(ReadAnythingTheme::New(font_name, font_size, foreground_color,
+                                        background_color));
 }
 
 void ReadAnythingAppController::SetContentForTesting(
diff --git a/chrome/renderer/accessibility/read_anything_app_controller.h b/chrome/renderer/accessibility/read_anything_app_controller.h
index a9dc29f..9e2e452 100644
--- a/chrome/renderer/accessibility/read_anything_app_controller.h
+++ b/chrome/renderer/accessibility/read_anything_app_controller.h
@@ -13,6 +13,7 @@
 #include "gin/wrappable.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
+#include "third_party/skia/include/core/SkColor.h"
 #include "ui/accessibility/ax_node_id_forward.h"
 #include "ui/accessibility/ax_tree_update_forward.h"
 
@@ -73,6 +74,8 @@
   std::vector<ui::AXNodeID> ContentNodeIds();
   std::string FontName();
   float FontSize();
+  SkColor ForegroundColor();
+  SkColor BackgroundColor();
   std::vector<ui::AXNodeID> GetChildren(ui::AXNodeID ax_node_id);
   uint32_t GetHeadingLevel(ui::AXNodeID ax_node_id);
   std::string GetTextContent(ui::AXNodeID ax_node_id);
@@ -102,7 +105,10 @@
   //   };
   void SetContentForTesting(v8::Local<v8::Value> v8_snapshot_lite,
                             std::vector<ui::AXNodeID> content_node_ids);
-  void SetThemeForTesting(const std::string& font_name, float font_size);
+  void SetThemeForTesting(const std::string& font_name,
+                          float font_size,
+                          SkColor foreground_color,
+                          SkColor background_color);
 
   ui::AXNode* GetAXNode(ui::AXNodeID ax_node_id);
 
@@ -116,6 +122,8 @@
   std::vector<ui::AXNodeID> content_node_ids_;
   std::string font_name_;
   float font_size_;
+  SkColor foreground_color_;
+  SkColor background_color_;
 };
 
 #endif  // CHROME_RENDERER_ACCESSIBILITY_READ_ANYTHING_APP_CONTROLLER_H_
diff --git a/chrome/renderer/accessibility/read_anything_app_controller_browsertest.cc b/chrome/renderer/accessibility/read_anything_app_controller_browsertest.cc
index 9d4a93f2..a74b356d 100644
--- a/chrome/renderer/accessibility/read_anything_app_controller_browsertest.cc
+++ b/chrome/renderer/accessibility/read_anything_app_controller_browsertest.cc
@@ -34,8 +34,12 @@
     basic_snapshot_.nodes[3].id = 4;
   }
 
-  void SetThemeForTesting(const std::string& font_name, float font_size) {
-    controller_->SetThemeForTesting(font_name, font_size);
+  void SetThemeForTesting(const std::string& font_name,
+                          float font_size,
+                          SkColor foreground_color,
+                          SkColor background_color) {
+    controller_->SetThemeForTesting(font_name, font_size, foreground_color,
+                                    background_color);
   }
   void OnAXTreeDistilled(const ui::AXTreeUpdate& snapshot,
                          const std::vector<ui::AXNodeID>& content_node_ids) {
@@ -50,6 +54,10 @@
 
   float FontSize() { return controller_->FontSize(); }
 
+  SkColor ForegroundColor() { return controller_->ForegroundColor(); }
+
+  SkColor BackgroundColor() { return controller_->BackgroundColor(); }
+
   std::vector<ui::AXNodeID> GetChildren(ui::AXNodeID ax_node_id) {
     return controller_->GetChildren(ax_node_id);
   }
@@ -93,9 +101,13 @@
 TEST_F(ReadAnythingAppControllerTest, Theme) {
   std::string font_name = "Roboto";
   float font_size = 18.0;
-  SetThemeForTesting(font_name, font_size);
+  SkColor foreground = SkColorSetRGB(0x33, 0x36, 0x39);
+  SkColor background = SkColorSetRGB(0xFD, 0xE2, 0x93);
+  SetThemeForTesting(font_name, font_size, foreground, background);
   EXPECT_EQ(font_name, FontName());
   EXPECT_EQ(font_size, FontSize());
+  EXPECT_EQ(foreground, ForegroundColor());
+  EXPECT_EQ(background, BackgroundColor());
 }
 
 TEST_F(ReadAnythingAppControllerTest, ContentNodeIds) {
diff --git a/chrome/renderer/cart/commerce_hint_agent.cc b/chrome/renderer/cart/commerce_hint_agent.cc
index 99dc734..c006dce3 100644
--- a/chrome/renderer/cart/commerce_hint_agent.cc
+++ b/chrome/renderer/cart/commerce_hint_agent.cc
@@ -809,10 +809,12 @@
     javascript_request_ = new JavaScriptRequest(weak_factory_.GetWeakPtr());
   }
   main_frame->RequestExecuteScript(
-      ISOLATED_WORLD_ID_CHROME_INTERNAL, base::make_span(&source, 1), false,
-      blink::WebLocalFrame::kAsynchronous, javascript_request_,
+      ISOLATED_WORLD_ID_CHROME_INTERNAL, base::make_span(&source, 1),
+      blink::mojom::UserActivationOption::kDoNotActivate,
+      blink::mojom::EvaluationTiming::kAsynchronous,
+      blink::mojom::LoadEventBlockingOption::kDoNotBlock, javascript_request_,
       blink::BackForwardCacheAware::kAllow,
-      blink::WebLocalFrame::PromiseBehavior::kAwait);
+      blink::mojom::PromiseResultOption::kAwait);
 }
 
 CommerceHintAgent::JavaScriptRequest::JavaScriptRequest(
diff --git a/chrome/renderer/safe_browsing/OWNERS b/chrome/renderer/safe_browsing/OWNERS
index 70e4b68..5225674 100644
--- a/chrome/renderer/safe_browsing/OWNERS
+++ b/chrome/renderer/safe_browsing/OWNERS
@@ -1,3 +1 @@
-drubery@chromium.org
-nparker@chromium.org
-vakh@chromium.org
+file://components/safe_browsing/OWNERS
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index dfcafe6..86d187f 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -299,7 +299,6 @@
     "//net:test_support",
     "//ppapi/buildflags",
     "//printing/buildflags",
-    "//services/cert_verifier:test_support",
     "//services/data_decoder/public/cpp:test_support",
     "//services/device/public/cpp:test_support",
     "//services/device/public/cpp/geolocation",
@@ -5508,9 +5507,12 @@
   ]
 
   if (!is_android) {
-    # The test is disabled for Android. The respective functionality is currently not being used on
-    # Android. We should enable this test when Android becomes supported.
-    sources += [ "../browser/policy/messaging_layer/util/reporting_server_connector_unittest.cc" ]
+    sources += [
+      # The test is disabled for Android. The respective functionality is currently not being used on
+      # Android. We should enable this test when Android becomes supported.
+      "../browser/performance_manager/metrics/metrics_provider_unittest.cc",
+      "../browser/policy/messaging_layer/util/reporting_server_connector_unittest.cc",
+    ]
   }
 
   if (enable_feed_v2) {
@@ -6536,6 +6538,8 @@
       "../browser/performance_manager/test_support/page_discarding_utils.h",
       "../browser/performance_manager/test_support/site_data_utils.cc",
       "../browser/performance_manager/test_support/site_data_utils.h",
+      "../browser/performance_manager/user_tuning/fake_frame_throttling_delegate.cc",
+      "../browser/performance_manager/user_tuning/fake_frame_throttling_delegate.h",
       "../browser/platform_util_unittest.cc",
       "../browser/policy/policy_path_parser_unittest.cc",
       "../browser/policy/serial_allow_usb_devices_for_urls_policy_handler_unittest.cc",
@@ -9320,6 +9324,7 @@
       "//components/javascript_dialogs",
       "//components/keep_alive_registry",
       "//components/live_caption",
+      "//components/live_caption:constants",
       "//components/media_message_center",
       "//components/media_router/browser:test_support",
       "//components/metrics:content",
diff --git a/chrome/test/chromedriver/BUILD.gn b/chrome/test/chromedriver/BUILD.gn
index 68fe940..eaa7ad9 100644
--- a/chrome/test/chromedriver/BUILD.gn
+++ b/chrome/test/chromedriver/BUILD.gn
@@ -59,6 +59,24 @@
   args += rebase_path(ts_files, root_build_dir)
 }
 
+action("embed_bidimapper_in_cpp") {
+  script = "embed_bidimapper_in_cpp.py"
+
+  js_files = [ "//third_party/bidimapper/mapper.js" ]
+
+  inputs = [ "cpp_source.py" ] + js_files
+
+  outputs = [
+    "$target_gen_dir/bidimapper/bidimapper.cc",
+    "$target_gen_dir/bidimapper/bidimapper.h",
+  ]
+  args = [
+    "--directory",
+    rebase_path("$target_gen_dir/bidimapper", root_build_dir),
+  ]
+  args += rebase_path(js_files, root_build_dir)
+}
+
 action("embed_user_data_dir_in_cpp") {
   script = "embed_user_data_dir_in_cpp.py"
 
@@ -86,6 +104,8 @@
     "chrome/adb.h",
     "chrome/adb_impl.cc",
     "chrome/adb_impl.h",
+    "chrome/bidi_tracker.cc",
+    "chrome/bidi_tracker.h",
     "chrome/browser_info.cc",
     "chrome/browser_info.h",
     "chrome/cast_tracker.cc",
@@ -196,9 +216,11 @@
   # Also compile the generated files.
   sources += get_target_outputs(":embed_js_in_cpp")
   sources += get_target_outputs(":embed_mobile_devices_in_cpp")
+  sources += get_target_outputs(":embed_bidimapper_in_cpp")
   sources += get_target_outputs(":embed_user_data_dir_in_cpp")
 
   deps = [
+    ":embed_bidimapper_in_cpp",
     ":embed_js_in_cpp",
     ":embed_mobile_devices_in_cpp",
     ":embed_user_data_dir_in_cpp",
diff --git a/chrome/test/chromedriver/chrome/bidi_tracker.cc b/chrome/test/chromedriver/chrome/bidi_tracker.cc
new file mode 100644
index 0000000..d293cd1
--- /dev/null
+++ b/chrome/test/chromedriver/chrome/bidi_tracker.cc
@@ -0,0 +1,54 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/test/chromedriver/chrome/bidi_tracker.h"
+
+#include <stddef.h>
+
+#include <utility>
+
+#include "base/logging.h"
+#include "base/values.h"
+#include "chrome/test/chromedriver/chrome/devtools_client.h"
+#include "chrome/test/chromedriver/chrome/status.h"
+
+BidiTracker::BidiTracker() = default;
+
+BidiTracker::~BidiTracker() = default;
+
+bool BidiTracker::ListensToConnections() const {
+  return false;
+}
+
+Status BidiTracker::OnEvent(DevToolsClient* client,
+                            const std::string& method,
+                            const base::DictionaryValue& params) {
+  if (method != "Runtime.bindingCalled") {
+    return Status(kOk);
+  }
+
+  const base::Value* nameVal = params.FindKey("name");
+  if (nameVal == nullptr) {
+    return Status(kUnknownError, "Runtime.bindingCalled missing 'name'");
+  }
+  std::string name = nameVal->GetString();
+  if (name != "sendBidiResponse") {
+    // We are not interested in this function call
+    return Status(kOk);
+  }
+
+  const base::Value* payloadVal = params.FindKey("payload");
+  if (payloadVal == nullptr) {
+    return Status(kUnknownError, "Runtime.bindingCalled missing 'payload'");
+  }
+
+  std::string payload = payloadVal->GetString();
+  send_bidi_response_.Run(payload);
+
+  return Status(kOk);
+}
+
+void BidiTracker::SetBidiCallback(SendTextFunc on_bidi_message) {
+  send_bidi_response_ = std::move(on_bidi_message);
+}
diff --git a/chrome/test/chromedriver/chrome/bidi_tracker.h b/chrome/test/chromedriver/chrome/bidi_tracker.h
new file mode 100644
index 0000000..a84d8e9
--- /dev/null
+++ b/chrome/test/chromedriver/chrome/bidi_tracker.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_TEST_CHROMEDRIVER_CHROME_BIDI_TRACKER_H_
+#define CHROME_TEST_CHROMEDRIVER_CHROME_BIDI_TRACKER_H_
+
+#include <map>
+#include <string>
+
+#include "base/callback.h"
+#include "chrome/test/chromedriver/chrome/devtools_event_listener.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+class DevToolsClient;
+class Status;
+typedef base::RepeatingCallback<void(const std::string&)> SendTextFunc;
+
+// Tracks the state of the DOM and BiDi messages coming from the browser
+class BidiTracker : public DevToolsEventListener {
+ public:
+  BidiTracker();
+
+  BidiTracker(const BidiTracker&) = delete;
+  BidiTracker& operator=(const BidiTracker&) = delete;
+
+  ~BidiTracker() override;
+
+  // Overridden from DevToolsEventListener:
+  bool ListensToConnections() const override;
+  Status OnEvent(DevToolsClient* client,
+                 const std::string& method,
+                 const base::DictionaryValue& params) override;
+
+  void SetBidiCallback(SendTextFunc on_bidi_message);
+
+ private:
+  SendTextFunc send_bidi_response_;
+};
+
+#endif  // CHROME_TEST_CHROMEDRIVER_CHROME_BIDI_TRACKER_H_
diff --git a/chrome/test/chromedriver/chrome/chrome_impl.cc b/chrome/test/chromedriver/chrome/chrome_impl.cc
index 31a797c3..d8dd0767 100644
--- a/chrome/test/chromedriver/chrome/chrome_impl.cc
+++ b/chrome/test/chromedriver/chrome/chrome_impl.cc
@@ -792,6 +792,10 @@
   return status;
 }
 
+DevToolsClient* ChromeImpl::Client() const {
+  return devtools_websocket_client_.get();
+}
+
 ChromeImpl::ChromeImpl(std::unique_ptr<DevToolsHttpClient> http_client,
                        std::unique_ptr<DevToolsClient> websocket_client,
                        std::vector<std::unique_ptr<DevToolsEventListener>>
diff --git a/chrome/test/chromedriver/chrome/chrome_impl.h b/chrome/test/chromedriver/chrome/chrome_impl.h
index 1403f07..47d276a 100644
--- a/chrome/test/chromedriver/chrome/chrome_impl.h
+++ b/chrome/test/chromedriver/chrome/chrome_impl.h
@@ -59,6 +59,7 @@
   bool HasTouchScreen() const override;
   std::string page_load_strategy() const override;
   Status Quit() override;
+  DevToolsClient* Client() const;
 
  protected:
   ChromeImpl(std::unique_ptr<DevToolsHttpClient> http_client,
diff --git a/chrome/test/chromedriver/chrome/devtools_client_impl.cc b/chrome/test/chromedriver/chrome/devtools_client_impl.cc
index faa3c81..4bf3c89 100644
--- a/chrome/test/chromedriver/chrome/devtools_client_impl.cc
+++ b/chrome/test/chromedriver/chrome/devtools_client_impl.cc
@@ -422,7 +422,7 @@
     // but return timeout status if primary timeout has expired
     // This supports cases when loading state is updated by a different client
     Timeout funcinterval = Timeout(base::Milliseconds(500), &timeout);
-    Status status = ProcessNextMessage(-1, false, funcinterval);
+    Status status = ProcessNextMessage(-1, false, funcinterval, this);
     if (status.code() == kTimeout) {
       if (timeout.IsExpired()) {
         // Build status message based on timeout parameter, not funcinterval
@@ -524,7 +524,7 @@
         // Use a long default timeout if user has not requested one.
         Status status = ProcessNextMessage(
             command_id, true,
-            timeout != nullptr ? *timeout : Timeout(base::Minutes(10)));
+            timeout != nullptr ? *timeout : Timeout(base::Minutes(10)), this);
         if (status.IsError()) {
           if (response_info->state == kReceived)
             response_info_map_.erase(command_id);
@@ -561,7 +561,8 @@
 
 Status DevToolsClientImpl::ProcessNextMessage(int expected_id,
                                               bool log_timeout,
-                                              const Timeout& timeout) {
+                                              const Timeout& timeout,
+                                              DevToolsClientImpl* caller) {
   ScopedIncrementer increment_stack_count(&stack_count_);
   DCHECK(IsConnected());
 
@@ -591,7 +592,7 @@
     return Status(kTargetDetached);
 
   if (parent_ != nullptr)
-    return parent_->ProcessNextMessage(-1, log_timeout, timeout);
+    return parent_->ProcessNextMessage(-1, log_timeout, timeout, caller);
 
   std::string message;
   switch (socket_->ReceiveNextMessage(&message, timeout)) {
@@ -615,11 +616,12 @@
       break;
   }
 
-  return HandleMessage(expected_id, message);
+  return HandleMessage(expected_id, message, caller);
 }
 
 Status DevToolsClientImpl::HandleMessage(int expected_id,
-                                         const std::string& message) {
+                                         const std::string& message,
+                                         DevToolsClientImpl* caller) {
   std::string session_id;
   internal::InspectorMessageType type;
   internal::InspectorEvent event;
@@ -643,10 +645,46 @@
   }
   WebViewImplHolder client_holder(client->owner_);
   if (type == internal::kEventMessageType) {
-    return client->ProcessEvent(event);
+    Status status = client->ProcessEvent(event);
+    if (caller == client || this == client) {
+      // In either case we are in the root.
+      // 'this == client' means that the error has happened in the browser
+      // session. Any errors happening here are global and most likely will lead
+      // to the session termination. Forward them to the caller!
+      // 'caller == client' means that the message must be routed to the
+      // same client that invoked the current root. Sending the errors
+      // to the caller is the proper behavior in this case as well.
+      return status;
+    } else {
+      // We support active event consumption meaning that the whole session
+      // makes progress independently from the active WebDriver Classic target.
+      // This is needed for timely delivery of bidi events to the user.
+      // If something wrong happens in the different target the corresponding
+      // WebView must update its state accordingly to notify the user
+      // about the issue on the next HTTP request.
+      return Status{kOk};
+    }
   }
   CHECK_EQ(type, internal::kCommandResponseMessageType);
-  return client->ProcessCommandResponse(response);
+  Status status = client->ProcessCommandResponse(response);
+  if (caller == client || this == client) {
+    // In either case we are in the root.
+    // 'this == client' means that the error has happened in the browser
+    // session. Any errors happening here are global and most likely will lead
+    // to the session termination. Forward them to the caller!
+    // 'caller == client' means that the message must be routed to the
+    // same client that invoked the current root. Sending the errors
+    // to the caller is the proper behavior in this case as well.
+    return status;
+  } else {
+    // We support active event consumption meaning that the whole session
+    // makes progress independently from the active WebDriver Classic target.
+    // This is needed for timely delivery of bidi events to the user.
+    // If something wrong happens in the different target the corresponding
+    // WebView must update its state accordingly to notify the user
+    // about the issue on the next HTTP request.
+    return Status{kOk};
+  }
 }
 
 Status DevToolsClientImpl::ProcessEvent(const internal::InspectorEvent& event) {
diff --git a/chrome/test/chromedriver/chrome/devtools_client_impl.h b/chrome/test/chromedriver/chrome/devtools_client_impl.h
index ecc9431..f2eb3d9 100644
--- a/chrome/test/chromedriver/chrome/devtools_client_impl.h
+++ b/chrome/test/chromedriver/chrome/devtools_client_impl.h
@@ -188,8 +188,11 @@
                              const Timeout* timeout);
   Status ProcessNextMessage(int expected_id,
                             bool log_timeout,
-                            const Timeout& timeout);
-  Status HandleMessage(int expected_id, const std::string& message);
+                            const Timeout& timeout,
+                            DevToolsClientImpl* caller);
+  Status HandleMessage(int expected_id,
+                       const std::string& message,
+                       DevToolsClientImpl* caller);
   Status ProcessEvent(const internal::InspectorEvent& event);
   Status ProcessCommandResponse(
       const internal::InspectorCommandResponse& response);
@@ -228,6 +231,7 @@
   int stack_count_;
   bool is_remote_end_configured_;
   bool is_main_page_;
+  base::WeakPtrFactory<DevToolsClientImpl> weak_ptr_factory_{this};
 };
 
 namespace internal {
diff --git a/chrome/test/chromedriver/chrome/stub_web_view.cc b/chrome/test/chromedriver/chrome/stub_web_view.cc
index f121289..c82ee98 100644
--- a/chrome/test/chromedriver/chrome/stub_web_view.cc
+++ b/chrome/test/chromedriver/chrome/stub_web_view.cc
@@ -28,6 +28,11 @@
   return Status(kOk);
 }
 
+Status StubWebView::HandleEventsUntil(const ConditionalFunc& conditional_func,
+                                      const Timeout& timeout) {
+  return Status{kOk};
+}
+
 Status StubWebView::HandleReceivedEvents() {
   return Status(kOk);
 }
diff --git a/chrome/test/chromedriver/chrome/stub_web_view.h b/chrome/test/chromedriver/chrome/stub_web_view.h
index 95500112..9d3744e 100644
--- a/chrome/test/chromedriver/chrome/stub_web_view.h
+++ b/chrome/test/chromedriver/chrome/stub_web_view.h
@@ -20,6 +20,8 @@
   std::string GetId() override;
   bool WasCrashed() override;
   Status ConnectIfNecessary() override;
+  Status HandleEventsUntil(const ConditionalFunc& conditional_func,
+                           const Timeout& timeout) override;
   Status HandleReceivedEvents() override;
   Status GetUrl(std::string* url) override;
   Status Load(const std::string& url, const Timeout* timeout) override;
diff --git a/chrome/test/chromedriver/chrome/web_view.h b/chrome/test/chromedriver/chrome/web_view.h
index 42a0cae..87024dd 100644
--- a/chrome/test/chromedriver/chrome/web_view.h
+++ b/chrome/test/chromedriver/chrome/web_view.h
@@ -9,6 +9,7 @@
 #include <string>
 #include <vector>
 
+#include "base/callback_forward.h"
 #include "base/values.h"
 
 namespace base {
@@ -31,7 +32,10 @@
 
 class WebView {
  public:
-  virtual ~WebView() {}
+  typedef base::RepeatingCallback<Status(bool* is_condition_met)>
+      ConditionalFunc;
+
+  virtual ~WebView() = default;
 
   virtual bool IsServiceWorker() const = 0;
 
@@ -44,6 +48,14 @@
   // Make DevToolsCient connect to DevTools if it is disconnected.
   virtual Status ConnectIfNecessary() = 0;
 
+  // Handles events until the given function reports the condition is met
+  // and there are no more received events to handle. If the given
+  // function ever returns an error, returns immediately with the error.
+  // If the condition is not met within |timeout|, kTimeout status
+  // is returned eventually. If |timeout| is 0, this function will not block.
+  virtual Status HandleEventsUntil(const ConditionalFunc& conditional_func,
+                                   const Timeout& timeout) = 0;
+
   // Handles events that have been received but not yet handled.
   virtual Status HandleReceivedEvents() = 0;
 
diff --git a/chrome/test/chromedriver/chrome/web_view_impl.cc b/chrome/test/chromedriver/chrome/web_view_impl.cc
index d87652c3..0c6c71f1 100644
--- a/chrome/test/chromedriver/chrome/web_view_impl.cc
+++ b/chrome/test/chromedriver/chrome/web_view_impl.cc
@@ -442,6 +442,11 @@
       ->AttachTo(static_cast<DevToolsClientImpl*>(parent));
 }
 
+Status WebViewImpl::HandleEventsUntil(const ConditionalFunc& conditional_func,
+                                      const Timeout& timeout) {
+  return client_->HandleEventsUntil(conditional_func, timeout);
+}
+
 Status WebViewImpl::HandleReceivedEvents() {
   return client_->HandleReceivedEvents();
 }
diff --git a/chrome/test/chromedriver/chrome/web_view_impl.h b/chrome/test/chromedriver/chrome/web_view_impl.h
index ac21a312..61661f5 100644
--- a/chrome/test/chromedriver/chrome/web_view_impl.h
+++ b/chrome/test/chromedriver/chrome/web_view_impl.h
@@ -57,6 +57,8 @@
   bool WasCrashed() override;
   Status ConnectIfNecessary() override;
   Status AttachTo(DevToolsClient* parent);
+  Status HandleEventsUntil(const ConditionalFunc& conditional_func,
+                           const Timeout& timeout) override;
   Status HandleReceivedEvents() override;
   Status GetUrl(std::string* url) override;
   Status Load(const std::string& url, const Timeout* timeout) override;
diff --git a/chrome/test/chromedriver/client/chromedriver.py b/chrome/test/chromedriver/client/chromedriver.py
index d2af048..6c92488 100644
--- a/chrome/test/chromedriver/client/chromedriver.py
+++ b/chrome/test/chromedriver/client/chromedriver.py
@@ -13,63 +13,13 @@
 from webelement import WebElement
 from webshadowroot import WebShadowRoot
 from websocket_connection import WebSocketConnection
+from exceptions import *
 
 ELEMENT_KEY_W3C = "element-6066-11e4-a52e-4f735466cecf"
 ELEMENT_KEY = "ELEMENT"
 SHADOW_KEY = "shadow-6066-11e4-a52e-4f735466cecf"
 MAX_RETRY_COUNT = 5
 
-class ChromeDriverException(Exception):
-  pass
-class NoSuchElement(ChromeDriverException):
-  pass
-class NoSuchFrame(ChromeDriverException):
-  pass
-class UnknownCommand(ChromeDriverException):
-  pass
-class StaleElementReference(ChromeDriverException):
-  pass
-class ElementNotVisible(ChromeDriverException):
-  pass
-class InvalidElementState(ChromeDriverException):
-  pass
-class UnknownError(ChromeDriverException):
-  pass
-class JavaScriptError(ChromeDriverException):
-  pass
-class XPathLookupError(ChromeDriverException):
-  pass
-class Timeout(ChromeDriverException):
-  pass
-class NoSuchWindow(ChromeDriverException):
-  pass
-class InvalidCookieDomain(ChromeDriverException):
-  pass
-class ScriptTimeout(ChromeDriverException):
-  pass
-class InvalidSelector(ChromeDriverException):
-  pass
-class SessionNotCreated(ChromeDriverException):
-  pass
-class InvalidSessionId(ChromeDriverException):
-  pass
-class UnexpectedAlertOpen(ChromeDriverException):
-  pass
-class NoSuchAlert(ChromeDriverException):
-  pass
-class NoSuchCookie(ChromeDriverException):
-  pass
-class InvalidArgument(ChromeDriverException):
-  pass
-class ElementNotInteractable(ChromeDriverException):
-  pass
-class UnsupportedOperation(ChromeDriverException):
-  pass
-class NoSuchShadowRoot(ChromeDriverException):
-  pass
-class DetachedShadowRoot(ChromeDriverException):
-  pass
-
 def _ExceptionForLegacyResponse(response):
   exception_class_map = {
     6: InvalidSessionId,
@@ -788,4 +738,9 @@
     params = {'vendorId': vendorId}
     return self.ExecuteCommand(Command.GET_CAST_SINKS, params)
 
+  def __enter__(self):
+    return self
+
+  def __exit__(self, *args):
+    self.Quit()
 
diff --git a/chrome/test/chromedriver/client/exceptions.py b/chrome/test/chromedriver/client/exceptions.py
new file mode 100644
index 0000000..c275d88
--- /dev/null
+++ b/chrome/test/chromedriver/client/exceptions.py
@@ -0,0 +1,60 @@
+# Copyright 2022 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+class ChromeDriverException(Exception):
+  pass
+class NoSuchElement(ChromeDriverException):
+  pass
+class NoSuchFrame(ChromeDriverException):
+  pass
+class UnknownCommand(ChromeDriverException):
+  pass
+class StaleElementReference(ChromeDriverException):
+  pass
+class ElementNotVisible(ChromeDriverException):
+  pass
+class InvalidElementState(ChromeDriverException):
+  pass
+class UnknownError(ChromeDriverException):
+  pass
+class JavaScriptError(ChromeDriverException):
+  pass
+class XPathLookupError(ChromeDriverException):
+  pass
+class Timeout(ChromeDriverException):
+  pass
+class NoSuchWindow(ChromeDriverException):
+  pass
+class InvalidCookieDomain(ChromeDriverException):
+  pass
+class ScriptTimeout(ChromeDriverException):
+  pass
+class InvalidSelector(ChromeDriverException):
+  pass
+class SessionNotCreated(ChromeDriverException):
+  pass
+class InvalidSessionId(ChromeDriverException):
+  pass
+class UnexpectedAlertOpen(ChromeDriverException):
+  pass
+class NoSuchAlert(ChromeDriverException):
+  pass
+class NoSuchCookie(ChromeDriverException):
+  pass
+class InvalidArgument(ChromeDriverException):
+  pass
+class ElementNotInteractable(ChromeDriverException):
+  pass
+class UnsupportedOperation(ChromeDriverException):
+  pass
+class NoSuchShadowRoot(ChromeDriverException):
+  pass
+class DetachedShadowRoot(ChromeDriverException):
+  pass
+class WebSocketException(ChromeDriverException):
+  pass
+class WebSocketConnectionClosedException(WebSocketException):
+  pass
+class WebSocketTimeoutException(WebSocketException):
+  pass
\ No newline at end of file
diff --git a/chrome/test/chromedriver/client/websocket_connection.py b/chrome/test/chromedriver/client/websocket_connection.py
index 8db4369..87ff1517 100644
--- a/chrome/test/chromedriver/client/websocket_connection.py
+++ b/chrome/test/chromedriver/client/websocket_connection.py
@@ -2,9 +2,10 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import json
 import os
 import sys
-import json
+import time
 from command_executor import CommandExecutor
 
 _THIS_DIR = os.path.abspath(os.path.dirname(__file__))
@@ -17,6 +18,11 @@
                                'websocket-client'))
 import websocket
 
+from websocket import WebSocketConnectionClosedException as InternalWebSocketConnectionClosedException
+from websocket import WebSocketTimeoutException as InternalWebSocketTimeoutException
+from exceptions import WebSocketConnectionClosedException
+from exceptions import WebSocketTimeoutException
+
 class WebSocketCommands:
   CREATE_WEBSOCKET = \
     '/session/:sessionId'
@@ -27,19 +33,57 @@
   def __init__(self, server_url, session_id):
     self._server_url = server_url.replace('http', 'ws')
     self._session_id = session_id
-    self._command_id = -1
+    self._command_id = 0
     cmd_params = {'sessionId': session_id}
     path = CommandExecutor.CreatePath(
       WebSocketCommands.CREATE_WEBSOCKET, cmd_params)
     self._websocket = websocket.create_connection(self._server_url + path)
+    self._responses = {}
 
   def SendCommand(self, cmd_params):
+    self._command_id = self._command_id + 1
     cmd_params['id'] = self._command_id
-    self._command_id -= 1
-    self._websocket.send(json.dumps(cmd_params))
+    try:
+      self._websocket.send(json.dumps(cmd_params))
+    except InternalWebSocketTimeoutException:
+      raise WebSocketTimeoutException()
+    return self._command_id
 
-  def ReadMessage(self):
-    return self._websocket.recv()
+  def WaitForResponse(self, command_id):
+    if command_id in self._responses:
+      msg = self._responses[command_id]
+      del self._responses[command_id]
+      return msg
+
+    start = time.monotonic()
+    timeout = self.GetTimeout()
+
+    try:
+      while True:
+        msg = json.loads(self._websocket.recv())
+        if 'id' in msg:
+          if msg['id'] == command_id:
+            return msg
+          elif msg['id'] >= 0:
+            self._responses[msg['id']] = msg
+        if start + timeout <= time.monotonic():
+          raise TimeoutError()
+    except InternalWebSocketConnectionClosedException:
+      raise WebSocketConnectionClosedException()
+    except InternalWebSocketTimeoutException:
+      raise WebSocketTimeoutException()
 
   def Close(self):
     self._websocket.close();
+
+  def __enter__(self):
+    return self
+
+  def __exit__(self, *args):
+    self.Close()
+
+  def GetTimeout(self):
+    return self._websocket.gettimeout()
+
+  def SetTimeout(self, timeout_seconds):
+    self._websocket.settimeout(timeout_seconds)
diff --git a/chrome/test/chromedriver/commands.cc b/chrome/test/chromedriver/commands.cc
index 12b91bad..c0844c8 100644
--- a/chrome/test/chromedriver/commands.cc
+++ b/chrome/test/chromedriver/commands.cc
@@ -202,6 +202,7 @@
 
 void ExecuteSessionCommandOnSessionThread(
     const char* command_name,
+    const std::string& session_id,
     const SessionCommand& command,
     bool w3c_standard_command,
     bool return_ok_without_session,
@@ -217,7 +218,7 @@
         base::BindOnce(
             callback_on_cmd,
             Status(return_ok_without_session ? kOk : kInvalidSessionId),
-            std::unique_ptr<base::Value>(), std::string(), kW3CDefault));
+            std::unique_ptr<base::Value>(), session_id, kW3CDefault));
     return;
   }
 
@@ -317,6 +318,7 @@
                                 session->id, session->w3c_compliant));
 
   if (session->quit) {
+    session->CloseAllConnections();
     SetThreadLocalSession(std::unique_ptr<Session>());
     delete session;
     cmd_task_runner->PostTask(FROM_HERE, terminate_on_cmd);
@@ -342,8 +344,8 @@
     iter->second->thread()->task_runner()->PostTask(
         FROM_HERE,
         base::BindOnce(
-            &ExecuteSessionCommandOnSessionThread, command_name, command,
-            w3c_standard_command, return_ok_without_session,
+            &ExecuteSessionCommandOnSessionThread, command_name, session_id,
+            command, w3c_standard_command, return_ok_without_session,
             base::DictionaryValue::From(
                 base::Value::ToUniquePtrValue(params.Clone())),
             base::ThreadTaskRunnerHandle::Get(), callback,
diff --git a/chrome/test/chromedriver/embed_bidimapper_in_cpp.py b/chrome/test/chromedriver/embed_bidimapper_in_cpp.py
new file mode 100755
index 0000000..0b0a1a4
--- /dev/null
+++ b/chrome/test/chromedriver/embed_bidimapper_in_cpp.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Embeds standalone JavaScript snippets in C++ code.
+
+Each argument to the script must be a file containing an associated JavaScript
+function (e.g., evaluate_script.js should contain an evaluateScript function).
+This is called the exported function of the script. The entire script will be
+put into a C-style string in the form of an anonymous function which invokes
+the exported function when called.
+"""
+
+import optparse
+import os
+import sys
+
+import cpp_source
+
+
+def main():
+  parser = optparse.OptionParser()
+  parser.add_option(
+      '', '--directory', type='string', default='.',
+      help='Path to directory where the cc/h js file should be created')
+  options, args = parser.parse_args()
+
+  global_string_map = {}
+  for js_file in args:
+    base_name = os.path.basename(js_file)[:-3].title().replace('_', '')
+    func_name = base_name[0].lower() + base_name[1:]
+    script_name = 'k%sScript' % base_name
+    with open(js_file, 'r') as f:
+      contents = f.read()
+      global_string_map[script_name] = contents
+
+  cpp_source.WriteSource('bidimapper', 'chrome/test/chromedriver/bidimapper',
+                         options.directory, global_string_map)
+
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/chrome/test/chromedriver/server/chromedriver_server.cc b/chrome/test/chromedriver/server/chromedriver_server.cc
index 848b2ad5..cd4fae86 100644
--- a/chrome/test/chromedriver/server/chromedriver_server.cc
+++ b/chrome/test/chromedriver/server/chromedriver_server.cc
@@ -336,8 +336,8 @@
       "add readable timestamps to log",
       "enable-chrome-logs",
       "show logs from the browser (overrides other logging options)",
-// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
-// of lacros-chrome is complete.
+    // TODO(crbug.com/1052397): Revisit the macro expression once build flag
+    // switch of lacros-chrome is complete.
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
       "disable-dev-shm-usage",
       "do not use /dev/shm "
diff --git a/chrome/test/chromedriver/server/http_handler.cc b/chrome/test/chromedriver/server/http_handler.cc
index 31426db..b68cc8e0 100644
--- a/chrome/test/chromedriver/server/http_handler.cc
+++ b/chrome/test/chromedriver/server/http_handler.cc
@@ -11,6 +11,7 @@
 
 #include "base/bind.h"
 #include "base/callback.h"
+#include "base/callback_forward.h"
 #include "base/json/json_reader.h"
 #include "base/json/json_writer.h"
 #include "base/logging.h"  // For CHECK macros.
@@ -66,13 +67,82 @@
   return kW3CDefault;
 }
 
-net::HttpServerResponseInfo createWebSocketRejectResponse(
+net::HttpServerResponseInfo CreateWebSocketRejectResponse(
     net::HttpStatusCode code,
     const std::string& msg) {
   net::HttpServerResponseInfo response(code);
   response.AddHeader("X-WebSocket-Reject-Reason", msg);
   return response;
 }
+
+void SendWebSocketResponseOnCmdThread(
+    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
+    HttpServer* http_server,
+    int connection_id,
+    const std::string& data) {
+  io_task_runner->PostTask(
+      FROM_HERE,
+      base::BindOnce(&HttpServer::SendOverWebSocket,
+                     base::Unretained(http_server), connection_id, data));
+}
+
+void SendWebSocketResponseOnSessionThread(
+    scoped_refptr<base::SingleThreadTaskRunner> cmd_task_runner,
+    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
+    HttpServer* http_server,
+    int connection_id,
+    const std::string& data) {
+  cmd_task_runner->PostTask(
+      FROM_HERE,
+      base::BindOnce(&SendWebSocketResponseOnCmdThread, io_task_runner,
+                     base::Unretained(http_server), connection_id, data));
+}
+
+void CloseWebSocketOnCmdThread(
+    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
+    HttpServer* http_server,
+    int connection_id) {
+  io_task_runner->PostTask(
+      FROM_HERE, base::BindOnce(&HttpServer::Close,
+                                base::Unretained(http_server), connection_id));
+}
+
+void CloseWebSocketOnSessionThread(
+    scoped_refptr<base::SingleThreadTaskRunner> cmd_task_runner,
+    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
+    HttpServer* http_server,
+    int connection_id) {
+  cmd_task_runner->PostTask(
+      FROM_HERE, base::BindOnce(&CloseWebSocketOnCmdThread, io_task_runner,
+                                base::Unretained(http_server), connection_id));
+}
+
+void AddBidiConnectionOnSessionThread(int connection_id,
+                                      SendTextFunc send_response,
+                                      CloseFunc close_connection) {
+  Session* session = GetThreadLocalSession();
+  // session == nullptr is a valid case: ExecuteQuit has already been handled
+  // in the session thread but the following
+  // TerminateSessionThreadOnCommandThread has not yet been executed (the latter
+  // destroys the session thread) The connection has already been accepted by
+  // the CMD thread but soon it will be closed. We don't need to do anything.
+  if (session != nullptr) {
+    session->AddBidiConnection(connection_id, std::move(send_response),
+                               std::move(close_connection));
+  }
+}
+
+void RemoveBidiConnectionOnSessionThread(int connection_id) {
+  Session* session = GetThreadLocalSession();
+  // session == nullptr is a valid case: ExecuteQuit has already been handled
+  // in the session thread but the following
+  // TerminateSessionThreadOnCommandThread has not yet been executed (the latter
+  // destroys the session thread)
+  if (session != nullptr) {
+    session->RemoveBidiConnection(connection_id);
+  }
+}
+
 }  // namespace
 
 // WrapperURLLoaderFactory subclasses mojom::URLLoaderFactory as non-mojo, cross
@@ -152,6 +222,7 @@
     int adb_port)
     : quit_func_(quit_func),
       io_task_runner_(io_task_runner),
+      cmd_task_runner_(cmd_task_runner),
       url_base_(url_base),
       received_shutdown_(false) {
 #if BUILDFLAG(IS_MAC)
@@ -1398,20 +1469,143 @@
   } else {
     session_connection_map_[session_id] = connection_id;
     connection_session_map_[connection_id] = session_id;
-    io_task_runner_->PostTask(
-        FROM_HERE,
-        base::BindOnce(&HttpServer::AcceptWebSocket,
-                       base::Unretained(http_server), connection_id, info));
+
+    auto thread_it = session_thread_map_.find(session_id);
+    // check first that the session thread is still alive
+    if (thread_it != session_thread_map_.end()) {
+      auto send_response_from_seq = base::BindRepeating(
+          &SendWebSocketResponseOnSessionThread, cmd_task_runner_,
+          io_task_runner_, base::Unretained(http_server), connection_id);
+
+      auto close_from_seq = base::BindRepeating(
+          &CloseWebSocketOnSessionThread, cmd_task_runner_, io_task_runner_,
+          base::Unretained(http_server), connection_id);
+
+      thread_it->second->thread()->task_runner()->PostTask(
+          FROM_HERE,
+          base::BindOnce(&AddBidiConnectionOnSessionThread, connection_id,
+                         std::move(send_response_from_seq),
+                         std::move(close_from_seq)));
+
+      io_task_runner_->PostTask(
+          FROM_HERE,
+          base::BindOnce(&HttpServer::AcceptWebSocket,
+                         base::Unretained(http_server), connection_id, info));
+    } else {
+      std::string err_msg = "session not found session_id=" + session_id;
+      VLOG(0) << "HttpHandler WebSocketRequest error " << err_msg;
+      SendWebSocketRejectResponse(http_server, connection_id,
+                                  net::HTTP_BAD_REQUEST, err_msg);
+    }
   }
 }
 
+void HttpHandler::OnWebSocketMessage(HttpServer* http_server,
+                                     int connection_id,
+                                     const std::string& data) {
+  base::RepeatingCallback<void(const Status&)> send_error = base::BindRepeating(
+      [](HttpServer* http_server, int connection_id, const std::string& data,
+         scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
+         const Status& status) {
+        base::Value::Dict msg_dict;
+        msg_dict.Set("message", status.message());
+        msg_dict.Set("error", StatusCodeToString(status.code()));
+        absl::optional<base::Value> value =
+            base::JSONReader::Read(data, base::JSON_PARSE_CHROMIUM_EXTENSIONS);
+        if (value && value->is_dict()) {
+          absl::optional<int> msg_id = value->GetDict().FindInt("id");
+          if (msg_id) {
+            msg_dict.Set("id", *msg_id);
+          } else {
+            LOG(WARNING) << "BiDi command has no id";
+          }
+        } else {
+          LOG(WARNING) << "BiDi command is not a JSON map";
+        }
+        std::string error_message;
+        if (base::JSONWriter::Write(msg_dict, &error_message)) {
+          io_task_runner->PostTask(
+              FROM_HERE, base::BindOnce(&HttpServer::SendOverWebSocket,
+                                        base::Unretained(http_server),
+                                        connection_id, error_message));
+        } else {
+          LOG(WARNING) << "unable to serialize BiDi error message";
+        }
+      },
+      http_server, connection_id, data, io_task_runner_);
+
+  auto it = connection_session_map_.find(connection_id);
+  if (it == connection_session_map_.end()) {
+    // Session was terminated but the connection is not yet closed
+    send_error.Run(Status{kNoSuchFrame, "session not found"});
+    return;
+  }
+
+  std::string session_id = it->second;
+
+  base::DictionaryValue params;
+  params.Set("bidiCommand", std::make_unique<base::Value>(data));
+
+  auto callback = base::BindRepeating(
+      [](base::RepeatingCallback<void(const Status&)> send_error,
+         const Status& status, std::unique_ptr<base::Value>,
+         const std::string& session_id, bool) {
+        if (status.IsOk()) {
+          return;
+        }
+        switch (status.code()) {
+          case kInvalidSessionId: {
+            // ExecuteSessionCommandOnSessionThread can return this error status
+            send_error.Run(Status{
+                kNoSuchFrame, "session not found session_id=" + session_id});
+            break;
+          }
+          default: {
+            send_error.Run(status);
+            break;
+          }
+        }
+      },
+      send_error);
+  auto cmd = WrapToCommand("ExecuteBidiCommand",
+                           base::BindRepeating(&ExecuteBidiCommand));
+  cmd.Run(params, session_id, std::move(callback));
+}
+
+void HttpHandler::OnWebSocketResponseOnCmdThread(HttpServer* http_server,
+                                                 int connection_id,
+                                                 const std::string& data) {
+  io_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&HttpServer::SendOverWebSocket,
+                     base::Unretained(http_server), connection_id, data));
+}
+
+void HttpHandler::OnWebSocketResponseOnSessionThread(HttpServer* http_server,
+                                                     int connection_id,
+                                                     const std::string& data) {
+  cmd_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(&HttpHandler::OnWebSocketResponseOnCmdThread, WeakPtr(),
+                     base::Unretained(http_server), connection_id, data));
+}
+
 void HttpHandler::OnClose(HttpServer* http_server, int connection_id) {
   auto it = connection_session_map_.find(connection_id);
   if (it == connection_session_map_.end()) {
     return;
   }
-  session_connection_map_[it->second] = -1;
+  std::string session_id = it->second;
+  session_connection_map_[session_id] = -1;
   connection_session_map_.erase(it);
+
+  auto thread_it = session_thread_map_.find(session_id);
+  // check first that the session thread is still alive
+  if (thread_it != session_thread_map_.end()) {
+    thread_it->second->thread()->task_runner()->PostTask(
+        FROM_HERE,
+        base::BindOnce(&RemoveBidiConnectionOnSessionThread, connection_id));
+  }
 }
 
 void HttpHandler::SendWebSocketRejectResponse(HttpServer* http_server,
@@ -1422,7 +1616,7 @@
       FROM_HERE,
       base::BindOnce(&HttpServer::SendResponse, base::Unretained(http_server),
                      connection_id,
-                     createWebSocketRejectResponse(net::HTTP_BAD_REQUEST, msg),
+                     CreateWebSocketRejectResponse(net::HTTP_BAD_REQUEST, msg),
                      TRAFFIC_ANNOTATION_FOR_TESTS));
 }
 
diff --git a/chrome/test/chromedriver/server/http_handler.h b/chrome/test/chromedriver/server/http_handler.h
index fa7aa05..9b18d0b 100644
--- a/chrome/test/chromedriver/server/http_handler.h
+++ b/chrome/test/chromedriver/server/http_handler.h
@@ -135,6 +135,18 @@
                           int connection_id,
                           const net::HttpServerRequestInfo& info);
 
+  void OnWebSocketMessage(HttpServer* http_server,
+                          int connection_id,
+                          const std::string& data);
+
+  void OnWebSocketResponseOnCmdThread(HttpServer* http_server,
+                                      int connection_id,
+                                      const std::string& data);
+
+  void OnWebSocketResponseOnSessionThread(HttpServer* http_server,
+                                          int connection_id,
+                                          const std::string& data);
+
   void OnClose(HttpServer* http_server, int connection_id);
 
   void SendWebSocketRejectResponse(HttpServer* http_server,
@@ -145,6 +157,7 @@
   base::ThreadChecker thread_checker_;
   base::RepeatingClosure quit_func_;
   scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
+  scoped_refptr<base::SingleThreadTaskRunner> cmd_task_runner_;
   std::string url_base_;
   bool received_shutdown_;
   scoped_refptr<URLRequestContextGetter> context_getter_;
diff --git a/chrome/test/chromedriver/server/http_server.cc b/chrome/test/chromedriver/server/http_server.cc
index deb8d9a7..999aeaf6 100644
--- a/chrome/test/chromedriver/server/http_server.cc
+++ b/chrome/test/chromedriver/server/http_server.cc
@@ -232,12 +232,9 @@
 }
 
 void HttpServer::OnWebSocketMessage(int connection_id, std::string data) {
-  // TODO: Make use of WebSocket data
-  VLOG(0) << "HttpServer::OnWebSocketMessage received: " << data;
-  // https://crbug.com/chromedriver/3974
-  // Response "not supported" to avoid WebSocket tests timeout.
-  server_->SendOverWebSocket(connection_id, "not supported",
-                             TRAFFIC_ANNOTATION_FOR_TESTS);
+  cmd_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&HttpHandler::OnWebSocketMessage, handler_,
+                                this, connection_id, data));
 }
 
 void HttpServer::OnClose(int connection_id) {
@@ -246,6 +243,14 @@
       base::BindOnce(&HttpHandler::OnClose, handler_, this, connection_id));
 }
 
+void HttpServer::Close(int connection_id) {
+  server_->Close(connection_id);
+}
+
+void HttpServer::SendOverWebSocket(int connection_id, const std::string& data) {
+  server_->SendOverWebSocket(connection_id, data, TRAFFIC_ANNOTATION_FOR_TESTS);
+}
+
 void HttpServer::AcceptWebSocket(int connection_id,
                                  const net::HttpServerRequestInfo& request) {
   server_->AcceptWebSocket(connection_id, request,
diff --git a/chrome/test/chromedriver/server/http_server.h b/chrome/test/chromedriver/server/http_server.h
index 01afb1d..1f9541a 100644
--- a/chrome/test/chromedriver/server/http_server.h
+++ b/chrome/test/chromedriver/server/http_server.h
@@ -44,9 +44,13 @@
 
   void OnClose(int connection_id) override;
 
+  void Close(int connection_id);
+
   void AcceptWebSocket(int connection_id,
                        const net::HttpServerRequestInfo& request);
 
+  void SendOverWebSocket(int connection_id, const std::string& data);
+
   void SendResponse(int connection_id,
                     const net::HttpServerResponseInfo& response,
                     const net::NetworkTrafficAnnotationTag& traffic_annotation);
diff --git a/chrome/test/chromedriver/session.cc b/chrome/test/chromedriver/session.cc
index d9a1cae..93346fa 100644
--- a/chrome/test/chromedriver/session.cc
+++ b/chrome/test/chromedriver/session.cc
@@ -7,7 +7,9 @@
 #include <list>
 #include <utility>
 
+#include "base/json/json_reader.h"
 #include "base/lazy_instance.h"
+#include "base/logging.h"
 #include "base/threading/thread_local.h"
 #include "base/values.h"
 #include "chrome/test/chromedriver/chrome/chrome.h"
@@ -51,10 +53,24 @@
 
 InputCancelListEntry::~InputCancelListEntry() = default;
 
+BidiConnection::BidiConnection(int connection_id,
+                               SendTextFunc send_response,
+                               CloseFunc close_connection)
+    : connection_id(connection_id),
+      send_response(std::move(send_response)),
+      close_connection(std::move(close_connection)) {}
+
+BidiConnection::BidiConnection(BidiConnection&& other) = default;
+
+BidiConnection::~BidiConnection() = default;
+
+BidiConnection& BidiConnection::operator=(BidiConnection&& other) = default;
+
 // The default timeout values came from W3C spec.
 const base::TimeDelta Session::kDefaultImplicitWaitTimeout = base::Seconds(0);
 const base::TimeDelta Session::kDefaultPageLoadTimeout = base::Seconds(300);
 const base::TimeDelta Session::kDefaultScriptTimeout = base::Seconds(30);
+const int kBidiQueueCapacity = 20;
 
 Session::Session(const std::string& id)
     : id(id),
@@ -142,6 +158,85 @@
   }
 }
 
+void Session::OnBidiResponse(const std::string& payload) {
+  if (payload == "{\"launched\":true}") {
+    // TODO(chromedriver:4180): Prohibit any user command handling before we
+    // receive the "launched" event from the BiDiMapper.
+    return;
+  }
+
+  // If there is no active bidi connections the events will be accumulated.
+  bidi_response_queue_.push(payload);
+  for (; bidi_response_queue_.size() > kBidiQueueCapacity;
+       bidi_response_queue_.pop()) {
+    LOG(WARNING) << "BiDi response queue overflow, dropping the message: "
+                 << bidi_response_queue_.front();
+  }
+  ProcessBidiResponseQueue();
+}
+
+void Session::AddBidiConnection(int connection_id,
+                                SendTextFunc send_response,
+                                CloseFunc close_connection) {
+  bidi_connections_.emplace_back(connection_id, std::move(send_response),
+                                 std::move(close_connection));
+  ProcessBidiResponseQueue();
+}
+
+void Session::RemoveBidiConnection(int connection_id) {
+  // Reallistically we will not have many connections, therefore linear search
+  // is optimal.
+  auto it = std::find_if(bidi_connections_.begin(), bidi_connections_.end(),
+                         [connection_id](const auto& conn) {
+                           return conn.connection_id == connection_id;
+                         });
+  if (it != bidi_connections_.end()) {
+    bidi_connections_.erase(it);
+  }
+}
+
+void Session::ProcessBidiResponseQueue() {
+  if (bidi_connections_.empty() || bidi_response_queue_.empty()) {
+    return;
+  }
+  // Only single websocket connection is supported now.
+  DCHECK(bidi_connections_.size() == 1);
+  for (; !bidi_response_queue_.empty(); bidi_response_queue_.pop()) {
+    // TODO(chromedriver:4179): In the future we will support multiple
+    // connections. The payload will have to be parsed and routed to the
+    // appropriate connection. The events will have to be delivered to all
+    // connections.
+    for (const BidiConnection& conn : bidi_connections_) {
+      // If the callback fails (asynchronously) because the connection was
+      // broken we simply ignore this fact as the message cannot be delivered
+      // over that connection anyway.
+      std::string response = bidi_response_queue_.front();
+      conn.send_response.Run(response);
+      absl::optional<base::Value> responseParsed = base::JSONReader::Read(
+          response, base::JSON_PARSE_CHROMIUM_EXTENSIONS);
+      if (responseParsed && responseParsed->is_dict()) {
+        absl::optional<int> response_id =
+            responseParsed->GetDict().FindInt("id");
+        if (response_id && *response_id == awaited_bidi_response_id) {
+          VLOG(0) << "awaited response is received!";
+          awaited_bidi_response_id = -1;
+          // No "id" means that we are dealing with an event
+        }
+      } else {
+        LOG(WARNING) << "BiDi response is not a map";
+      }
+    }
+  }
+}
+
+void Session::CloseAllConnections() {
+  for (BidiConnection& conn : bidi_connections_) {
+    // If the callback fails (asynchronously) because the connection was
+    // terminated we simply ignore this - it is already closed.
+    conn.close_connection.Run();
+  }
+}
+
 Session* GetThreadLocalSession() {
   return lazy_tls_session.Pointer()->Get();
 }
diff --git a/chrome/test/chromedriver/session.h b/chrome/test/chromedriver/session.h
index 7a4bd6b..1a6f78e 100644
--- a/chrome/test/chromedriver/session.h
+++ b/chrome/test/chromedriver/session.h
@@ -7,9 +7,11 @@
 
 #include <list>
 #include <memory>
+#include <queue>
 #include <string>
 #include <vector>
 
+#include "base/callback.h"
 #include "base/memory/raw_ptr.h"
 #include "base/time/time.h"
 #include "base/values.h"
@@ -64,6 +66,23 @@
   std::unique_ptr<KeyEvent> key_event;
 };
 
+typedef base::RepeatingCallback<void(const std::string& /*payload*/)>
+    SendTextFunc;
+
+typedef base::RepeatingCallback<void()> CloseFunc;
+
+struct BidiConnection {
+  BidiConnection(int connection_id,
+                 SendTextFunc send_response,
+                 CloseFunc close_connection);
+  BidiConnection(BidiConnection&& other);
+  ~BidiConnection();
+  BidiConnection& operator=(BidiConnection&& other);
+  int connection_id;
+  SendTextFunc send_response;
+  CloseFunc close_connection;
+};
+
 struct Session {
   static const base::TimeDelta kDefaultImplicitWaitTimeout;
   static const base::TimeDelta kDefaultPageLoadTimeout;
@@ -83,11 +102,19 @@
   std::string GetCurrentFrameId() const;
   std::vector<WebDriverLog*> GetAllLogs() const;
 
+  void OnBidiResponse(const std::string& payload);
+  void AddBidiConnection(int connection_id,
+                         SendTextFunc send_response,
+                         CloseFunc close_connection);
+  void RemoveBidiConnection(int connection_id);
+  void CloseAllConnections();
+
   const std::string id;
   bool w3c_compliant;
   bool webSocketUrl = false;
   bool quit;
   bool detach;
+  int awaited_bidi_response_id = -1;
   std::unique_ptr<Chrome> chrome;
   std::string window;
   int sticky_modifiers;
@@ -135,6 +162,19 @@
 
  private:
   void SwitchFrameInternal(bool for_top_frame);
+  void ProcessBidiResponseQueue();
+
+  // TODO: for the moment being we support single connection per client
+  // In the future (2022Q4) we will probably support multiple bidi connections.
+  // In order to do that we can try either of the following approaches:
+  // * Create a separate CDP session per connection
+  // * Give some connection identifying context to the BiDiMapper.
+  //   The context will travel between the BiDiMapper and ChromeDriver.
+  // * Store an internal map between CDP command id and connection.
+  std::vector<BidiConnection> bidi_connections_;
+  // If there is no active connections the messages from Chrome are accumulated
+  // in this queue until a connection is created or the queue overflows.
+  std::queue<std::string> bidi_response_queue_;
 };
 
 Session* GetThreadLocalSession();
diff --git a/chrome/test/chromedriver/session_commands.cc b/chrome/test/chromedriver/session_commands.cc
index d0784a2..2e84853 100644
--- a/chrome/test/chromedriver/session_commands.cc
+++ b/chrome/test/chromedriver/session_commands.cc
@@ -6,11 +6,14 @@
 
 #include <list>
 #include <memory>
+#include <thread>
 #include <utility>
 
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/files/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
 #include "base/logging.h"  // For CHECK macros.
 #include "base/memory/ref_counted.h"
 #include "base/strings/string_util.h"
@@ -22,12 +25,16 @@
 #include "base/time/time.h"
 #include "base/values.h"
 #include "chrome/test/chromedriver/basic_types.h"
+#include "chrome/test/chromedriver/bidimapper/bidimapper.h"
 #include "chrome/test/chromedriver/capabilities.h"
+#include "chrome/test/chromedriver/chrome/bidi_tracker.h"
 #include "chrome/test/chromedriver/chrome/browser_info.h"
 #include "chrome/test/chromedriver/chrome/chrome.h"
 #include "chrome/test/chromedriver/chrome/chrome_android_impl.h"
 #include "chrome/test/chromedriver/chrome/chrome_desktop_impl.h"
+#include "chrome/test/chromedriver/chrome/chrome_impl.h"
 #include "chrome/test/chromedriver/chrome/device_manager.h"
+#include "chrome/test/chromedriver/chrome/devtools_client_impl.h"
 #include "chrome/test/chromedriver/chrome/devtools_event_listener.h"
 #include "chrome/test/chromedriver/chrome/geoposition.h"
 #include "chrome/test/chromedriver/chrome/javascript_dialog_manager.h"
@@ -254,7 +261,7 @@
 }
 
 Status CheckSessionCreated(Session* session) {
-  WebView* web_view = NULL;
+  WebView* web_view = nullptr;
   Status status = session->GetTargetWindow(&web_view);
   if (status.IsError())
     return Status(kSessionNotCreated, status);
@@ -307,11 +314,17 @@
   // |session| will own the |CommandListener|s.
   session->command_listeners.swap(command_listeners);
 
+  BidiTracker* bidi_tracker = new BidiTracker();
+  bidi_tracker->SetBidiCallback(
+      base::BindRepeating(&Session::OnBidiResponse, base::Unretained(session)));
+  devtools_event_listeners.emplace_back(bidi_tracker);
+
   status =
       LaunchChrome(bound_params.url_loader_factory, bound_params.socket_factory,
                    bound_params.device_manager, capabilities,
                    std::move(devtools_event_listeners), &session->chrome,
                    session->w3c_compliant);
+
   if (status.IsError())
     return status;
 
@@ -341,7 +354,77 @@
   } else {
     *value = base::Value::ToUniquePtrValue(session->capabilities->Clone());
   }
-  return CheckSessionCreated(session);
+
+  status = CheckSessionCreated(session);
+  if (status.IsError())
+    return status;
+
+  if (session->webSocketUrl) {
+    WebView* web_view = nullptr;
+    Status status = session->GetTargetWindow(&web_view);
+    if (status.IsError())
+      return status;
+    ChromeImpl* chrome = static_cast<ChromeImpl*>(session->chrome.get());
+    DevToolsClient* client = chrome->Client();
+
+    {
+      base::Value body(base::Value::Type::DICTIONARY);
+      body.SetStringKey("bindingName", "cdp");
+      body.SetStringKey("targetId", session->window);
+      client->SendCommandAndIgnoreResponse(
+          "Target.exposeDevToolsProtocol",
+          base::Value::AsDictionaryValue(body));
+    }
+
+    {
+      std::unique_ptr<base::Value> result;
+      base::Value body(base::Value::Type::DICTIONARY);
+      body.SetStringKey("name", "sendBidiResponse");
+      web_view->SendCommandAndGetResult(
+          "Runtime.addBinding", base::Value::AsDictionaryValue(body), &result);
+    }
+
+    status = EvaluateScriptAndIgnoreResult(session, kMapperScript);
+    if (status.IsError())
+      return status;
+
+    {
+      std::unique_ptr<base::Value> result;
+      base::Value body(base::Value::Type::DICTIONARY);
+      std::string window_id;
+      if (!base::JSONWriter::Write(session->window, &window_id)) {
+        return Status(kUnknownError,
+                      "cannot serialize be window id: " + session->window);
+      }
+      body.SetStringKey("expression",
+                        "window.setSelfTargetId(" + window_id + ")");
+      status = web_view->SendCommandAndGetResult(
+          "Runtime.evaluate", base::Value::AsDictionaryValue(body), &result);
+      if (status.IsError())
+        return status;
+    }
+
+    {
+      // Create a new tab because the default one is occupied by the BiDiMapper
+      std::string web_view_id;
+      status = session->chrome->NewWindow(
+          session->window, Chrome::WindowType::kTab, &web_view_id);
+
+      if (status.IsError())
+        return status;
+
+      std::string handle = WebViewIdToWindowHandle(web_view_id);
+
+      std::unique_ptr<base::Value> result;
+      base::Value body(base::Value::Type::DICTIONARY);
+      body.GetDict().Set("handle", handle);
+
+      status = ExecuteSwitchToWindow(
+          session, base::Value::AsDictionaryValue(body), &result);
+    }
+  }
+
+  return status;
 }
 
 }  // namespace
@@ -627,7 +710,7 @@
   Status status = InitSessionHelper(bound_params, session, params, value);
   if (status.IsError()) {
     session->quit = true;
-    if (session->chrome != NULL)
+    if (session->chrome != nullptr)
       session->chrome->Quit();
   } else if (session->webSocketUrl) {
     bound_params.cmd_task_runner->PostTask(
@@ -658,7 +741,7 @@
 Status ExecuteGetCurrentWindowHandle(Session* session,
                                      const base::DictionaryValue& params,
                                      std::unique_ptr<base::Value>* value) {
-  WebView* web_view = NULL;
+  WebView* web_view = nullptr;
   Status status = session->GetTargetWindow(&web_view);
   if (status.IsError())
     return status;
@@ -679,9 +762,12 @@
   if (status.IsError())
     return status;
   bool is_last_web_view = web_view_ids.size() == 1u;
+  if (session->webSocketUrl) {
+    is_last_web_view = web_view_ids.size() <= 2u;
+  }
   web_view_ids.clear();
 
-  WebView* web_view = NULL;
+  WebView* web_view = nullptr;
   status = session->GetTargetWindow(&web_view);
   if (status.IsError())
     return status;
@@ -725,16 +811,16 @@
   if (status.IsError())
     return status;
 
-  if (!is_last_web_view) {
-    status = ExecuteGetWindowHandles(session, base::DictionaryValue(), value);
-    if (status.IsError())
-      return status;
-  } else {
+  if (is_last_web_view) {
     // If there is only one window left, call quit as well.
     session->quit = true;
     status = session->chrome->Quit();
     if (status.IsOk())
       *value = std::make_unique<base::Value>(base::Value::Type::LIST);
+  } else {
+    status = ExecuteGetWindowHandles(session, base::DictionaryValue(), value);
+    if (status.IsError())
+      return status;
   }
 
   return status;
@@ -748,6 +834,20 @@
                                                  session->w3c_compliant);
   if (status.IsError())
     return status;
+
+  if (session->webSocketUrl) {
+    std::string mapper_view_id;
+    // TODO(chromedriver:4181): How do we know for sure that the first page is
+    // the mapper?
+    status = session->chrome->GetWebViewIdForFirstTab(&mapper_view_id,
+                                                      session->w3c_compliant);
+    auto it =
+        std::find(web_view_ids.begin(), web_view_ids.end(), mapper_view_id);
+    if (it != web_view_ids.end()) {
+      web_view_ids.erase(it);
+    }
+  }
+
   std::unique_ptr<base::Value> window_ids(
       new base::Value(base::Value::Type::LIST));
   for (std::list<std::string>::const_iterator it = web_view_ids.begin();
@@ -977,7 +1077,7 @@
 Status ExecuteIsLoading(Session* session,
                         const base::DictionaryValue& params,
                         std::unique_ptr<base::Value>* value) {
-  WebView* web_view = NULL;
+  WebView* web_view = nullptr;
   Status status = session->GetTargetWindow(&web_view);
   if (status.IsError())
     return status;
@@ -1330,3 +1430,106 @@
                                     value);
   return Status(kOk);
 }
+
+// Run a BiDi command
+Status ExecuteBidiCommand(Session* session,
+                          const base::DictionaryValue& params,
+                          std::unique_ptr<base::Value>* value) {
+  // session == nullptr is a valid case: ExecuteQuit has already been handled
+  // in the session thread but the following
+  // TerminateSessionThreadOnCommandThread has not yet been executed (the later
+  // destroys the session thread) The connection has already been accepted by
+  // the CMD thread but soon it will be closed. We don't need to do anything.
+  if (session == nullptr) {
+    return Status{kNoSuchFrame, "session not found"};
+  }
+  std::string data;
+  params.GetString("bidiCommand", &data);
+
+  std::string web_view_id;
+  Status status = session->chrome->GetWebViewIdForFirstTab(
+      &web_view_id, session->w3c_compliant);
+  if (status.IsError()) {
+    return status;
+  }
+  WebView* web_view = nullptr;
+  status = session->chrome->GetWebViewById(web_view_id, &web_view);
+  if (status.IsError()) {
+    return status;
+  }
+
+  absl::optional<base::Value> dataParsed =
+      base::JSONReader::Read(data, base::JSON_PARSE_CHROMIUM_EXTENSIONS);
+
+  if (!dataParsed) {
+    return Status(kUnknownError, "cannot parse the BiDi command: " + data);
+  }
+
+  if (!dataParsed->is_dict()) {
+    return Status(kUnknownError,
+                  "a JSON map is expected as a BiDi command: " + data);
+  }
+
+  absl::optional<int> cmd_id = dataParsed->GetDict().FindInt("id");
+  if (!cmd_id) {
+    return Status(kUnknownError, "BiDi command is missing 'id' field: " + data);
+  }
+
+  std::string* method = dataParsed->GetDict().FindString("method");
+  if (!method) {
+    return Status(kUnknownError,
+                  "BiDi command is missing 'method' field: " + data);
+  }
+
+  std::string msg;
+  if (!base::JSONWriter::Write(data, &msg)) {
+    return Status(kUnknownError, "cannot serialize be BiDi command: " + data);
+  }
+  std::string expression = "onBidiMessage(" + msg + ")";
+
+  if (*method == "browsingContext.close") {
+    // Closing of the context is handled in a blocking way.
+    // This simplifies us closing the browser if the last tab was closed.
+    session->awaited_bidi_response_id = *cmd_id;
+    status = web_view->EvaluateScript(std::string(), expression, false, value);
+    base::RepeatingCallback<Status(bool*)> bidi_response_is_received =
+        base::BindRepeating(
+            [](Session* session, int cmd_id, bool* condition_is_met) {
+              *condition_is_met = session->awaited_bidi_response_id != cmd_id;
+              return Status{kOk};
+            },
+            base::Unretained(session), *cmd_id);
+    if (status.IsError()) {
+      return status;
+    }
+
+    // The timeout is the same as in ChromeImpl::CloseTarget
+    status = web_view->HandleEventsUntil(std::move(bidi_response_is_received),
+                                         Timeout(base::Seconds(20)));
+    if (status.code() == kTimeout) {
+      // It looks like something is going wrong with the BiDiMapper.
+      // Terminating the session...
+      session->quit = true;
+      status = session->chrome->Quit();
+      return Status(kUnknownError, "failed to close window in 20 seconds");
+    }
+    if (status.IsError())
+      return status;
+
+    std::list<std::string> web_view_ids;
+    status =
+        session->chrome->GetWebViewIds(&web_view_ids, session->w3c_compliant);
+    if (status.IsError())
+      return status;
+
+    bool is_last_web_view = web_view_ids.size() <= 1u;
+    if (is_last_web_view) {
+      session->quit = true;
+      status = session->chrome->Quit();
+    }
+  } else {
+    status = web_view->EvaluateScript(std::string(), expression, false, value);
+  }
+
+  return status;
+}
diff --git a/chrome/test/chromedriver/session_commands.h b/chrome/test/chromedriver/session_commands.h
index 4ff91b5..eae7841 100644
--- a/chrome/test/chromedriver/session_commands.h
+++ b/chrome/test/chromedriver/session_commands.h
@@ -174,6 +174,11 @@
                           const base::DictionaryValue& params,
                           std::unique_ptr<base::Value>* value);
 
+// Run a BiDi command
+Status ExecuteBidiCommand(Session* session,
+                          const base::DictionaryValue& params,
+                          std::unique_ptr<base::Value>* value);
+
 namespace internal {
 Status ConfigureHeadlessSession(Session* session,
                                 const Capabilities& capabilities);
diff --git a/chrome/test/chromedriver/test/run_py_tests.py b/chrome/test/chromedriver/test/run_py_tests.py
index c17d68d..f17cf4b2 100755
--- a/chrome/test/chromedriver/test/run_py_tests.py
+++ b/chrome/test/chromedriver/test/run_py_tests.py
@@ -562,9 +562,10 @@
     self.assertEqual(driver.capabilities['webSocketUrl'],
         self.composeWebSocketUrl(_CHROMEDRIVER_SERVER_URL,
                                  driver.GetSessionId()))
+
     websocket = websocket_connection.WebSocketConnection(
         _CHROMEDRIVER_SERVER_URL, driver.GetSessionId())
-    self.assertNotEqual(None, websocket)
+    self.assertIsNotNone(websocket)
 
   def testWebSocketUrlInvalid(self):
     self.assertRaises(chromedriver.InvalidArgument,
@@ -578,15 +579,6 @@
     self.assertRaises(Exception, websocket_connection.WebSocketConnection,
                       _CHROMEDRIVER_SERVER_URL, driver.GetSessionId())
 
-  def testWebSocketCommandReturnsNotSupported(self):
-    driver = self.CreateDriver(web_socket_url=True)
-    websocket = websocket_connection.WebSocketConnection(
-        _CHROMEDRIVER_SERVER_URL, driver.GetSessionId())
-
-    websocket.SendCommand({"SOME": "COMMAND"})
-    message = websocket.ReadMessage()
-    self.assertEqual("not supported", message)
-
   def testWebSocketInvalidSessionId(self):
     driver = self.CreateDriver(web_socket_url=True)
     self.assertRaises(Exception, websocket_connection.WebSocketConnection,
@@ -5314,6 +5306,143 @@
     self.assertRaises(chromedriver.InvalidArgument,
                       self._driver.PrintPDF, {'pageRanges': ['x-y']})
 
+class BidiTest(ChromeDriverBaseTestWithWebServer):
+
+  def setUp(self):
+    self._driver = self.CreateDriver(web_socket_url = True)
+
+  def createWebSocketConnection(self, driver = None):
+    if driver is None:
+      driver = self._driver
+    conn = driver.CreateWebSocketConnection()
+    conn.SetTimeout(5 * 60) # 5 minutes
+    return conn
+
+  def testCreateContext(self):
+    conn = self.createWebSocketConnection()
+
+    old_handles = self._driver.GetWindowHandles()
+    self.assertEqual(1, len(old_handles))
+    self.assertNotEqual("BiDi Mapper", self._driver.GetTitle())
+
+    cmd_id = conn.SendCommand({
+      'method': 'browsingContext.create',
+      'params': {
+          'type': 'tab'
+      }
+    })
+    conn.WaitForResponse(cmd_id)
+    new_handles = self._driver.GetWindowHandles()
+    diff = set(new_handles) - set(old_handles)
+    self.assertEqual(1, len(diff))
+
+  def testGetBrowsingContextTree(self):
+    conn = self.createWebSocketConnection()
+
+    cmd_id = conn.SendCommand({
+      'method': 'browsingContext.getTree',
+      'params': {
+      }
+    })
+    resp = conn.WaitForResponse(cmd_id)
+    contexts = resp['result']['contexts']
+    self.assertEqual(1, len(contexts))
+
+  def testMapperIsNotDisplacedByNavigation(self):
+    self._http_server.SetDataForPath('/page.html',
+     bytes('<html><title>Regular Page</title></body></html>', 'utf-8'))
+
+    conn = self.createWebSocketConnection()
+    old_handles = self._driver.GetWindowHandles()
+
+    self._driver.Load(self._http_server.GetUrl() + '/page.html')
+    self.assertEqual("Regular Page", self._driver.GetTitle())
+
+    cmd_id = conn.SendCommand({
+      'method': 'browsingContext.create',
+      'params': {
+          'type': 'tab'
+      }
+    })
+    conn.WaitForResponse(cmd_id)
+    new_handles = self._driver.GetWindowHandles()
+    diff = set(new_handles) - set(old_handles)
+    self.assertEqual(1, len(diff))
+
+  def testBrowserQuitsWhenLastPageIsClosed(self):
+    conn = self.createWebSocketConnection()
+
+    handles = self._driver.GetWindowHandles()
+    self.assertEqual(1, len(handles))
+    self._driver.CloseWindow()
+
+    with self.assertRaises(chromedriver.WebSocketConnectionClosedException):
+      # BiDi messages cannot have negative "id".
+      # Wait indefinitely until time out.
+      conn.WaitForResponse(-1)
+
+  def testCloseOneOfManyPages(self):
+    conn = self.createWebSocketConnection()
+
+    cmd_id = conn.SendCommand({
+      'method': 'browsingContext.create',
+      'params': {
+          'type': 'tab'
+      }
+    })
+    conn.WaitForResponse(cmd_id)
+
+    handles = self._driver.GetWindowHandles()
+    self.assertEqual(2, len(handles))
+    self._driver.CloseWindow()
+
+    cmd_id = conn.SendCommand({
+      'method': 'browsingContext.getTree',
+      'params': {
+      }
+    })
+    resp = conn.WaitForResponse(cmd_id)
+    contexts = resp['result']['contexts']
+    self.assertEqual(1, len(contexts))
+
+  def testBrowserQuitsWhenLastBrowsingContextIsClosed(self):
+    conn = self.createWebSocketConnection()
+
+    cmd_id = conn.SendCommand({
+      'method': 'browsingContext.getTree',
+      'params': {
+      }
+    })
+    resp = conn.WaitForResponse(cmd_id)
+    contexts = resp['result']['contexts']
+
+    cmd_id = conn.SendCommand({
+      'method': 'browsingContext.close',
+      'params': {
+          'context': contexts[0]['context']
+      }
+    })
+    conn.WaitForResponse(cmd_id)
+
+    with self.assertRaises(chromedriver.WebSocketConnectionClosedException):
+      # BiDi messages cannot have negative "id".
+      # Wait indefinitely until time out.
+      conn.WaitForResponse(-1)
+
+
+  # TODO(nechaev): Test over tab switching by different means.
+
+class ClassiTest(ChromeDriverBaseTestWithWebServer):
+
+  def testAfterLastPage(self):
+    driver = self.CreateDriver(web_socket_url = False)
+
+    handles = driver.GetWindowHandles()
+    self.assertEqual(1, len(handles))
+    driver.CloseWindow()
+
+
+
 class SupportIPv4AndIPv6(ChromeDriverBaseTest):
   def testSupportIPv4AndIPv6(self):
     has_ipv4 = False
diff --git a/chrome/test/chromedriver/test/run_py_tests.pydeps b/chrome/test/chromedriver/test/run_py_tests.pydeps
index ae77f00..3703cfb9 100644
--- a/chrome/test/chromedriver/test/run_py_tests.pydeps
+++ b/chrome/test/chromedriver/test/run_py_tests.pydeps
@@ -82,6 +82,7 @@
 ../chrome_paths.py
 ../client/chromedriver.py
 ../client/command_executor.py
+../client/exceptions.py
 ../client/webelement.py
 ../client/webshadowroot.py
 ../client/websocket_connection.py
diff --git a/chrome/test/data/webui/cr_elements/cr_radio_group_test.ts b/chrome/test/data/webui/cr_elements/cr_radio_group_test.ts
index 7ff9a331..4efde5ef 100644
--- a/chrome/test/data/webui/cr_elements/cr_radio_group_test.ts
+++ b/chrome/test/data/webui/cr_elements/cr_radio_group_test.ts
@@ -3,11 +3,11 @@
 // found in the LICENSE file.
 
 // clang-format off
-import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.js';
 import 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
 
 import {CrRadioButtonElement} from 'chrome://resources/cr_elements/cr_radio_button/cr_radio_button.m.js';
-import {CrRadioGroupElement} from 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.m.js';
+import {CrRadioGroupElement} from 'chrome://resources/cr_elements/cr_radio_group/cr_radio_group.js';
 import {pressAndReleaseKeyOn} from 'chrome://resources/polymer/v3_0/iron-test-helpers/mock-interactions.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';
diff --git a/chrome/test/data/webui/mojo/mojo_file_system_access_browsertest.cc b/chrome/test/data/webui/mojo/mojo_file_system_access_browsertest.cc
index 19d37e2..a257330 100644
--- a/chrome/test/data/webui/mojo/mojo_file_system_access_browsertest.cc
+++ b/chrome/test/data/webui/mojo/mojo_file_system_access_browsertest.cc
@@ -9,7 +9,6 @@
 #include "base/test/bind.h"
 #include "base/threading/sequenced_task_runner_handle.h"
 #include "chrome/browser/bad_message.h"
-#include "chrome/browser/chrome_browser_interface_binders.h"
 #include "chrome/browser/chrome_content_browser_client.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
@@ -25,6 +24,7 @@
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_ui_controller_factory.h"
+#include "content/public/browser/web_ui_controller_interface_binder.h"
 #include "content/public/browser/web_ui_data_source.h"
 #include "content/public/common/content_client.h"
 #include "content/public/common/extra_mojo_js_features.mojom.h"
@@ -196,7 +196,7 @@
         mojo::BinderMapWithContext<content::RenderFrameHost*>* map) override {
       ChromeContentBrowserClient::RegisterBrowserInterfaceBindersForFrame(
           render_frame_host, map);
-      chrome::internal::RegisterWebUIControllerInterfaceBinder<
+      content::RegisterWebUIControllerInterfaceBinder<
           ::test::mojom::MojoFileSystemAccessTest, MojoFileSystemAccessUI>(map);
     }
   };
diff --git a/chrome/test/data/webui/mojo/mojo_web_ui_controller_browsertest.cc b/chrome/test/data/webui/mojo/mojo_web_ui_controller_browsertest.cc
index cda29a5..1ef35bdd 100644
--- a/chrome/test/data/webui/mojo/mojo_web_ui_controller_browsertest.cc
+++ b/chrome/test/data/webui/mojo/mojo_web_ui_controller_browsertest.cc
@@ -21,6 +21,7 @@
 #include "content/public/browser/render_process_host_observer.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_ui_controller_factory.h"
+#include "content/public/browser/web_ui_controller_interface_binder.h"
 #include "content/public/browser/web_ui_data_source.h"
 #include "content/public/common/content_client.h"
 #include "content/public/common/url_constants.h"
@@ -187,10 +188,10 @@
         mojo::BinderMapWithContext<content::RenderFrameHost*>* map) override {
       ChromeContentBrowserClient::RegisterBrowserInterfaceBindersForFrame(
           render_frame_host, map);
-      chrome::internal::RegisterWebUIControllerInterfaceBinder<
-          ::test::mojom::Bar, FooBarUI>(map);
-      chrome::internal::RegisterWebUIControllerInterfaceBinder<
-          ::test::mojom::Foo, FooUI, FooBarUI>(map);
+      content::RegisterWebUIControllerInterfaceBinder<::test::mojom::Bar,
+                                                      FooBarUI>(map);
+      content::RegisterWebUIControllerInterfaceBinder<::test::mojom::Foo, FooUI,
+                                                      FooBarUI>(map);
     }
   };
 
diff --git a/chrome/test/data/webui/settings/passwords_and_autofill_fake_data.ts b/chrome/test/data/webui/settings/passwords_and_autofill_fake_data.ts
index f7c8d14bc..8bd8696a 100644
--- a/chrome/test/data/webui/settings/passwords_and_autofill_fake_data.ts
+++ b/chrome/test/data/webui/settings/passwords_and_autofill_fake_data.ts
@@ -163,6 +163,7 @@
       isLocal: true,
       summaryLabel: card + ' ' +
           '****' + cardNumber.substr(-4),
+      summarySublabel: 'Jane Doe',
     },
   };
 }
diff --git a/chrome/test/data/webui/settings/payments_section_test.ts b/chrome/test/data/webui/settings/payments_section_test.ts
index 035c1387..d69422101b 100644
--- a/chrome/test/data/webui/settings/payments_section_test.ts
+++ b/chrome/test/data/webui/settings/payments_section_test.ts
@@ -43,6 +43,7 @@
     loadTimeData.overrideValues({
       migrationEnabled: true,
       virtualCardEnrollmentEnabled: true,
+      virtualCardMetadataEnabled: true,
     });
   });
 
@@ -207,7 +208,7 @@
     assertEquals(
         creditCard.metadata!.summaryLabel,
         rowShadowRoot.querySelector<HTMLElement>(
-                         '#creditCardLabel')!.textContent!.trim());
+                         '#summaryLabel')!.textContent!.trim());
     assertEquals(
         creditCard.expirationMonth + '/' + creditCard.expirationYear,
         rowShadowRoot.querySelector<HTMLElement>(
@@ -305,6 +306,77 @@
         assertFalse(!!outlinkButton);
       });
 
+  test('verifyCreditCardSummarySublabelWhenSublabelIsValid', function() {
+    const creditCard = createCreditCardEntry();
+    creditCard.metadata!.isLocal = false;
+    creditCard.metadata!.isVirtualCardEnrollmentEligible = false;
+    creditCard.metadata!.isVirtualCardEnrolled = false;
+    const section =
+        createPaymentsSection([creditCard], /*upiIds=*/[], /*prefValues=*/ {});
+
+    const creditCardList = section.$.paymentsList;
+    assertTrue(!!creditCardList);
+    assertEquals(1, getLocalAndServerCreditCardListItems().length);
+    assertFalse(getCardRowShadowRoot(section.$.paymentsList)
+                    .querySelector<HTMLElement>('#summarySublabel')!.hidden);
+  });
+
+  test('verifyCreditCardSummarySublabelWhenSublabelIsInvalid', function() {
+    const creditCard = createCreditCardEntry();
+    creditCard.metadata!.isLocal = false;
+    creditCard.metadata!.isVirtualCardEnrollmentEligible = false;
+    creditCard.metadata!.isVirtualCardEnrolled = false;
+    creditCard.metadata!.summarySublabel = '';
+    const section =
+        createPaymentsSection([creditCard], /*upiIds=*/[], /*prefValues=*/ {});
+
+    const creditCardList = section.$.paymentsList;
+    assertTrue(!!creditCardList);
+    assertEquals(1, getLocalAndServerCreditCardListItems().length);
+    assertTrue(getCardRowShadowRoot(section.$.paymentsList)
+                   .querySelector<HTMLElement>('#summarySublabel')!.hidden);
+  });
+
+  test('verifyCreditCardSummarySublabelWhenVirtualCardAvailable', function() {
+    const creditCard = createCreditCardEntry();
+    creditCard.metadata!.isLocal = false;
+    creditCard.metadata!.isVirtualCardEnrollmentEligible = true;
+    creditCard.metadata!.isVirtualCardEnrolled = false;
+    const section =
+        createPaymentsSection([creditCard], /*upiIds=*/[], /*prefValues=*/ {});
+
+    const creditCardList = section.$.paymentsList;
+    assertTrue(!!creditCardList);
+    assertEquals(1, getLocalAndServerCreditCardListItems().length);
+    assertFalse(getCardRowShadowRoot(section.$.paymentsList)
+                    .querySelector<HTMLElement>('#summarySublabel')!.hidden);
+    assertEquals(
+        'Virtual card available',
+        getCardRowShadowRoot(section.$.paymentsList)
+            .querySelector<HTMLElement>(
+                '#summarySublabel')!.textContent!.trim());
+  });
+
+  test('verifyCreditCardSummarySublabelWhenVirtualCardTurnedOn', function() {
+    const creditCard = createCreditCardEntry();
+    creditCard.metadata!.isLocal = false;
+    creditCard.metadata!.isVirtualCardEnrollmentEligible = false;
+    creditCard.metadata!.isVirtualCardEnrolled = true;
+    const section =
+        createPaymentsSection([creditCard], /*upiIds=*/[], /*prefValues=*/ {});
+
+    const creditCardList = section.$.paymentsList;
+    assertTrue(!!creditCardList);
+    assertEquals(1, getLocalAndServerCreditCardListItems().length);
+    assertFalse(getCardRowShadowRoot(section.$.paymentsList)
+                    .querySelector<HTMLElement>('#summarySublabel')!.hidden);
+    assertEquals(
+        'Virtual card turned on',
+        getCardRowShadowRoot(section.$.paymentsList)
+            .querySelector<HTMLElement>(
+                '#summarySublabel')!.textContent!.trim());
+  });
+
   test('verifyAddVsEditCreditCardTitle', function() {
     const newCreditCard = createEmptyCreditCardEntry();
     const newCreditCardDialog = createCreditCardDialog(newCreditCard);
diff --git a/chrome/test/data/webui/side_panel/read_anything/read_anything_app_test.ts b/chrome/test/data/webui/side_panel/read_anything/read_anything_app_test.ts
index 658b5c5..34ac1ad 100644
--- a/chrome/test/data/webui/side_panel/read_anything/read_anything_app_test.ts
+++ b/chrome/test/data/webui/side_panel/read_anything/read_anything_app_test.ts
@@ -20,7 +20,7 @@
     document.body.innerHTML = '';
     readAnythingApp = document.createElement('read-anything-app');
     document.body.appendChild(readAnythingApp);
-    chrome.readAnything.setThemeForTesting('default', 18.0);
+    chrome.readAnything.setThemeForTesting('default', 18.0, 0, 0);
   });
 
   function assertFontName(fontFamily: string) {
@@ -40,33 +40,50 @@
   }
 
   test('updateTheme fontName', () => {
-    chrome.readAnything.setThemeForTesting('Standard font', 18.0);
+    chrome.readAnything.setThemeForTesting('Standard font', 18.0, 0, 0);
     assertFontName('"Standard font"');
 
-    chrome.readAnything.setThemeForTesting('Sans-serif', 18.0);
+    chrome.readAnything.setThemeForTesting('Sans-serif', 18.0, 0, 0);
     assertFontName('sans-serif');
 
-    chrome.readAnything.setThemeForTesting('Serif', 18.0);
+    chrome.readAnything.setThemeForTesting('Serif', 18.0, 0, 0);
     assertFontName('serif');
 
-    chrome.readAnything.setThemeForTesting('Avenir', 18.0);
+    chrome.readAnything.setThemeForTesting('Avenir', 18.0, 0, 0);
     assertFontName('avenir');
 
-    chrome.readAnything.setThemeForTesting('Comic Neue', 18.0);
+    chrome.readAnything.setThemeForTesting('Comic Neue', 18.0, 0, 0);
     assertFontName('"Comic Neue"');
 
-    chrome.readAnything.setThemeForTesting('Comic Sans MS', 18.0);
+    chrome.readAnything.setThemeForTesting('Comic Sans MS', 18.0, 0, 0);
     assertFontName('"Comic Sans MS"');
 
-    chrome.readAnything.setThemeForTesting('Poppins', 18.0);
+    chrome.readAnything.setThemeForTesting('Poppins', 18.0, 0, 0);
     assertFontName('poppins');
   });
 
   test('updateTheme fontSize', () => {
-    chrome.readAnything.setThemeForTesting('Standard font', 27.0);
+    chrome.readAnything.setThemeForTesting('Standard font', 27.0, 0, 0);
     assertFontSize('27px');
   });
 
+  test('updateTheme foregroundColor', () => {
+    chrome.readAnything.setThemeForTesting(
+        'f', 1, /* SkColorSetRGB(0x33, 0x36, 0x39) = */ 4281546297, 0);
+    const container = readAnythingApp.shadowRoot!.getElementById('container');
+    assertEquals(
+        /* #333639 = */ 'rgb(51, 54, 57)', getComputedStyle(container!).color);
+  });
+
+  test('updateTheme backgroundColor', () => {
+    chrome.readAnything.setThemeForTesting(
+        'f', 1, 0, /* SkColorSetRGB(0xFD, 0xE2, 0x93) = */ 4294828691);
+    const container = readAnythingApp.shadowRoot!.getElementById('container');
+    assertEquals(
+        /* #FDE293 = */ 'rgb(253, 226, 147)',
+        getComputedStyle(container!).backgroundColor);
+  });
+
   test('updateContent paragraph', () => {
     // root id=1
     // ++paragraph id=2
diff --git a/chrome/test/media_router/media_router_integration_browsertest.cc b/chrome/test/media_router/media_router_integration_browsertest.cc
index 6a1bfe5..6be2e31 100644
--- a/chrome/test/media_router/media_router_integration_browsertest.cc
+++ b/chrome/test/media_router/media_router_integration_browsertest.cc
@@ -234,19 +234,17 @@
   // Read the test result, the test result set by javascript is a
   // JSON string with the following format:
   // {"passed": "<true/false>", "errorMessage": "<error_message>"}
-  std::unique_ptr<base::Value> value = base::JSONReader::ReadDeprecated(
-      result, base::JSON_ALLOW_TRAILING_COMMAS);
+  absl::optional<base::Value> value =
+      base::JSONReader::Read(result, base::JSON_ALLOW_TRAILING_COMMAS);
 
   // Convert to dictionary.
-  base::DictionaryValue* dict_value = nullptr;
-  ASSERT_TRUE(value->GetAsDictionary(&dict_value));
+  base::Value::Dict* dict_value = value->GetIfDict();
+  ASSERT_TRUE(dict_value);
 
   // Extract the fields.
-  std::string error_message;
-  ASSERT_TRUE(dict_value->GetString("errorMessage", &error_message));
-
-  ASSERT_THAT(dict_value->FindBoolKey("passed"), Optional(true))
-      << error_message;
+  const std::string* error_message = dict_value->FindString("errorMessage");
+  ASSERT_TRUE(error_message);
+  ASSERT_THAT(dict_value->FindBool("passed"), Optional(true)) << error_message;
 }
 
 void MediaRouterIntegrationBrowserTest::StartSessionAndAssertNotFoundError() {
diff --git a/chrome/updater/app/app_install.cc b/chrome/updater/app/app_install.cc
index 3f764f0..c202ae4 100644
--- a/chrome/updater/app/app_install.cc
+++ b/chrome/updater/app/app_install.cc
@@ -184,14 +184,18 @@
   // It's possible that a previous updater existed but is nonresponsive. In
   // this case, clear the active version in global prefs so that the system can
   // recover.
-  base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})
-      ->PostTaskAndReply(FROM_HERE,
-                         base::BindOnce(
-                             [](UpdaterScope scope) {
-                               CreateGlobalPrefs(scope)->SetActiveVersion("");
-                             },
-                             updater_scope()),
-                         base::BindOnce(&AppInstall::WakeCandidate, this));
+  base::ThreadPool::CreateSequencedTaskRunner(
+      {base::MayBlock(), base::WithBaseSyncPrimitives()})
+      ->PostTaskAndReply(
+          FROM_HERE,
+          base::BindOnce(
+              [](UpdaterScope scope) {
+                scoped_refptr<GlobalPrefs> prefs = CreateGlobalPrefs(scope);
+                prefs->SetActiveVersion("");
+                PrefsCommitPendingWrites(prefs->GetPrefService());
+              },
+              updater_scope()),
+          base::BindOnce(&AppInstall::WakeCandidate, this));
 }
 
 void AppInstall::WakeCandidate() {
diff --git a/chrome/updater/unittest_util_unittest.cc b/chrome/updater/unittest_util_unittest.cc
index 56d93e1..c4679f12 100644
--- a/chrome/updater/unittest_util_unittest.cc
+++ b/chrome/updater/unittest_util_unittest.cc
@@ -26,7 +26,9 @@
 TEST(UnitTestUtil, Processes) {
   // Test the state of the process for the unit test process itself.
   base::FilePath::StringType unit_test = GetUnitTestExecutableName();
-  EXPECT_TRUE(IsProcessRunning(unit_test));
+  // TODO(crbug.com/1352190) - remove process name insertion after the flakiness
+  // of the test is resolved.
+  EXPECT_TRUE(IsProcessRunning(unit_test)) << unit_test;
   EXPECT_FALSE(WaitForProcessesToExit(unit_test, base::Milliseconds(1)));
 
 #if BUILDFLAG(IS_WIN)
diff --git a/chrome/utility/safe_browsing/OWNERS b/chrome/utility/safe_browsing/OWNERS
index 70e4b68..5225674 100644
--- a/chrome/utility/safe_browsing/OWNERS
+++ b/chrome/utility/safe_browsing/OWNERS
@@ -1,3 +1 @@
-drubery@chromium.org
-nparker@chromium.org
-vakh@chromium.org
+file://components/safe_browsing/OWNERS
diff --git a/chromecast/browser/cast_permission_manager.cc b/chromecast/browser/cast_permission_manager.cc
index 8dc88916..1703f4c 100644
--- a/chromecast/browser/cast_permission_manager.cc
+++ b/chromecast/browser/cast_permission_manager.cc
@@ -174,6 +174,17 @@
   return GetPermissionStatusInternal(permission, requesting_origin);
 }
 
+content::PermissionResult
+CastPermissionManager::GetPermissionResultForOriginWithoutContext(
+    blink::PermissionType permission,
+    const url::Origin& origin) {
+  blink::mojom::PermissionStatus status =
+      GetPermissionStatus(permission, origin.GetURL(), origin.GetURL());
+
+  return content::PermissionResult(
+      status, content::PermissionStatusSource::UNSPECIFIED);
+}
+
 blink::mojom::PermissionStatus
 CastPermissionManager::GetPermissionStatusForCurrentDocument(
     blink::PermissionType permission,
diff --git a/chromecast/browser/cast_permission_manager.h b/chromecast/browser/cast_permission_manager.h
index 7fac9e2..be32fcf4 100644
--- a/chromecast/browser/cast_permission_manager.h
+++ b/chromecast/browser/cast_permission_manager.h
@@ -7,6 +7,7 @@
 
 #include "base/callback_forward.h"
 #include "content/public/browser/permission_controller_delegate.h"
+#include "content/public/browser/permission_result.h"
 #include "third_party/blink/public/common/permissions/permission_utils.h"
 #include "url/gurl.h"
 
@@ -56,6 +57,9 @@
       blink::PermissionType permission,
       const GURL& requesting_origin,
       const GURL& embedding_origin) override;
+  content::PermissionResult GetPermissionResultForOriginWithoutContext(
+      blink::PermissionType permission,
+      const url::Origin& origin) override;
   blink::mojom::PermissionStatus GetPermissionStatusForCurrentDocument(
       blink::PermissionType permission,
       content::RenderFrameHost* render_frame_host) override;
diff --git a/chromeos/ash/components/network/cellular_inhibitor.cc b/chromeos/ash/components/network/cellular_inhibitor.cc
index 0bb89dd..1b302d5 100644
--- a/chromeos/ash/components/network/cellular_inhibitor.cc
+++ b/chromeos/ash/components/network/cellular_inhibitor.cc
@@ -355,33 +355,33 @@
   return stream;
 }
 
-}  // namespace ash
-
 std::ostream& operator<<(
     std::ostream& stream,
-    const ash::CellularInhibitor::InhibitReason& inhibit_reason) {
+    const CellularInhibitor::InhibitReason& inhibit_reason) {
   switch (inhibit_reason) {
-    case ash::CellularInhibitor::InhibitReason::kInstallingProfile:
+    case CellularInhibitor::InhibitReason::kInstallingProfile:
       stream << "[Installing profile]";
       break;
-    case ash::CellularInhibitor::InhibitReason::kRenamingProfile:
+    case CellularInhibitor::InhibitReason::kRenamingProfile:
       stream << "[Renaming profile]";
       break;
-    case ash::CellularInhibitor::InhibitReason::kRemovingProfile:
+    case CellularInhibitor::InhibitReason::kRemovingProfile:
       stream << "[Removing profile]";
       break;
-    case ash::CellularInhibitor::InhibitReason::kConnectingToProfile:
+    case CellularInhibitor::InhibitReason::kConnectingToProfile:
       stream << "[Connecting to profile]";
       break;
-    case ash::CellularInhibitor::InhibitReason::kRefreshingProfileList:
+    case CellularInhibitor::InhibitReason::kRefreshingProfileList:
       stream << "[Refreshing profile list]";
       break;
-    case ash::CellularInhibitor::InhibitReason::kResettingEuiccMemory:
+    case CellularInhibitor::InhibitReason::kResettingEuiccMemory:
       stream << "[Resetting EUICC memory]";
       break;
-    case ash::CellularInhibitor::InhibitReason::kDisablingProfile:
+    case CellularInhibitor::InhibitReason::kDisablingProfile:
       stream << "[Disabling profile]";
       break;
   }
   return stream;
 }
+
+}  // namespace ash
diff --git a/chromeos/ash/components/network/cellular_inhibitor.h b/chromeos/ash/components/network/cellular_inhibitor.h
index c2d57ab..4af01624 100644
--- a/chromeos/ash/components/network/cellular_inhibitor.h
+++ b/chromeos/ash/components/network/cellular_inhibitor.h
@@ -76,6 +76,8 @@
     kResettingEuiccMemory,
     kDisablingProfile,
   };
+  friend std::ostream& operator<<(std::ostream& stream,
+                                  const InhibitReason& state);
 
   // Callback which returns InhibitLock on inhibit success or nullptr on
   // failure.
@@ -197,12 +199,16 @@
   base::WeakPtrFactory<CellularInhibitor> weak_ptr_factory_{this};
 };
 
-}  // namespace ash
+std::ostream& COMPONENT_EXPORT(CHROMEOS_NETWORK) operator<<(
+    std::ostream& stream,
+    const ash::CellularInhibitor::State& state);
 
-std::ostream& operator<<(
+std::ostream& COMPONENT_EXPORT(CHROMEOS_NETWORK) operator<<(
     std::ostream& stream,
     const ash::CellularInhibitor::InhibitReason& inhibit_reason);
 
+}  // namespace ash
+
 // TODO(https://crbug.com/1164001): remove when the migration is finished.
 namespace chromeos {
 using ::ash::CellularInhibitor;
diff --git a/chromeos/ash/components/network/network_state.cc b/chromeos/ash/components/network/network_state.cc
index 9fd4cc5..793fbe4c 100644
--- a/chromeos/ash/components/network/network_state.cc
+++ b/chromeos/ash/components/network/network_state.cc
@@ -667,7 +667,7 @@
                             shill_portal_state_);
   if (shill_portal_state_ != PortalState::kOnline) {
     NET_LOG(EVENT) << "Shill captive portal state for: " << NetworkId(this)
-                   << " = " << static_cast<int>(shill_portal_state_)
+                   << " = " << shill_portal_state_
                    << " ,status_code=" << status_code;
     base::UmaHistogramSparse("CaptivePortal.NetworkStateStatusCode",
                              std::abs(status_code));
@@ -687,4 +687,29 @@
   vpn_provider_->type = type;
 }
 
+std::ostream& operator<<(std::ostream& stream,
+                         const NetworkState::PortalState& portal_state) {
+  switch (portal_state) {
+    case NetworkState::PortalState::kUnknown:
+      stream << "Unknown";
+      break;
+    case NetworkState::PortalState::kOnline:
+      stream << "Online";
+      break;
+    case NetworkState::PortalState::kPortalSuspected:
+      stream << "PortalSuspected";
+      break;
+    case NetworkState::PortalState::kPortal:
+      stream << "Portal";
+      break;
+    case NetworkState::PortalState::kProxyAuthRequired:
+      stream << "ProxyAuthRequired";
+      break;
+    case NetworkState::PortalState::kNoInternet:
+      stream << "NoInternet";
+      break;
+  }
+  return stream;
+}
+
 }  // namespace ash
diff --git a/chromeos/ash/components/network/network_state.h b/chromeos/ash/components/network/network_state.h
index 5a82d1e..b511d84d 100644
--- a/chromeos/ash/components/network/network_state.h
+++ b/chromeos/ash/components/network/network_state.h
@@ -6,6 +6,7 @@
 #define CHROMEOS_ASH_COMPONENTS_NETWORK_NETWORK_STATE_H_
 
 #include <stdint.h>
+#include <sstream>
 
 #include <memory>
 #include <string>
@@ -69,6 +70,8 @@
     kNoInternet,
     kMaxValue = kNoInternet  // For UMA_HISTOGRAM_ENUMERATION
   };
+  friend std::ostream& operator<<(std::ostream& stream,
+                                  const PortalState& portal_state);
 
   // ManagedState overrides
   // If you change this method, update GetProperties too.
@@ -383,6 +386,10 @@
   bool connect_requested_ = false;
 };
 
+std::ostream& COMPONENT_EXPORT(CHROMEOS_NETWORK) operator<<(
+    std::ostream& stream,
+    const NetworkState::PortalState& portal_state);
+
 }  // namespace ash
 
 // TODO(https://crbug.com/1164001): remove when the migration is finished.
diff --git a/chromeos/ash/components/network/network_state_handler.cc b/chromeos/ash/components/network/network_state_handler.cc
index 15dd9197..60c246a3 100644
--- a/chromeos/ash/components/network/network_state_handler.cc
+++ b/chromeos/ash/components/network/network_state_handler.cc
@@ -567,6 +567,8 @@
   NetworkState* network = GetModifiableNetworkState(service_path);
   if (!network)
     return;
+  NET_LOG(USER) << "Setting Chrome PortalState for "
+                << NetworkPathId(service_path) << " = " << portal_state;
   auto prev_portal_state = network->GetPortalState();
   network->set_chrome_portal_state(portal_state);
   if (prev_portal_state == network->GetPortalState())
@@ -2075,6 +2077,8 @@
        default_network->proxy_config() != default_network_proxy_config_)) {
     default_network_portal_state_ = default_network->GetPortalState();
     default_network_proxy_config_ = default_network->proxy_config().Clone();
+    NET_LOG(EVENT) << "NOTIFY: PortalStateChanged: "
+                   << default_network_portal_state_;
     for (auto& observer : observers_) {
       observer.PortalStateChanged(default_network,
                                   default_network_portal_state_);
@@ -2084,6 +2088,7 @@
                                   !default_network_proxy_config_.is_none())) {
     default_network_portal_state_ = NetworkState::PortalState::kUnknown;
     default_network_proxy_config_ = base::Value();
+    NET_LOG(EVENT) << "NOTIFY: PortalStateChanged: Unknown (no network)";
     for (auto& observer : observers_)
       observer.PortalStateChanged(nullptr, NetworkState::PortalState::kUnknown);
   }
diff --git a/chromeos/ash/components/network/network_state_handler_unittest.cc b/chromeos/ash/components/network/network_state_handler_unittest.cc
index be0ba79..fb9de76f 100644
--- a/chromeos/ash/components/network/network_state_handler_unittest.cc
+++ b/chromeos/ash/components/network/network_state_handler_unittest.cc
@@ -144,7 +144,7 @@
                           NetworkState::PortalState portal_state) override {
     default_network_portal_state_ = portal_state;
     ++portal_state_change_count_;
-    VLOG(1) << "PortalStateChanged: " << static_cast<int>(portal_state);
+    VLOG(1) << "PortalStateChanged: " << portal_state;
   }
 
   void NetworkConnectionStateChanged(const NetworkState* network) override {
diff --git a/chromeos/components/cdm_factory_daemon/BUILD.gn b/chromeos/components/cdm_factory_daemon/BUILD.gn
index e5c5c65..34dcc0d4 100644
--- a/chromeos/components/cdm_factory_daemon/BUILD.gn
+++ b/chromeos/components/cdm_factory_daemon/BUILD.gn
@@ -52,6 +52,10 @@
     "chromeos_cdm_factory.h",
     "content_decryption_module_adapter.cc",
     "content_decryption_module_adapter.h",
+    "remote_cdm_context.cc",
+    "remote_cdm_context.h",
+    "stable_cdm_context_impl.cc",
+    "stable_cdm_context_impl.h",
   ]
   public_deps = [
     "//chromeos/components/cdm_factory_daemon/mojom",
@@ -59,6 +63,7 @@
   ]
   deps = [
     "//base",
+    "//media/mojo/mojom/stable:stable_video_decoder",
     "//mojo/public/cpp/bindings",
   ]
   defines = [ "IS_CDM_FACTORY_DAEMON_IMPL" ]
diff --git a/chromeos/components/cdm_factory_daemon/chromeos_cdm_context.h b/chromeos/components/cdm_factory_daemon/chromeos_cdm_context.h
index 75e2420..f297248 100644
--- a/chromeos/components/cdm_factory_daemon/chromeos_cdm_context.h
+++ b/chromeos/components/cdm_factory_daemon/chromeos_cdm_context.h
@@ -55,6 +55,11 @@
   // Returns true if this is coming from a CDM in ARC.
   virtual bool UsingArcCdm() const = 0;
 
+  // Returns true if this is coming from a remote CDM in another process or
+  // operating system. This is used to determine if certain processing has
+  // already been done on the data such as transcryption.
+  virtual bool IsRemoteCdm() const = 0;
+
  protected:
   virtual ~ChromeOsCdmContext() = default;
 };
diff --git a/chromeos/components/cdm_factory_daemon/chromeos_cdm_factory.cc b/chromeos/components/cdm_factory_daemon/chromeos_cdm_factory.cc
index 05ed5c7..146cd49 100644
--- a/chromeos/components/cdm_factory_daemon/chromeos_cdm_factory.cc
+++ b/chromeos/components/cdm_factory_daemon/chromeos_cdm_factory.cc
@@ -146,6 +146,7 @@
     return std::make_unique<SingletonCdmContextRef>(this);
   }
   bool UsingArcCdm() const override { return true; }
+  bool IsRemoteCdm() const override { return true; }
 
   // media::CdmContext implementation.
   ChromeOsCdmContext* GetChromeOsCdmContext() override { return this; }
diff --git a/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter.cc b/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter.cc
index 73198a9..1d5e76e 100644
--- a/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter.cc
+++ b/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter.cc
@@ -259,6 +259,10 @@
   return false;
 }
 
+bool ContentDecryptionModuleAdapter::IsRemoteCdm() const {
+  return false;
+}
+
 void ContentDecryptionModuleAdapter::OnSessionMessage(
     const std::string& session_id,
     media::CdmMessageType message_type,
diff --git a/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter.h b/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter.h
index 83e3534..edb1e0c 100644
--- a/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter.h
+++ b/chromeos/components/cdm_factory_daemon/content_decryption_module_adapter.h
@@ -108,6 +108,7 @@
   void GetScreenResolutions(GetScreenResolutionsCB callback) override;
   std::unique_ptr<media::CdmContextRef> GetCdmContextRef() override;
   bool UsingArcCdm() const override;
+  bool IsRemoteCdm() const override;
 
   // cdm::mojom::ContentDecryptionModuleClient:
   void OnSessionMessage(const std::string& session_id,
diff --git a/chromeos/components/cdm_factory_daemon/remote_cdm_context.cc b/chromeos/components/cdm_factory_daemon/remote_cdm_context.cc
new file mode 100644
index 0000000..ae4ddad
--- /dev/null
+++ b/chromeos/components/cdm_factory_daemon/remote_cdm_context.cc
@@ -0,0 +1,127 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/components/cdm_factory_daemon/remote_cdm_context.h"
+
+#include "base/callback.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/threading/thread_checker.h"
+#include "media/cdm/cdm_context_ref_impl.h"
+
+namespace chromeos {
+
+namespace {
+class RemoteCdmContextRef final : public media::CdmContextRef {
+ public:
+  explicit RemoteCdmContextRef(scoped_refptr<RemoteCdmContext> cdm_context)
+      : cdm_context_(std::move(cdm_context)) {}
+
+  RemoteCdmContextRef(const RemoteCdmContextRef&) = delete;
+  RemoteCdmContextRef& operator=(const RemoteCdmContextRef&) = delete;
+
+  ~RemoteCdmContextRef() final = default;
+
+  // CdmContextRef:
+  media::CdmContext* GetCdmContext() final { return cdm_context_.get(); }
+
+ private:
+  scoped_refptr<RemoteCdmContext> cdm_context_;
+};
+}  // namespace
+
+RemoteCdmContext::RemoteCdmContext(
+    mojo::PendingRemote<media::stable::mojom::StableCdmContext>
+        stable_cdm_context)
+    : stable_cdm_context_(std::move(stable_cdm_context)),
+      mojo_task_runner_(base::SequencedTaskRunnerHandle::Get()) {}
+
+std::unique_ptr<media::CallbackRegistration> RemoteCdmContext::RegisterEventCB(
+    EventCB event_cb) {
+  auto registration = event_callbacks_.Register(std::move(event_cb));
+  mojo_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&RemoteCdmContext::RegisterForRemoteCallbacks,
+                                weak_ptr_factory_.GetWeakPtr()));
+  return registration;
+}
+
+RemoteCdmContext::~RemoteCdmContext() {}
+
+void RemoteCdmContext::RegisterForRemoteCallbacks() {
+  if (!event_callback_receiver_.is_bound()) {
+    stable_cdm_context_->RegisterEventCallback(
+        event_callback_receiver_.BindNewPipeAndPassRemote());
+  }
+}
+
+ChromeOsCdmContext* RemoteCdmContext::GetChromeOsCdmContext() {
+  return this;
+}
+
+void RemoteCdmContext::GetHwKeyData(const media::DecryptConfig* decrypt_config,
+                                    const std::vector<uint8_t>& hw_identifier,
+                                    GetHwKeyDataCB callback) {
+  // Clone the |decrypt_config| in case the pointer becomes invalid when we are
+  // re-posting the task.
+  GetHwKeyDataInternal(decrypt_config->Clone(), hw_identifier,
+                       std::move(callback));
+}
+
+void RemoteCdmContext::GetHwKeyDataInternal(
+    std::unique_ptr<media::DecryptConfig> decrypt_config,
+    const std::vector<uint8_t>& hw_identifier,
+    GetHwKeyDataCB callback) {
+  // This can get called from decoder threads, so we may need to repost the
+  // task.
+  if (!mojo_task_runner_->RunsTasksInCurrentSequence()) {
+    mojo_task_runner_->PostTask(
+        FROM_HERE, base::BindOnce(&RemoteCdmContext::GetHwKeyDataInternal,
+                                  weak_ptr_factory_.GetWeakPtr(),
+                                  std::move(decrypt_config), hw_identifier,
+                                  std::move(callback)));
+    return;
+  }
+  stable_cdm_context_->GetHwKeyData(std::move(decrypt_config), hw_identifier,
+                                    std::move(callback));
+}
+
+void RemoteCdmContext::GetHwConfigData(GetHwConfigDataCB callback) {
+  stable_cdm_context_->GetHwConfigData(std::move(callback));
+}
+
+void RemoteCdmContext::GetScreenResolutions(GetScreenResolutionsCB callback) {
+  stable_cdm_context_->GetScreenResolutions(std::move(callback));
+}
+
+std::unique_ptr<media::CdmContextRef> RemoteCdmContext::GetCdmContextRef() {
+  return std::make_unique<RemoteCdmContextRef>(base::WrapRefCounted(this));
+}
+
+bool RemoteCdmContext::UsingArcCdm() const {
+  return false;
+}
+
+bool RemoteCdmContext::IsRemoteCdm() const {
+  return true;
+}
+
+void RemoteCdmContext::EventCallback(media::CdmContext::Event event) {
+  event_callbacks_.Notify(std::move(event));
+}
+
+void RemoteCdmContext::DeleteOnCorrectThread() const {
+  if (!mojo_task_runner_->RunsTasksInCurrentSequence()) {
+    // When DeleteSoon returns false, |this| will be leaked, which is okay.
+    mojo_task_runner_->DeleteSoon(FROM_HERE, this);
+  } else {
+    delete this;
+  }
+}
+
+// static
+void RemoteCdmContextTraits::Destruct(
+    const RemoteCdmContext* remote_cdm_context) {
+  remote_cdm_context->DeleteOnCorrectThread();
+}
+
+}  // namespace chromeos
diff --git a/chromeos/components/cdm_factory_daemon/remote_cdm_context.h b/chromeos/components/cdm_factory_daemon/remote_cdm_context.h
new file mode 100644
index 0000000..fb634c1
--- /dev/null
+++ b/chromeos/components/cdm_factory_daemon/remote_cdm_context.h
@@ -0,0 +1,96 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_COMPONENTS_CDM_FACTORY_DAEMON_REMOTE_CDM_CONTEXT_H_
+#define CHROMEOS_COMPONENTS_CDM_FACTORY_DAEMON_REMOTE_CDM_CONTEXT_H_
+
+#include <memory>
+
+#include "base/component_export.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/task/sequenced_task_runner.h"
+#include "chromeos/components/cdm_factory_daemon/chromeos_cdm_context.h"
+#include "media/base/callback_registry.h"
+#include "media/base/cdm_context.h"
+#include "media/mojo/mojom/stable/stable_video_decoder.mojom.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/remote.h"
+
+namespace chromeos {
+
+struct RemoteCdmContextTraits;
+
+// Provides the implementation that runs in out of process video decoding that
+// proxies the media::CdmContext calls back through a mojom::StableCdmContext
+// IPC connection.
+class COMPONENT_EXPORT(CDM_FACTORY_DAEMON) RemoteCdmContext
+    : public media::CdmContext,
+      public ChromeOsCdmContext,
+      public media::stable::mojom::CdmContextEventCallback,
+      public base::RefCountedThreadSafe<RemoteCdmContext,
+                                        RemoteCdmContextTraits> {
+ public:
+  explicit RemoteCdmContext(
+      mojo::PendingRemote<media::stable::mojom::StableCdmContext>
+          stable_cdm_context);
+
+  RemoteCdmContext(const RemoteCdmContext&) = delete;
+  RemoteCdmContext& operator=(const RemoteCdmContext&) = delete;
+
+  // media::CdmContext:
+  std::unique_ptr<media::CallbackRegistration> RegisterEventCB(
+      EventCB event_cb) override;
+  ChromeOsCdmContext* GetChromeOsCdmContext() override;
+
+  // chromeos::ChromeOsCdmContext:
+  void GetHwKeyData(const media::DecryptConfig* decrypt_config,
+                    const std::vector<uint8_t>& hw_identifier,
+                    GetHwKeyDataCB callback) override;
+  void GetHwConfigData(GetHwConfigDataCB callback) override;
+  void GetScreenResolutions(GetScreenResolutionsCB callback) override;
+  std::unique_ptr<media::CdmContextRef> GetCdmContextRef() override;
+  bool UsingArcCdm() const override;
+  bool IsRemoteCdm() const override;
+
+  // media::stable::mojom::CdmContextEventCallback:
+  void EventCallback(media::CdmContext::Event event) override;
+
+  // Deletes |this| on the correct thread.
+  void DeleteOnCorrectThread() const;
+
+ private:
+  // For DeleteSoon() in DeleteOnCorrectThread().
+  friend class base::DeleteHelper<RemoteCdmContext>;
+
+  ~RemoteCdmContext() override;
+
+  void RegisterForRemoteCallbacks();
+
+  void GetHwKeyDataInternal(
+      std::unique_ptr<media::DecryptConfig> decrypt_config,
+      const std::vector<uint8_t>& hw_identifier,
+      GetHwKeyDataCB callback);
+
+  mojo::Remote<media::stable::mojom::StableCdmContext> stable_cdm_context_;
+
+  scoped_refptr<base::SequencedTaskRunner> mojo_task_runner_;
+
+  mojo::Receiver<media::stable::mojom::CdmContextEventCallback>
+      event_callback_receiver_{this};
+
+  media::CallbackRegistry<EventCB::RunType> event_callbacks_;
+
+  // WeakPtrFactory to use for callbacks.
+  base::WeakPtrFactory<RemoteCdmContext> weak_ptr_factory_{this};
+};
+
+struct COMPONENT_EXPORT(CDM_FACTORY_DAEMON) RemoteCdmContextTraits {
+  // Destroys |remote_cdm_context| on the correct thread.
+  static void Destruct(const RemoteCdmContext* remote_cdm_context);
+};
+
+}  // namespace chromeos
+
+#endif  // CHROMEOS_COMPONENTS_CDM_FACTORY_DAEMON_REMOTE_CDM_CONTEXT_H_
diff --git a/chromeos/components/cdm_factory_daemon/stable_cdm_context_impl.cc b/chromeos/components/cdm_factory_daemon/stable_cdm_context_impl.cc
new file mode 100644
index 0000000..311afc5
--- /dev/null
+++ b/chromeos/components/cdm_factory_daemon/stable_cdm_context_impl.cc
@@ -0,0 +1,55 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/components/cdm_factory_daemon/stable_cdm_context_impl.h"
+
+#include "base/callback.h"
+#include "chromeos/components/cdm_factory_daemon/chromeos_cdm_context.h"
+#include "chromeos/components/cdm_factory_daemon/chromeos_cdm_factory.h"
+
+namespace chromeos {
+
+StableCdmContextImpl::StableCdmContextImpl(media::CdmContext* cdm_context)
+    : cdm_context_(cdm_context) {
+  DCHECK(cdm_context_);
+  DCHECK(cdm_context_->GetChromeOsCdmContext());
+  cdm_context_ref_ = cdm_context_->GetChromeOsCdmContext()->GetCdmContextRef();
+}
+
+StableCdmContextImpl::~StableCdmContextImpl() {}
+
+void StableCdmContextImpl::GetHwKeyData(
+    std::unique_ptr<media::DecryptConfig> decrypt_config,
+    const std::vector<uint8_t>& hw_identifier,
+    GetHwKeyDataCallback callback) {
+  cdm_context_->GetChromeOsCdmContext()->GetHwKeyData(
+      decrypt_config.get(), hw_identifier, std::move(callback));
+}
+
+void StableCdmContextImpl::RegisterEventCallback(
+    mojo::PendingRemote<media::stable::mojom::CdmContextEventCallback>
+        callback) {
+  remote_event_callbacks_.Add(std::move(callback));
+  if (!callback_registration_) {
+    callback_registration_ = cdm_context_->RegisterEventCB(
+        base::BindRepeating(&StableCdmContextImpl::CdmEventCallback,
+                            weak_ptr_factory_.GetWeakPtr()));
+  }
+}
+
+void StableCdmContextImpl::GetHwConfigData(GetHwConfigDataCallback callback) {
+  ChromeOsCdmFactory::GetHwConfigData(std::move(callback));
+}
+
+void StableCdmContextImpl::GetScreenResolutions(
+    GetScreenResolutionsCallback callback) {
+  ChromeOsCdmFactory::GetScreenResolutions(std::move(callback));
+}
+
+void StableCdmContextImpl::CdmEventCallback(media::CdmContext::Event event) {
+  for (auto& cb : remote_event_callbacks_)
+    cb->EventCallback(event);
+}
+
+}  // namespace chromeos
diff --git a/chromeos/components/cdm_factory_daemon/stable_cdm_context_impl.h b/chromeos/components/cdm_factory_daemon/stable_cdm_context_impl.h
new file mode 100644
index 0000000..50fd6a8
--- /dev/null
+++ b/chromeos/components/cdm_factory_daemon/stable_cdm_context_impl.h
@@ -0,0 +1,61 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_COMPONENTS_CDM_FACTORY_DAEMON_STABLE_CDM_CONTEXT_IMPL_H_
+#define CHROMEOS_COMPONENTS_CDM_FACTORY_DAEMON_STABLE_CDM_CONTEXT_IMPL_H_
+
+#include <memory>
+
+#include "base/component_export.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "media/base/callback_registry.h"
+#include "media/base/cdm_context.h"
+#include "media/mojo/mojom/stable/stable_video_decoder.mojom.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/remote_set.h"
+
+namespace chromeos {
+
+// Provides the receiving implementation for the CdmContext Mojo interface
+// used with out of process video decoding. This will run in the GPU process and
+// is used by the OOPVideoDecoder. The remote end of it will run in the video
+// decoder utility process launched from ash-chrome.
+class COMPONENT_EXPORT(CDM_FACTORY_DAEMON) StableCdmContextImpl
+    : public media::stable::mojom::StableCdmContext {
+ public:
+  explicit StableCdmContextImpl(media::CdmContext* cdm_context);
+
+  StableCdmContextImpl(const StableCdmContextImpl&) = delete;
+  StableCdmContextImpl& operator=(const StableCdmContextImpl&) = delete;
+
+  ~StableCdmContextImpl() override;
+
+  // media::stable::mojom::StableCdmContext:
+  void GetHwKeyData(std::unique_ptr<media::DecryptConfig> decrypt_config,
+                    const std::vector<uint8_t>& hw_identifier,
+                    GetHwKeyDataCallback callback) override;
+  void RegisterEventCallback(
+      mojo::PendingRemote<media::stable::mojom::CdmContextEventCallback>
+          callback) override;
+  void GetHwConfigData(GetHwConfigDataCallback callback) override;
+  void GetScreenResolutions(GetScreenResolutionsCallback callback) override;
+
+ private:
+  // Receives callbacks from the |cdm_context_| after we register with it.
+  void CdmEventCallback(media::CdmContext::Event event);
+
+  const raw_ptr<media::CdmContext> cdm_context_;
+  std::unique_ptr<media::CdmContextRef> cdm_context_ref_;
+  std::unique_ptr<media::CallbackRegistration> callback_registration_;
+  mojo::RemoteSet<media::stable::mojom::CdmContextEventCallback>
+      remote_event_callbacks_;
+
+  // WeakPtrFactory to use for callbacks.
+  base::WeakPtrFactory<StableCdmContextImpl> weak_ptr_factory_{this};
+};
+
+}  // namespace chromeos
+
+#endif  // CHROMEOS_COMPONENTS_CDM_FACTORY_DAEMON_STABLE_CDM_CONTEXT_IMPL_H_
diff --git a/chromeos/tast_control.gni b/chromeos/tast_control.gni
index 138fe19..79f55a0 100644
--- a/chromeos/tast_control.gni
+++ b/chromeos/tast_control.gni
@@ -265,6 +265,10 @@
 
   # https://crbug.com/1350880
   "arc.PIPRoundedCornersUnderlay",
+
+  # http://b/242172965
+  "crostini.SSHFSMount.buster_stable",
+  "crostini.SSHFSMount.bullseye_stable",
 ]
 
 # To create filters to be used on specific builders add them like this:
diff --git a/components/autofill/core/browser/BUILD.gn b/components/autofill/core/browser/BUILD.gn
index 18e22473..d7af9e7 100644
--- a/components/autofill/core/browser/BUILD.gn
+++ b/components/autofill/core/browser/BUILD.gn
@@ -187,6 +187,8 @@
     "form_parsing/field_candidates.h",
     "form_parsing/form_field.cc",
     "form_parsing/form_field.h",
+    "form_parsing/iban_field.cc",
+    "form_parsing/iban_field.h",
     "form_parsing/merchant_promo_code_field.cc",
     "form_parsing/merchant_promo_code_field.h",
     "form_parsing/name_field.cc",
@@ -819,6 +821,7 @@
     "form_parsing/credit_card_field_unittest.cc",
     "form_parsing/field_candidates_unittest.cc",
     "form_parsing/form_field_unittest.cc",
+    "form_parsing/iban_field_unittest.cc",
     "form_parsing/merchant_promo_code_field_unittest.cc",
     "form_parsing/name_field_unittest.cc",
     "form_parsing/parsing_test_utils.cc",
diff --git a/components/autofill/core/browser/autofill_field.cc b/components/autofill/core/browser/autofill_field.cc
index 757a5bae..abc1795 100644
--- a/components/autofill/core/browser/autofill_field.cc
+++ b/components/autofill/core/browser/autofill_field.cc
@@ -204,8 +204,14 @@
           (heuristic_type() == ADDRESS_HOME_STREET_NAME ||
            heuristic_type() == ADDRESS_HOME_HOUSE_NUMBER));
 
+    // For merchant promo code fields the heuristic predictions get precedence
+    // over the server predictions.
     believe_server =
-        believe_server && !(heuristic_type() == MERCHANT_PROMO_CODE);
+        believe_server && (heuristic_type() != MERCHANT_PROMO_CODE);
+
+    // For international bank account number (IBAN) fields the heuristic
+    // predictions get precedence over the server predictions.
+    believe_server = believe_server && (heuristic_type() != IBAN_VALUE);
 
     if (believe_server)
       return AutofillType(server_type());
diff --git a/components/autofill/core/browser/autofill_regex_constants.h b/components/autofill/core/browser/autofill_regex_constants.h
index c560393..73a92dc9 100644
--- a/components/autofill/core/browser/autofill_regex_constants.h
+++ b/components/autofill/core/browser/autofill_regex_constants.h
@@ -581,12 +581,17 @@
     u"yesbankltd"
     u")$";
 
-// Used to match field data that might be an International Bank Account Number.
+// Used to match the HTML name and label for International Bank Account Number
+// (IBAN).
+inline constexpr char16_t kIBANRe[] =
+    u"(\\biban(\\b|_)|international bank account number)";
+
+// Used to match field value that might be an International Bank Account Number.
 // TODO(crbug.com/977377): The regex doesn't match IBANs for Saint Lucia (LC),
 // Kazakhstan (KZ) and Romania (RO). Consider replace the regex with something
 // like "(?:IT|SM)\d{2}[A-Z]\d{22}|CY\d{2}[A-Z]\d{23}...". For reference:
 //    - https://www.swift.com/resource/iban-registry-pdf
-inline constexpr char16_t kInternationalBankAccountNumberRe[] =
+inline constexpr char16_t kInternationalBankAccountNumberValueRe[] =
     u"^[a-zA-Z]{2}[0-9]{2}[a-zA-Z0-9]{4}[0-9]{7}([a-zA-Z0-9]?){0,16}$";
 
 // Matches all 3 and 4 digit numbers.
diff --git a/components/autofill/core/browser/autofill_type.cc b/components/autofill/core/browser/autofill_type.cc
index 03b60e7..9153d9a 100644
--- a/components/autofill/core/browser/autofill_type.cc
+++ b/components/autofill/core/browser/autofill_type.cc
@@ -201,6 +201,9 @@
     case HTML_TYPE_MERCHANT_PROMO_CODE:
       return FieldTypeGroup::kNoGroup;
 
+    case HTML_TYPE_IBAN:
+      return FieldTypeGroup::kNoGroup;
+
     case HTML_TYPE_UNSPECIFIED:
     case HTML_TYPE_UNRECOGNIZED:
       return FieldTypeGroup::kNoGroup;
@@ -374,6 +377,7 @@
     case HTML_TYPE_TRANSACTION_CURRENCY:
     case HTML_TYPE_ONE_TIME_CODE:
     case HTML_TYPE_MERCHANT_PROMO_CODE:
+    case HTML_TYPE_IBAN:
       return UNKNOWN_TYPE;
 
     case HTML_TYPE_UNRECOGNIZED:
diff --git a/components/autofill/core/browser/data_model/credit_card.cc b/components/autofill/core/browser/data_model/credit_card.cc
index 2df69bc..94d167f7 100644
--- a/components/autofill/core/browser/data_model/credit_card.cc
+++ b/components/autofill/core/browser/data_model/credit_card.cc
@@ -866,9 +866,7 @@
   SetExpirationYear(num);
 }
 
-const std::pair<std::u16string, std::u16string> CreditCard::LabelPieces()
-    const {
-  std::u16string label;
+std::pair<std::u16string, std::u16string> CreditCard::LabelPieces() const {
   if (number().empty()) {
     // No CC number, if valid nickname is present, return nickname only.
     // Otherwise, return cardholder name only.
@@ -878,22 +876,16 @@
     return std::make_pair(name_on_card_, std::u16string());
   }
 
-  std::u16string obfuscated_cc_number =
-      CardIdentifierStringForAutofillDisplay();
-  // No expiration date set.
-  if (!expiration_month_ || !expiration_year_)
-    return std::make_pair(obfuscated_cc_number, std::u16string());
-
-  std::u16string formatted_date = ExpirationDateForDisplay();
-
-  std::u16string separator =
-      l10n_util::GetStringUTF16(IDS_AUTOFILL_ADDRESS_SUMMARY_SEPARATOR);
-  return std::make_pair(obfuscated_cc_number, separator + formatted_date);
+  return std::make_pair(CardIdentifierStringForAutofillDisplay(),
+                        name_on_card_);
 }
 
-const std::u16string CreditCard::Label() const {
+std::u16string CreditCard::Label() const {
   std::pair<std::u16string, std::u16string> pieces = LabelPieces();
-  return pieces.first + pieces.second;
+  if (pieces.first.empty() || pieces.second.empty())
+    return pieces.first + pieces.second;
+
+  return pieces.first + u", " + pieces.second;
 }
 
 std::u16string CreditCard::LastFourDigits() const {
diff --git a/components/autofill/core/browser/data_model/credit_card.h b/components/autofill/core/browser/data_model/credit_card.h
index eeb9dab..cb8daaf 100644
--- a/components/autofill/core/browser/data_model/credit_card.h
+++ b/components/autofill/core/browser/data_model/credit_card.h
@@ -275,11 +275,11 @@
 
   // Various display functions.
 
-  // Card preview summary, for example: "Nickname/Network - ****1234",
-  // ", 01/2020".
-  const std::pair<std::u16string, std::u16string> LabelPieces() const;
+  // Card preview summary, for example: "Nickname/Network - ****1234 John
+  // Smith".
+  std::pair<std::u16string, std::u16string> LabelPieces() const;
   // Like LabelPieces, but appends the two pieces together.
-  const std::u16string Label() const;
+  std::u16string Label() const;
   // The last four digits of the card number (or possibly less if there aren't
   // enough characters).
   std::u16string LastFourDigits() const;
diff --git a/components/autofill/core/browser/data_model/credit_card_unittest.cc b/components/autofill/core/browser/data_model/credit_card_unittest.cc
index ebfcc92..3e704144 100644
--- a/components/autofill/core/browser/data_model/credit_card_unittest.cc
+++ b/components/autofill/core/browser/data_model/credit_card_unittest.cc
@@ -103,81 +103,57 @@
 // Tests credit card summary string generation.  This test simulates a variety
 // of different possible summary strings.  Variations occur based on the
 // existence of credit card number, month, and year fields.
-TEST(CreditCardTest, PreviewSummaryAndNetworkAndLastFourDigitsStrings) {
+TEST(CreditCardTest, LabelSummary) {
   std::u16string valid_nickname = u"My Visa Card";
 
   // Case 0: empty credit card.
   CreditCard credit_card0(base::GenerateGUID(), "https://www.example.com/");
-  std::u16string summary0 = credit_card0.Label();
-  EXPECT_EQ(std::u16string(), summary0);
-  std::u16string obfuscated0 = credit_card0.NetworkAndLastFourDigits();
-  EXPECT_EQ(ASCIIToUTF16(std::string("Card")), obfuscated0);
+  EXPECT_EQ(std::u16string(), credit_card0.Label());
 
   // Case 00: Empty credit card with empty strings.
   CreditCard credit_card00(base::GenerateGUID(), "https://www.example.com/");
   test::SetCreditCardInfo(&credit_card00, "John Dillinger", "", "", "", "");
-  std::u16string summary00 = credit_card00.Label();
-  EXPECT_EQ(std::u16string(u"John Dillinger"), summary00);
-  std::u16string obfuscated00 = credit_card00.NetworkAndLastFourDigits();
-  EXPECT_EQ(ASCIIToUTF16(std::string("Card")), obfuscated00);
+  EXPECT_EQ(std::u16string(u"John Dillinger"), credit_card00.Label());
 
   // Case 1: No credit card number.
   CreditCard credit_card1(base::GenerateGUID(), "https://www.example.com/");
   test::SetCreditCardInfo(&credit_card1, "John Dillinger", "", "01", "2010",
                           "1");
-  std::u16string summary1 = credit_card1.Label();
-  EXPECT_EQ(std::u16string(u"John Dillinger"), summary1);
-  std::u16string obfuscated1 = credit_card1.NetworkAndLastFourDigits();
-  EXPECT_EQ(ASCIIToUTF16(std::string("Card")), obfuscated1);
+  EXPECT_EQ(std::u16string(u"John Dillinger"), credit_card1.Label());
 
   // Case 1.1: No credit card number, but has nickname.
   CreditCard credit_card11(base::GenerateGUID(), "https://www.example.com/");
   test::SetCreditCardInfo(&credit_card11, "John Dillinger", "", "01", "2010",
                           "1");
   credit_card11.SetNickname(valid_nickname);
-  std::u16string summary11 = credit_card11.Label();
-  EXPECT_EQ(valid_nickname, summary11);
-  std::u16string obfuscated11 = credit_card11.NetworkAndLastFourDigits();
-  EXPECT_EQ(ASCIIToUTF16(std::string("Card")), obfuscated11);
+  EXPECT_EQ(valid_nickname, credit_card11.Label());
 
   // Case 2: No month.
   CreditCard credit_card2(base::GenerateGUID(), "https://www.example.com/");
   test::SetCreditCardInfo(&credit_card2, "John Dillinger",
                           "5105 1051 0510 5100", "", "2010", "1");
-  std::u16string summary2 = credit_card2.Label();
   EXPECT_EQ(UTF8ToUTF16(std::string("Mastercard  ") +
-                        test::ObfuscatedCardDigitsAsUTF8("5100")),
-            summary2);
-  std::u16string obfuscated2 = credit_card2.NetworkAndLastFourDigits();
-  EXPECT_EQ(UTF8ToUTF16(std::string("Mastercard  ") +
-                        test::ObfuscatedCardDigitsAsUTF8("5100")),
-            obfuscated2);
+                        test::ObfuscatedCardDigitsAsUTF8("5100") +
+                        ", John Dillinger"),
+            credit_card2.Label());
 
   // Case 3: No year.
   CreditCard credit_card3(base::GenerateGUID(), "https://www.example.com/");
   test::SetCreditCardInfo(&credit_card3, "John Dillinger",
                           "5105 1051 0510 5100", "01", "", "1");
-  std::u16string summary3 = credit_card3.Label();
   EXPECT_EQ(UTF8ToUTF16(std::string("Mastercard  ") +
-                        test::ObfuscatedCardDigitsAsUTF8("5100")),
-            summary3);
-  std::u16string obfuscated3 = credit_card3.NetworkAndLastFourDigits();
-  EXPECT_EQ(UTF8ToUTF16(std::string("Mastercard  ") +
-                        test::ObfuscatedCardDigitsAsUTF8("5100")),
-            obfuscated3);
+                        test::ObfuscatedCardDigitsAsUTF8("5100") +
+                        ", John Dillinger"),
+            credit_card3.Label());
 
   // Case 4: Have everything except nickname.
   CreditCard credit_card4(base::GenerateGUID(), "https://www.example.com/");
   test::SetCreditCardInfo(&credit_card4, "John Dillinger",
                           "5105 1051 0510 5100", "01", "2010", "1");
-  std::u16string summary4 = credit_card4.Label();
   EXPECT_EQ(UTF8ToUTF16(std::string("Mastercard  ") +
-                        test::ObfuscatedCardDigitsAsUTF8("5100") + ", 01/2010"),
-            summary4);
-  std::u16string obfuscated4 = credit_card4.NetworkAndLastFourDigits();
-  EXPECT_EQ(UTF8ToUTF16(std::string("Mastercard  ") +
-                        test::ObfuscatedCardDigitsAsUTF8("5100")),
-            obfuscated4);
+                        test::ObfuscatedCardDigitsAsUTF8("5100") +
+                        ", John Dillinger"),
+            credit_card4.Label());
 
   // Case 5: Very long credit card
   CreditCard credit_card5(base::GenerateGUID(), "https://www.example.com/");
@@ -185,26 +161,94 @@
       &credit_card5, "John Dillinger",
       "0123456789 0123456789 0123456789 5105 1051 0510 5100", "01", "2010",
       "1");
-  std::u16string summary5 = credit_card5.Label();
   EXPECT_EQ(UTF8ToUTF16(std::string("Card  ") +
-                        test::ObfuscatedCardDigitsAsUTF8("5100") + ", 01/2010"),
-            summary5);
-  std::u16string obfuscated5 = credit_card5.NetworkAndLastFourDigits();
-  EXPECT_EQ(UTF8ToUTF16(std::string("Card  ") +
-                        test::ObfuscatedCardDigitsAsUTF8("5100")),
-            obfuscated5);
+                        test::ObfuscatedCardDigitsAsUTF8("5100") +
+                        ", John Dillinger"),
+            credit_card5.Label());
 
   // Case 6: Have everything including nickname.
   CreditCard credit_card6(base::GenerateGUID(), "https://www.example.com/");
   test::SetCreditCardInfo(&credit_card6, "John Dillinger",
                           "5105 1051 0510 5100", "01", "2010", "1");
   credit_card6.SetNickname(valid_nickname);
-  std::u16string summary6 = credit_card6.Label();
   EXPECT_EQ(
-      valid_nickname +
-          UTF8ToUTF16(std::string("  ") +
-                      test::ObfuscatedCardDigitsAsUTF8("5100") + ", 01/2010"),
-      summary6);
+      valid_nickname + UTF8ToUTF16(std::string("  ") +
+                                   test::ObfuscatedCardDigitsAsUTF8("5100") +
+                                   ", John Dillinger"),
+      credit_card6.Label());
+}
+
+TEST(CreditCardTest, NetworkAndLastFourDigits) {
+  std::u16string valid_nickname = u"My Visa Card";
+
+  // Case 0: empty credit card.
+  CreditCard credit_card0(base::GenerateGUID(), "https://www.example.com/");
+  EXPECT_EQ(ASCIIToUTF16(std::string("Card")),
+            credit_card0.NetworkAndLastFourDigits());
+
+  // Case 00: Empty credit card with empty strings.
+  CreditCard credit_card00(base::GenerateGUID(), "https://www.example.com/");
+  test::SetCreditCardInfo(&credit_card00, "John Dillinger", "", "", "", "");
+  EXPECT_EQ(ASCIIToUTF16(std::string("Card")),
+            credit_card00.NetworkAndLastFourDigits());
+
+  // Case 1: No credit card number.
+  CreditCard credit_card1(base::GenerateGUID(), "https://www.example.com/");
+  test::SetCreditCardInfo(&credit_card1, "John Dillinger", "", "01", "2010",
+                          "1");
+  EXPECT_EQ(ASCIIToUTF16(std::string("Card")),
+            credit_card1.NetworkAndLastFourDigits());
+
+  // Case 1.1: No credit card number, but has nickname.
+  CreditCard credit_card11(base::GenerateGUID(), "https://www.example.com/");
+  test::SetCreditCardInfo(&credit_card11, "John Dillinger", "", "01", "2010",
+                          "1");
+  credit_card11.SetNickname(valid_nickname);
+  EXPECT_EQ(ASCIIToUTF16(std::string("Card")),
+            credit_card11.NetworkAndLastFourDigits());
+
+  // Case 2: No month.
+  CreditCard credit_card2(base::GenerateGUID(), "https://www.example.com/");
+  test::SetCreditCardInfo(&credit_card2, "John Dillinger",
+                          "5105 1051 0510 5100", "", "2010", "1");
+  EXPECT_EQ(UTF8ToUTF16(std::string("Mastercard  ") +
+                        test::ObfuscatedCardDigitsAsUTF8("5100")),
+            credit_card2.NetworkAndLastFourDigits());
+
+  // Case 3: No year.
+  CreditCard credit_card3(base::GenerateGUID(), "https://www.example.com/");
+  test::SetCreditCardInfo(&credit_card3, "John Dillinger",
+                          "5105 1051 0510 5100", "01", "", "1");
+  EXPECT_EQ(UTF8ToUTF16(std::string("Mastercard  ") +
+                        test::ObfuscatedCardDigitsAsUTF8("5100")),
+            credit_card3.NetworkAndLastFourDigits());
+
+  // Case 4: Have everything except nickname.
+  CreditCard credit_card4(base::GenerateGUID(), "https://www.example.com/");
+  test::SetCreditCardInfo(&credit_card4, "John Dillinger",
+                          "5105 1051 0510 5100", "01", "2010", "1");
+  EXPECT_EQ(UTF8ToUTF16(std::string("Mastercard  ") +
+                        test::ObfuscatedCardDigitsAsUTF8("5100")),
+            credit_card4.NetworkAndLastFourDigits());
+
+  // Case 5: Very long credit card
+  CreditCard credit_card5(base::GenerateGUID(), "https://www.example.com/");
+  test::SetCreditCardInfo(
+      &credit_card5, "John Dillinger",
+      "0123456789 0123456789 0123456789 5105 1051 0510 5100", "01", "2010",
+      "1");
+  EXPECT_EQ(UTF8ToUTF16(std::string("Card  ") +
+                        test::ObfuscatedCardDigitsAsUTF8("5100")),
+            credit_card5.NetworkAndLastFourDigits());
+
+  // Case 6: Have everything including nickname.
+  CreditCard credit_card6(base::GenerateGUID(), "https://www.example.com/");
+  test::SetCreditCardInfo(&credit_card6, "John Dillinger",
+                          "5105 1051 0510 5100", "01", "2010", "1");
+  credit_card6.SetNickname(valid_nickname);
+  EXPECT_EQ(UTF8ToUTF16(std::string("Mastercard  ") +
+                        test::ObfuscatedCardDigitsAsUTF8("5100")),
+            credit_card6.NetworkAndLastFourDigits());
 }
 
 TEST(CreditCardTest, NicknameAndLastFourDigitsStrings) {
diff --git a/components/autofill/core/browser/field_types.cc b/components/autofill/core/browser/field_types.cc
index 20ee50f8e..537ba0a 100644
--- a/components/autofill/core/browser/field_types.cc
+++ b/components/autofill/core/browser/field_types.cc
@@ -101,9 +101,8 @@
     case UPI_VPA:
       return base::FeatureList::IsEnabled(features::kAutofillSaveAndFillVPA);
 
-    // TODO(crbug/1335549) to return true when the flag is enabled.
     case IBAN_VALUE:
-      return false;
+      return base::FeatureList::IsEnabled(features::kAutofillParseIBANFields);
 
     case COMPANY_NAME:
       return true;
@@ -425,6 +424,8 @@
       return "HTML_TYPE_ONE_TIME_CODE";
     case HTML_TYPE_MERCHANT_PROMO_CODE:
       return "HTML_TYPE_MERCHANT_PROMO_CODE";
+    case HTML_TYPE_IBAN:
+      return "HTML_TYPE_IBAN";
     case HTML_TYPE_UNRECOGNIZED:
       return "HTML_TYPE_UNRECOGNIZED";
   }
diff --git a/components/autofill/core/browser/field_types.h b/components/autofill/core/browser/field_types.h
index 496ab51..16ff815 100644
--- a/components/autofill/core/browser/field_types.h
+++ b/components/autofill/core/browser/field_types.h
@@ -239,7 +239,9 @@
   PHONE_HOME_NUMBER_PREFIX = 123,
   PHONE_HOME_NUMBER_SUFFIX = 124,
 
-  // IBAN data.
+  // International Bank Account Number (IBAN) details are usually entered on
+  // banking and merchant websites used to make international transactions.
+  // See https://en.wikipedia.org/wiki/International_Bank_Account_Number.
   IBAN_VALUE = 125,
   // No new types can be added without a corresponding change to the Autofill
   // server.
diff --git a/components/autofill/core/browser/form_parsing/form_field.cc b/components/autofill/core/browser/form_parsing/form_field.cc
index 1dbf810..e2ed3af3 100644
--- a/components/autofill/core/browser/form_parsing/form_field.cc
+++ b/components/autofill/core/browser/form_parsing/form_field.cc
@@ -26,6 +26,7 @@
 #include "components/autofill/core/browser/form_parsing/birthdate_field.h"
 #include "components/autofill/core/browser/form_parsing/credit_card_field.h"
 #include "components/autofill/core/browser/form_parsing/email_field.h"
+#include "components/autofill/core/browser/form_parsing/iban_field.h"
 #include "components/autofill/core/browser/form_parsing/merchant_promo_code_field.h"
 #include "components/autofill/core/browser/form_parsing/name_field.h"
 #include "components/autofill/core/browser/form_parsing/phone_field.h"
@@ -191,6 +192,12 @@
                         field_candidates, page_language, pattern_source,
                         log_manager);
   }
+
+  // IBAN pass.
+  if (base::FeatureList::IsEnabled(features::kAutofillParseIBANFields)) {
+    ParseFormFieldsPass(IBANField::Parse, processed_fields, field_candidates,
+                        page_language, pattern_source, log_manager);
+  }
 }
 
 // static
@@ -501,7 +508,7 @@
 
 // static
 bool FormField::IsSingleFieldParseableType(ServerFieldType field_type) {
-  return field_type == MERCHANT_PROMO_CODE;
+  return field_type == MERCHANT_PROMO_CODE || field_type == IBAN_VALUE;
 }
 
 // static
diff --git a/components/autofill/core/browser/form_parsing/form_field.h b/components/autofill/core/browser/form_parsing/form_field.h
index 0404742..6cd50974 100644
--- a/components/autofill/core/browser/form_parsing/form_field.h
+++ b/components/autofill/core/browser/form_parsing/form_field.h
@@ -93,13 +93,14 @@
   // Initial values assigned to FieldCandidates by their corresponding parsers.
   // There's an implicit precedence determined by the values assigned here.
   // Email is currently the most important followed by Phone, Travel, Address,
-  // Birthdate, Credit Card, Price, Name, Merchant promo code, and Search.
+  // Birthdate, Credit Card, IBAN, Price, Name, Merchant promo code, and Search.
   static constexpr float kBaseEmailParserScore = 1.4f;
   static constexpr float kBasePhoneParserScore = 1.3f;
   static constexpr float kBaseTravelParserScore = 1.2f;
   static constexpr float kBaseAddressParserScore = 1.1f;
   static constexpr float kBaseBirthdateParserScore = 1.05f;
   static constexpr float kBaseCreditCardParserScore = 1.0f;
+  static constexpr float kBaseIBANParserScore = 0.975f;
   static constexpr float kBasePriceParserScore = 0.95f;
   static constexpr float kBaseNameParserScore = 0.9f;
   static constexpr float kBaseMerchantPromoCodeParserScore = 0.85f;
diff --git a/components/autofill/core/browser/form_parsing/form_field_unittest.cc b/components/autofill/core/browser/form_parsing/form_field_unittest.cc
index 5292514..f6d7f86 100644
--- a/components/autofill/core/browser/form_parsing/form_field_unittest.cc
+++ b/components/autofill/core/browser/form_parsing/form_field_unittest.cc
@@ -211,7 +211,7 @@
 }
 
 // Test that `ParseSingleFieldForms` parses single field promo codes.
-TEST_P(FormFieldTest, ParseSingleFieldFormsPromoCode) {
+TEST_P(FormFieldTest, ParseFormFieldsForSingleFieldPromoCode) {
   base::test::ScopedFeatureList scoped_feature;
   scoped_feature.InitAndEnableFeature(
       features::kAutofillParseMerchantPromoCodeFields);
@@ -229,6 +229,24 @@
   TestClassificationExpectations();
 }
 
+// Test that `ParseSingleFieldForms` parses single field IBAN.
+TEST_P(FormFieldTest, ParseSingleFieldFormsIban) {
+  base::test::ScopedFeatureList scoped_feature;
+  scoped_feature.InitAndEnableFeature(features::kAutofillParseIBANFields);
+
+  // Parse single field IBAN.
+  AddTextFormFieldData("", "IBAN", IBAN_VALUE);
+  EXPECT_EQ(1, ParseSingleFieldForms());
+  TestClassificationExpectations();
+
+  // Don't parse other fields.
+  // UNKNOWN_TYPE is used as the expected type, which prevents it from being
+  // part of the expectations in `TestClassificationExpectations()`.
+  AddTextFormFieldData("", "Address line 1", UNKNOWN_TYPE);
+  EXPECT_EQ(1, ParseSingleFieldForms());
+  TestClassificationExpectations();
+}
+
 struct ParseInAnyOrderTestcase {
   // An nxn matrix, describing that field i is matched by parser j.
   std::vector<std::vector<bool>> field_matches_parser;
diff --git a/components/autofill/core/browser/form_parsing/iban_field.cc b/components/autofill/core/browser/form_parsing/iban_field.cc
new file mode 100644
index 0000000..4526a33
--- /dev/null
+++ b/components/autofill/core/browser/form_parsing/iban_field.cc
@@ -0,0 +1,42 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill/core/browser/form_parsing/iban_field.h"
+
+#include "components/autofill/core/browser/autofill_field.h"
+#include "components/autofill/core/browser/autofill_regex_constants.h"
+#include "components/autofill/core/browser/form_parsing/autofill_scanner.h"
+#include "components/autofill/core/common/autofill_payments_features.h"
+
+namespace autofill {
+
+// static
+std::unique_ptr<FormField> IBANField::Parse(AutofillScanner* scanner,
+                                            const LanguageCode& page_language,
+                                            PatternSource pattern_source,
+                                            LogManager* log_manager) {
+  if (!base::FeatureList::IsEnabled(features::kAutofillParseIBANFields))
+    return nullptr;
+
+  AutofillField* field;
+  base::span<const MatchPatternRef> iban_patterns =
+      GetMatchPatterns(IBAN_VALUE, page_language, pattern_source);
+
+  if (ParseFieldSpecifics(scanner, kIBANRe,
+                          kDefaultMatchParamsWith<MatchFieldType::kNumber,
+                                                  MatchFieldType::kTextArea>,
+                          iban_patterns, &field, {log_manager, "kIBANRe"})) {
+    return std::make_unique<IBANField>(field);
+  }
+
+  return nullptr;
+}
+
+IBANField::IBANField(const AutofillField* field) : field_(field) {}
+
+void IBANField::AddClassifications(FieldCandidatesMap& field_candidates) const {
+  AddClassification(field_, IBAN_VALUE, kBaseIBANParserScore, field_candidates);
+}
+
+}  // namespace autofill
diff --git a/components/autofill/core/browser/form_parsing/iban_field.h b/components/autofill/core/browser/form_parsing/iban_field.h
new file mode 100644
index 0000000..d8b4339
--- /dev/null
+++ b/components/autofill/core/browser/form_parsing/iban_field.h
@@ -0,0 +1,42 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_FORM_PARSING_IBAN_FIELD_H_
+#define COMPONENTS_AUTOFILL_CORE_BROWSER_FORM_PARSING_IBAN_FIELD_H_
+
+#include <memory>
+
+#include "base/memory/raw_ptr.h"
+#include "components/autofill/core/browser/form_parsing/form_field.h"
+#include "components/autofill/core/common/language_code.h"
+
+namespace autofill {
+
+class AutofillField;
+class AutofillScanner;
+class LogManager;
+
+// A form field that accepts International Bank Account Number (IBAN).
+class IBANField : public FormField {
+ public:
+  static std::unique_ptr<FormField> Parse(AutofillScanner* scanner,
+                                          const LanguageCode& page_language,
+                                          PatternSource pattern_source,
+                                          LogManager* log_manager);
+
+  explicit IBANField(const AutofillField* field);
+
+  IBANField(const IBANField&) = delete;
+  IBANField& operator=(const IBANField&) = delete;
+
+ protected:
+  void AddClassifications(FieldCandidatesMap& field_candidates) const override;
+
+ private:
+  raw_ptr<const AutofillField> field_;
+};
+
+}  // namespace autofill
+
+#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_FORM_PARSING_IBAN_FIELD_H_
diff --git a/components/autofill/core/browser/form_parsing/iban_field_unittest.cc b/components/autofill/core/browser/form_parsing/iban_field_unittest.cc
new file mode 100644
index 0000000..f5a39d1
--- /dev/null
+++ b/components/autofill/core/browser/form_parsing/iban_field_unittest.cc
@@ -0,0 +1,68 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill/core/browser/form_parsing/iban_field.h"
+
+#include "base/test/scoped_feature_list.h"
+#include "components/autofill/core/browser/form_parsing/parsing_test_utils.h"
+#include "components/autofill/core/common/autofill_payments_features.h"
+
+namespace autofill {
+
+class IBANFieldTest
+    : public FormFieldTestBase,
+      public testing::TestWithParam<PatternProviderFeatureState> {
+ public:
+  IBANFieldTest() : FormFieldTestBase(GetParam()) {}
+  IBANFieldTest(const IBANFieldTest&) = delete;
+  IBANFieldTest& operator=(const IBANFieldTest&) = delete;
+
+  void SetUp() override {
+    scoped_feature_list_.InitAndEnableFeature(
+        features::kAutofillParseIBANFields);
+  }
+
+ protected:
+  std::unique_ptr<FormField> Parse(
+      AutofillScanner* scanner,
+      const LanguageCode& page_language = LanguageCode("en")) override {
+    return IBANField::Parse(scanner, page_language, GetActivePatternSource(),
+                            /*log_manager=*/nullptr);
+  }
+
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+INSTANTIATE_TEST_SUITE_P(
+    IBANFieldTest,
+    IBANFieldTest,
+    ::testing::ValuesIn(PatternProviderFeatureState::All()));
+
+// Match IBAN
+TEST_P(IBANFieldTest, ParseIban) {
+  AddTextFormFieldData("iban-field", "Enter account number", IBAN_VALUE);
+
+  ClassifyAndVerify(ParseResult::PARSED);
+}
+
+TEST_P(IBANFieldTest, ParseIbanBanks) {
+  AddTextFormFieldData("accountNumber", "IBAN*", IBAN_VALUE);
+
+  ClassifyAndVerify(ParseResult::PARSED);
+}
+
+TEST_P(IBANFieldTest, ParseNonIban) {
+  AddTextFormFieldData("other-field", "Field for Account Number", UNKNOWN_TYPE);
+
+  ClassifyAndVerify(ParseResult::NOT_PARSED);
+}
+
+TEST_P(IBANFieldTest, ParseIbanFlagOff) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndDisableFeature(features::kAutofillParseIBANFields);
+  AddTextFormFieldData("iban-field", "Enter IBAN here", IBAN_VALUE);
+
+  ClassifyAndVerify(ParseResult::NOT_PARSED);
+}
+}  // namespace autofill
diff --git a/components/autofill/core/browser/form_parsing/resources/legacy_regex_patterns.json b/components/autofill/core/browser/form_parsing/resources/legacy_regex_patterns.json
index a4217b5a..d03eae03 100644
--- a/components/autofill/core/browser/form_parsing/resources/legacy_regex_patterns.json
+++ b/components/autofill/core/browser/form_parsing/resources/legacy_regex_patterns.json
@@ -3315,5 +3315,17 @@
         "match_field_input_types": [0]
       }
     ]
+  },
+    "IBAN_VALUE": {
+    "en": [
+      {
+        "pattern_identifier": "en_iban_preserving",
+        "positive_pattern": "(\\biban(\\b|_)|international bank account number)",
+        "positive_score": 0.975,
+        "negative_pattern": null,
+        "match_field_attributes": [0, 1],
+        "match_field_input_types": [0]
+      }
+    ]
   }
 }
diff --git a/components/autofill/core/browser/form_processing/autocomplete_attribute_processing_util.cc b/components/autofill/core/browser/form_processing/autocomplete_attribute_processing_util.cc
index 38c9829..7773dfb 100644
--- a/components/autofill/core/browser/form_processing/autocomplete_attribute_processing_util.cc
+++ b/components/autofill/core/browser/form_processing/autocomplete_attribute_processing_util.cc
@@ -156,6 +156,7 @@
           {"company", HTML_TYPE_ORGANIZATION},
           {"first-name", HTML_TYPE_GIVEN_NAME},
           {"gift-code", HTML_TYPE_MERCHANT_PROMO_CODE},
+          {"iban", HTML_TYPE_IBAN},
           {"locality", HTML_TYPE_ADDRESS_LEVEL2},
           {"promo-code", HTML_TYPE_MERCHANT_PROMO_CODE},
           {"promotional-code", HTML_TYPE_MERCHANT_PROMO_CODE},
diff --git a/components/autofill/core/browser/validation.cc b/components/autofill/core/browser/validation.cc
index 2e9eae6..d81a9b46 100644
--- a/components/autofill/core/browser/validation.cc
+++ b/components/autofill/core/browser/validation.cc
@@ -350,7 +350,7 @@
 bool IsInternationalBankAccountNumber(const std::u16string& value) {
   std::u16string no_spaces;
   base::RemoveChars(value, u" ", &no_spaces);
-  return MatchesRegex<kInternationalBankAccountNumberRe>(no_spaces);
+  return MatchesRegex<kInternationalBankAccountNumberValueRe>(no_spaces);
 }
 
 bool IsPlausibleCreditCardCVCNumber(const std::u16string& value) {
diff --git a/components/autofill/core/common/html_field_types.h b/components/autofill/core/common/html_field_types.h
index c3a7c31..0cfcdfe2 100644
--- a/components/autofill/core/common/html_field_types.h
+++ b/components/autofill/core/common/html_field_types.h
@@ -91,6 +91,9 @@
   // Promo code for merchant sites.
   HTML_TYPE_MERCHANT_PROMO_CODE,
 
+  // International Bank Account Number (IBAN) for banking and merchant sites.
+  HTML_TYPE_IBAN,
+
   // Non-standard autocomplete types.
   HTML_TYPE_UNRECOGNIZED,
 };
diff --git a/components/browser_ui/site_settings/android/website_preference_bridge.cc b/components/browser_ui/site_settings/android/website_preference_bridge.cc
index 40c23141..90f97ba 100644
--- a/components/browser_ui/site_settings/android/website_preference_bridge.cc
+++ b/components/browser_ui/site_settings/android/website_preference_bridge.cc
@@ -39,6 +39,7 @@
 #include "content/public/browser/android/browser_context_handle.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/permission_controller.h"
 #include "content/public/browser/storage_partition.h"
 #include "services/network/public/mojom/cookie_manager.mojom.h"
 #include "storage/browser/quota/quota_manager.h"
@@ -194,20 +195,39 @@
     ContentSettingsType content_type,
     jstring origin,
     jstring embedder) {
-  GURL url(ConvertJavaStringToUTF8(env, origin));
+  GURL requesting_origin(ConvertJavaStringToUTF8(env, origin));
   std::string embedder_str = ConvertJavaStringToUTF8(env, embedder);
-  GURL embedder_url;
+  GURL embedding_origin;
   // TODO(raymes): This check to see if '*' is the embedder is a hack that fixes
   // crbug.com/738377. In general querying the settings for patterns is broken
   // and needs to be fixed. See crbug.com/738757.
   if (embedder_str == "*")
-    embedder_url = url;
+    embedding_origin = requesting_origin;
   else
-    embedder_url = GURL(embedder_str);
-  return permissions::PermissionsClient::Get()
-      ->GetPermissionManager(unwrap(jbrowser_context_handle))
-      ->GetPermissionStatusDeprecated(content_type, url, embedder_url)
-      .content_setting;
+    embedding_origin = GURL(embedder_str);
+
+  // If `content_type` is permission, then we need to apply a set of
+  // verifications before reading its value in `HostContentSettingsMap`.
+  if (permissions::PermissionUtil::IsPermission(content_type)) {
+    BrowserContext* browser_context = unwrap(jbrowser_context_handle);
+    content::PermissionController* permission_controller =
+        browser_context->GetPermissionController();
+    blink::mojom::PermissionStatus status =
+        permission_controller->GetPermissionStatusForOriginWithoutContext(
+            permissions::PermissionUtil::ContentSettingTypeToPermissionType(
+                content_type),
+            url::Origin::Create(requesting_origin),
+            url::Origin::Create(embedding_origin));
+    return permissions::PermissionUtil::PermissionStatusToContentSetting(
+        status);
+  } else {
+    // If `content_type` is not permission, then we can directly read its value
+    // from `HostContentSettingsMap`.
+    HostContentSettingsMap* host_content_settings_map =
+        GetHostContentSettingsMap(jbrowser_context_handle);
+    return host_content_settings_map->GetContentSetting(
+        requesting_origin, embedding_origin, content_type);
+  }
 }
 
 void SetPermissionSettingForOrigin(
diff --git a/components/content_settings/android/cookie_controls_bridge.cc b/components/content_settings/android/cookie_controls_bridge.cc
index f1808bc2..a3cb1119 100644
--- a/components/content_settings/android/cookie_controls_bridge.cc
+++ b/components/content_settings/android/cookie_controls_bridge.cc
@@ -11,6 +11,7 @@
 #include "components/permissions/permissions_client.h"
 #include "content/public/browser/android/browser_context_handle.h"
 #include "content/public/browser/browser_context.h"
+#include "content/public/browser/web_contents.h"
 
 namespace content_settings {
 
diff --git a/components/content_settings/browser/page_specific_content_settings.cc b/components/content_settings/browser/page_specific_content_settings.cc
index fe1ef5b..123175b 100644
--- a/components/content_settings/browser/page_specific_content_settings.cc
+++ b/components/content_settings/browser/page_specific_content_settings.cc
@@ -34,12 +34,15 @@
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/cookie_access_details.h"
 #include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/navigation_handle_user_data.h"
 #include "content/public/browser/page.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/render_view_host.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_delegate.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_contents_user_data.h"
 #include "content/public/common/content_constants.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
@@ -59,39 +62,113 @@
          !navigation_handle->IsPrerenderedPageActivation();
 }
 
+// Keeps track of cookie and service worker access during a navigation.
+// These types of access can happen for the current page or for a new
+// navigation (think cookies sent in the HTTP request or service worker
+// being run to serve a fetch request). A navigation might fail to
+// commit in which case we have to handle it as if it had never
+// occurred. So we cache all cookies and service worker accesses that
+// happen during a navigation and only apply the changes if the
+// navigation commits.
+class InflightNavigationContentSettings
+    : public content::NavigationHandleUserData<
+          InflightNavigationContentSettings> {
+ public:
+  ~InflightNavigationContentSettings() override;
+  std::vector<content::CookieAccessDetails> cookie_accesses;
+  std::vector<std::pair<GURL, content::AllowServiceWorkerResult>>
+      service_worker_accesses;
+
+ private:
+  explicit InflightNavigationContentSettings(
+      content::NavigationHandle& navigation_handle);
+  friend class content::NavigationHandleUserData<
+      InflightNavigationContentSettings>;
+  NAVIGATION_HANDLE_USER_DATA_KEY_DECL();
+};
+
+// This class attaches to WebContents to listen to events and route them to
+// appropriate PageSpecificContentSettings, store navigation related events
+// until the navigation finishes and then transferring the
+// navigation-associated state to the newly-created page.
+class WebContentsHandler
+    : public content::WebContentsObserver,
+      public content::WebContentsUserData<WebContentsHandler> {
+ public:
+  using Delegate = PageSpecificContentSettings::Delegate;
+  using SiteDataObserver = PageSpecificContentSettings::SiteDataObserver;
+
+  explicit WebContentsHandler(content::WebContents* web_contents,
+                              std::unique_ptr<Delegate> delegate);
+  ~WebContentsHandler() override;
+  // Adds the given |SiteDataObserver|. The |observer| is notified when a
+  // locale shared object, like for example a cookie, is accessed.
+  void AddSiteDataObserver(SiteDataObserver* observer);
+
+  // Removes the given |SiteDataObserver|.
+  void RemoveSiteDataObserver(SiteDataObserver* observer);
+
+  // Notifies all registered |SiteDataObserver|s.
+  void NotifySiteDataObservers();
+
+  Delegate* delegate() { return delegate_.get(); }
+
+ private:
+  friend class content::WebContentsUserData<WebContentsHandler>;
+
+  // Applies all stored events for the given navigation to the current main
+  // document.
+  void TransferNavigationContentSettingsToCommittedDocument(
+      const InflightNavigationContentSettings& navigation_settings,
+      content::RenderFrameHost* rfh);
+
+  // content::WebContentsObserver overrides.
+  void ReadyToCommitNavigation(
+      content::NavigationHandle* navigation_handle) override;
+  void DidFinishNavigation(
+      content::NavigationHandle* navigation_handle) override;
+  void OnCookiesAccessed(content::NavigationHandle* navigation,
+                         const content::CookieAccessDetails& details) override;
+  void OnCookiesAccessed(content::RenderFrameHost* rfh,
+                         const content::CookieAccessDetails& details) override;
+  // Called when a specific Service Worker scope was accessed.
+  // If access was blocked due to the user's content settings,
+  // |blocked_by_policy_javascript| or/and |blocked_by_policy_cookie|
+  // should be true, and this function should invoke OnContentBlocked for
+  // JavaScript or/and cookies respectively.
+  void OnServiceWorkerAccessed(
+      content::NavigationHandle* navigation,
+      const GURL& scope,
+      content::AllowServiceWorkerResult allowed) override;
+  void OnServiceWorkerAccessed(
+      content::RenderFrameHost* frame,
+      const GURL& scope,
+      content::AllowServiceWorkerResult allowed) override;
+
+  std::unique_ptr<Delegate> delegate_;
+
+  raw_ptr<HostContentSettingsMap> map_;
+
+  // All currently registered |SiteDataObserver|s.
+  base::ObserverList<SiteDataObserver>::Unchecked observer_list_;
+
+  WEB_CONTENTS_USER_DATA_KEY_DECL();
+};
+
 }  // namespace
 
 using StorageType = mojom::ContentSettingsManager::StorageType;
 
-PageSpecificContentSettings::SiteDataObserver::SiteDataObserver(
-    content::WebContents* web_contents)
-    : web_contents_(web_contents) {
-  // Make sure the handler was attached to the WebContents as some UT might skip
-  // this.
-  auto* handler =
-      PageSpecificContentSettings::WebContentsHandler::FromWebContents(
-          web_contents_);
-  if (handler)
-    handler->AddSiteDataObserver(this);
-}
+InflightNavigationContentSettings::InflightNavigationContentSettings(
+    content::NavigationHandle&) {}
 
-PageSpecificContentSettings::SiteDataObserver::~SiteDataObserver() {
-  if (!web_contents_)
-    return;
-  auto* handler =
-      PageSpecificContentSettings::WebContentsHandler::FromWebContents(
-          web_contents_);
-  if (handler)
-    handler->RemoveSiteDataObserver(this);
-}
+InflightNavigationContentSettings::~InflightNavigationContentSettings() =
+    default;
 
-void PageSpecificContentSettings::SiteDataObserver::WebContentsDestroyed() {
-  web_contents_ = nullptr;
-}
+NAVIGATION_HANDLE_USER_DATA_KEY_IMPL(InflightNavigationContentSettings);
 
-PageSpecificContentSettings::WebContentsHandler::WebContentsHandler(
-    content::WebContents* web_contents,
-    std::unique_ptr<Delegate> delegate)
+WebContentsHandler::WebContentsHandler(content::WebContents* web_contents,
+                                       std::unique_ptr<Delegate> delegate)
     : WebContentsObserver(web_contents),
       content::WebContentsUserData<WebContentsHandler>(*web_contents),
       delegate_(std::move(delegate)),
@@ -99,18 +176,17 @@
   DCHECK(
       !PageSpecificContentSettings::GetForPage(web_contents->GetPrimaryPage()));
   content::PageUserData<PageSpecificContentSettings>::CreateForPage(
-      web_contents->GetPrimaryPage(), *this, delegate_.get());
+      web_contents->GetPrimaryPage(), delegate_.get());
 }
 
-PageSpecificContentSettings::WebContentsHandler::~WebContentsHandler() {
+WebContentsHandler::~WebContentsHandler() {
   for (SiteDataObserver& observer : observer_list_)
     observer.WebContentsDestroyed();
 }
 
-void PageSpecificContentSettings::WebContentsHandler::
-    TransferNavigationContentSettingsToCommittedDocument(
-        const InflightNavigationContentSettings& navigation_settings,
-        content::RenderFrameHost* rfh) {
+void WebContentsHandler::TransferNavigationContentSettingsToCommittedDocument(
+    const InflightNavigationContentSettings& navigation_settings,
+    content::RenderFrameHost* rfh) {
   for (const auto& cookie_access : navigation_settings.cookie_accesses) {
     OnCookiesAccessed(rfh, cookie_access);
   }
@@ -121,7 +197,7 @@
   }
 }
 
-void PageSpecificContentSettings::WebContentsHandler::OnCookiesAccessed(
+void WebContentsHandler::OnCookiesAccessed(
     content::NavigationHandle* navigation,
     const content::CookieAccessDetails& details) {
   if (WillNavigationCreateNewPageSpecificContentSettingsOnCommit(navigation)) {
@@ -138,7 +214,7 @@
   OnCookiesAccessed(navigation->GetParentFrame()->GetMainFrame(), details);
 }
 
-void PageSpecificContentSettings::WebContentsHandler::OnCookiesAccessed(
+void WebContentsHandler::OnCookiesAccessed(
     content::RenderFrameHost* rfh,
     const content::CookieAccessDetails& details) {
   auto* pscs = PageSpecificContentSettings::GetForPage(rfh->GetPage());
@@ -146,7 +222,7 @@
     pscs->OnCookiesAccessed(details);
 }
 
-void PageSpecificContentSettings::WebContentsHandler::OnServiceWorkerAccessed(
+void WebContentsHandler::OnServiceWorkerAccessed(
     content::NavigationHandle* navigation,
     const GURL& scope,
     content::AllowServiceWorkerResult allowed) {
@@ -168,7 +244,7 @@
                           allowed);
 }
 
-void PageSpecificContentSettings::WebContentsHandler::OnServiceWorkerAccessed(
+void WebContentsHandler::OnServiceWorkerAccessed(
     content::RenderFrameHost* frame,
     const GURL& scope,
     content::AllowServiceWorkerResult allowed) {
@@ -177,7 +253,7 @@
     pscs->OnServiceWorkerAccessed(scope, allowed);
 }
 
-void PageSpecificContentSettings::WebContentsHandler::ReadyToCommitNavigation(
+void WebContentsHandler::ReadyToCommitNavigation(
     content::NavigationHandle* navigation_handle) {
   content::RenderFrameHost* rfh = navigation_handle->GetRenderFrameHost();
 
@@ -200,7 +276,7 @@
   agent->SendRendererContentSettingRules(std::move(rules));
 }
 
-void PageSpecificContentSettings::WebContentsHandler::DidFinishNavigation(
+void WebContentsHandler::DidFinishNavigation(
     content::NavigationHandle* navigation_handle) {
   if (!navigation_handle->HasCommitted())
     return;
@@ -208,8 +284,7 @@
   if (WillNavigationCreateNewPageSpecificContentSettingsOnCommit(
           navigation_handle)) {
     content::PageUserData<PageSpecificContentSettings>::CreateForPage(
-        navigation_handle->GetRenderFrameHost()->GetPage(), *this,
-        delegate_.get());
+        navigation_handle->GetRenderFrameHost()->GetPage(), delegate_.get());
     InflightNavigationContentSettings* inflight_settings =
         content::NavigationHandleUserData<InflightNavigationContentSettings>::
             GetForNavigationHandle(*navigation_handle);
@@ -232,56 +307,60 @@
     delegate_->UpdateLocationBar();
 }
 
-void PageSpecificContentSettings::WebContentsHandler::AddSiteDataObserver(
-    SiteDataObserver* observer) {
+void WebContentsHandler::AddSiteDataObserver(SiteDataObserver* observer) {
   observer_list_.AddObserver(observer);
 }
 
-void PageSpecificContentSettings::WebContentsHandler::RemoveSiteDataObserver(
-    SiteDataObserver* observer) {
+void WebContentsHandler::RemoveSiteDataObserver(SiteDataObserver* observer) {
   observer_list_.RemoveObserver(observer);
 }
 
-void PageSpecificContentSettings::WebContentsHandler::
-    NotifySiteDataObservers() {
+void WebContentsHandler::NotifySiteDataObservers() {
   for (SiteDataObserver& observer : observer_list_)
     observer.OnSiteDataAccessed();
 }
 
-PageSpecificContentSettings::InflightNavigationContentSettings::
-    InflightNavigationContentSettings(content::NavigationHandle&) {}
+WEB_CONTENTS_USER_DATA_KEY_IMPL(WebContentsHandler);
 
-PageSpecificContentSettings::InflightNavigationContentSettings::
-    ~InflightNavigationContentSettings() = default;
+PageSpecificContentSettings::SiteDataObserver::SiteDataObserver(
+    content::WebContents* web_contents)
+    : web_contents_(web_contents) {
+  // Make sure the handler was attached to the WebContents as some UT might skip
+  // this.
+  auto* handler = WebContentsHandler::FromWebContents(web_contents_);
+  if (handler)
+    handler->AddSiteDataObserver(this);
+}
 
-NAVIGATION_HANDLE_USER_DATA_KEY_IMPL(
-    PageSpecificContentSettings::InflightNavigationContentSettings);
+PageSpecificContentSettings::SiteDataObserver::~SiteDataObserver() {
+  if (!web_contents_)
+    return;
+  auto* handler = WebContentsHandler::FromWebContents(web_contents_);
+  if (handler)
+    handler->RemoveSiteDataObserver(this);
+}
 
-WEB_CONTENTS_USER_DATA_KEY_IMPL(
-    PageSpecificContentSettings::WebContentsHandler);
+void PageSpecificContentSettings::SiteDataObserver::WebContentsDestroyed() {
+  web_contents_ = nullptr;
+}
 
 PageSpecificContentSettings::PendingUpdates::PendingUpdates() = default;
 
 PageSpecificContentSettings::PendingUpdates::~PendingUpdates() = default;
 
-PageSpecificContentSettings::PageSpecificContentSettings(
-    content::Page& page,
-    PageSpecificContentSettings::WebContentsHandler& handler,
-    Delegate* delegate)
+PageSpecificContentSettings::PageSpecificContentSettings(content::Page& page,
+                                                         Delegate* delegate)
     : content::PageUserData<PageSpecificContentSettings>(page),
-      handler_(handler),
       delegate_(delegate),
       map_(delegate_->GetSettingsMap()),
-      allowed_local_shared_objects_(
-          handler_.web_contents()->GetBrowserContext(),
-          /*ignore_empty_localstorage=*/true,
-          delegate_->GetAdditionalFileSystemTypes(),
-          delegate_->GetIsDeletionDisabledCallback()),
-      blocked_local_shared_objects_(
-          handler_.web_contents()->GetBrowserContext(),
-          /*ignore_empty_localstorage=*/false,
-          delegate_->GetAdditionalFileSystemTypes(),
-          delegate_->GetIsDeletionDisabledCallback()),
+      allowed_local_shared_objects_(GetWebContents()->GetBrowserContext(),
+                                    /*ignore_empty_localstorage=*/true,
+                                    delegate_->GetAdditionalFileSystemTypes(),
+                                    delegate_->GetIsDeletionDisabledCallback()),
+      blocked_local_shared_objects_(GetWebContents()->GetBrowserContext(),
+                                    /*ignore_empty_localstorage=*/false,
+                                    delegate_->GetAdditionalFileSystemTypes(),
+                                    delegate_->GetIsDeletionDisabledCallback()),
       microphone_camera_state_(MICROPHONE_CAMERA_NOT_ACCESSED) {
   observation_.Observe(map_.get());
   if (page.GetMainDocument().GetLifecycleState() ==
@@ -296,8 +375,7 @@
 void PageSpecificContentSettings::CreateForWebContents(
     content::WebContents* web_contents,
     std::unique_ptr<Delegate> delegate) {
-  PageSpecificContentSettings::WebContentsHandler::CreateForWebContents(
-      web_contents, std::move(delegate));
+  WebContentsHandler::CreateForWebContents(web_contents, std::move(delegate));
 }
 
 // static
@@ -305,8 +383,7 @@
     content::WebContents* web_contents) {
   PageSpecificContentSettings::DeleteForPage(web_contents->GetPrimaryPage());
 
-  web_contents->RemoveUserData(
-      PageSpecificContentSettings::WebContentsHandler::UserDataKey());
+  web_contents->RemoveUserData(WebContentsHandler::UserDataKey());
 }
 
 // static
@@ -330,9 +407,7 @@
 PageSpecificContentSettings::Delegate*
 PageSpecificContentSettings::GetDelegateForWebContents(
     content::WebContents* web_contents) {
-  auto* handler =
-      PageSpecificContentSettings::WebContentsHandler::FromWebContents(
-          web_contents);
+  auto* handler = WebContentsHandler::FromWebContents(web_contents);
   return handler ? handler->delegate() : nullptr;
 }
 
@@ -393,8 +468,7 @@
 content::WebContentsObserver*
 PageSpecificContentSettings::GetWebContentsObserverForTest(
     content::WebContents* web_contents) {
-  return PageSpecificContentSettings::WebContentsHandler::FromWebContents(
-      web_contents);
+  return WebContentsHandler::FromWebContents(web_contents);
 }
 
 bool PageSpecificContentSettings::IsContentBlocked(
@@ -917,7 +991,8 @@
   }
 
   if (updates_queued_during_prerender_->site_data_accessed) {
-    handler_.NotifySiteDataObservers();
+    WebContentsHandler::FromWebContents(GetWebContents())
+        ->NotifySiteDataObservers();
   }
 
   updates_queued_during_prerender_.reset();
@@ -930,7 +1005,8 @@
     updates_queued_during_prerender_->site_data_accessed = true;
     return;
   }
-  handler_.NotifySiteDataObservers();
+  WebContentsHandler::FromWebContents(GetWebContents())
+      ->NotifySiteDataObservers();
 }
 
 void PageSpecificContentSettings::MaybeUpdateLocationBar() {
@@ -941,6 +1017,10 @@
   delegate_->UpdateLocationBar();
 }
 
+content::WebContents* PageSpecificContentSettings::GetWebContents() const {
+  return content::WebContents::FromRenderFrameHost(&page().GetMainDocument());
+}
+
 PAGE_USER_DATA_KEY_IMPL(PageSpecificContentSettings);
 
 }  // namespace content_settings
diff --git a/components/content_settings/browser/page_specific_content_settings.h b/components/content_settings/browser/page_specific_content_settings.h
index 186bd34..e2f2a864 100644
--- a/components/content_settings/browser/page_specific_content_settings.h
+++ b/components/content_settings/browser/page_specific_content_settings.h
@@ -29,18 +29,16 @@
 #include "components/content_settings/core/common/content_settings_types.h"
 #include "components/privacy_sandbox/canonical_topic.h"
 #include "content/public/browser/allow_service_worker_result.h"
-#include "content/public/browser/navigation_handle_user_data.h"
 #include "content/public/browser/page_user_data.h"
 #include "content/public/browser/render_frame_host.h"
-#include "content/public/browser/web_contents_observer.h"
-#include "content/public/browser/web_contents_user_data.h"
 
 namespace blink {
 class StorageKey;
 }  // namespace blink
 
 namespace content {
-class NavigationHandle;
+class WebContents;
+class WebContentsObserver;
 }
 
 namespace url {
@@ -380,101 +378,13 @@
   // Returns the topics that were accessed by this page.
   std::vector<privacy_sandbox::CanonicalTopic> GetAccessedTopics() const;
 
+  // Runs any queued updates in |updates_queued_during_prerender_|, should be
+  // called after the page activates.
+  void OnPrerenderingPageActivation();
+
  private:
   friend class content::PageUserData<PageSpecificContentSettings>;
 
-  // Keeps track of cookie and service worker access during a navigation.
-  // These types of access can happen for the current page or for a new
-  // navigation (think cookies sent in the HTTP request or service worker
-  // being run to serve a fetch request). A navigation might fail to
-  // commit in which case we have to handle it as if it had never
-  // occurred. So we cache all cookies and service worker accesses that
-  // happen during a navigation and only apply the changes if the
-  // navigation commits.
-  class InflightNavigationContentSettings
-      : public content::NavigationHandleUserData<
-            InflightNavigationContentSettings> {
-   public:
-    ~InflightNavigationContentSettings() override;
-    std::vector<content::CookieAccessDetails> cookie_accesses;
-    std::vector<std::pair<GURL, content::AllowServiceWorkerResult>>
-        service_worker_accesses;
-
-   private:
-    explicit InflightNavigationContentSettings(
-        content::NavigationHandle& navigation_handle);
-    friend class content::NavigationHandleUserData<
-        InflightNavigationContentSettings>;
-    NAVIGATION_HANDLE_USER_DATA_KEY_DECL();
-  };
-
-  // This class attaches to WebContents to listen to events and route them to
-  // appropriate PageSpecificContentSettings, store navigation related events
-  // until the navigation finishes and then transferring the
-  // navigation-associated state to the newly-created page.
-  class WebContentsHandler
-      : public content::WebContentsObserver,
-        public content::WebContentsUserData<WebContentsHandler> {
-   public:
-    explicit WebContentsHandler(content::WebContents* web_contents,
-                                std::unique_ptr<Delegate> delegate);
-    ~WebContentsHandler() override;
-    // Adds the given |SiteDataObserver|. The |observer| is notified when a
-    // locale shared object, like for example a cookie, is accessed.
-    void AddSiteDataObserver(SiteDataObserver* observer);
-
-    // Removes the given |SiteDataObserver|.
-    void RemoveSiteDataObserver(SiteDataObserver* observer);
-
-    // Notifies all registered |SiteDataObserver|s.
-    void NotifySiteDataObservers();
-
-    Delegate* delegate() { return delegate_.get(); }
-
-   private:
-    friend class content::WebContentsUserData<WebContentsHandler>;
-
-    // Applies all stored events for the given navigation to the current main
-    // document.
-    void TransferNavigationContentSettingsToCommittedDocument(
-        const InflightNavigationContentSettings& navigation_settings,
-        content::RenderFrameHost* rfh);
-
-    // content::WebContentsObserver overrides.
-    void ReadyToCommitNavigation(
-        content::NavigationHandle* navigation_handle) override;
-    void DidFinishNavigation(
-        content::NavigationHandle* navigation_handle) override;
-    void OnCookiesAccessed(
-        content::NavigationHandle* navigation,
-        const content::CookieAccessDetails& details) override;
-    void OnCookiesAccessed(
-        content::RenderFrameHost* rfh,
-        const content::CookieAccessDetails& details) override;
-    // Called when a specific Service Worker scope was accessed.
-    // If access was blocked due to the user's content settings,
-    // |blocked_by_policy_javascript| or/and |blocked_by_policy_cookie|
-    // should be true, and this function should invoke OnContentBlocked for
-    // JavaScript or/and cookies respectively.
-    void OnServiceWorkerAccessed(
-        content::NavigationHandle* navigation,
-        const GURL& scope,
-        content::AllowServiceWorkerResult allowed) override;
-    void OnServiceWorkerAccessed(
-        content::RenderFrameHost* frame,
-        const GURL& scope,
-        content::AllowServiceWorkerResult allowed) override;
-
-    std::unique_ptr<Delegate> delegate_;
-
-    raw_ptr<HostContentSettingsMap> map_;
-
-    // All currently registered |SiteDataObserver|s.
-    base::ObserverList<SiteDataObserver>::Unchecked observer_list_;
-
-    WEB_CONTENTS_USER_DATA_KEY_DECL();
-  };
-
   struct PendingUpdates {
     PendingUpdates();
     ~PendingUpdates();
@@ -483,10 +393,7 @@
     bool site_data_accessed = false;
   };
 
-  explicit PageSpecificContentSettings(
-      content::Page& page,
-      PageSpecificContentSettings::WebContentsHandler& handler,
-      Delegate* delegate);
+  explicit PageSpecificContentSettings(content::Page& page, Delegate* delegate);
 
   // content_settings::Observer implementation.
   void OnContentSettingChanged(const ContentSettingsPattern& primary_pattern,
@@ -498,7 +405,6 @@
 
   bool IsPagePrerendering() const;
   bool IsEmbeddedPage() const;
-  void OnPrerenderingPageActivation();
 
   // Delays the call of the delegate method if the page is currently
   // prerendering until the page is activated; directly calls the method
@@ -536,7 +442,7 @@
   // the page is currently prerendering or is embedded.
   void MaybeUpdateLocationBar();
 
-  WebContentsHandler& handler_;
+  content::WebContents* GetWebContents() const;
 
   raw_ptr<Delegate> delegate_;
 
diff --git a/components/content_settings/core/browser/cookie_settings.cc b/components/content_settings/core/browser/cookie_settings.cc
index d08d6f5c..0acb2f8 100644
--- a/components/content_settings/core/browser/cookie_settings.cc
+++ b/components/content_settings/core/browser/cookie_settings.cc
@@ -18,6 +18,7 @@
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/prefs/pref_service.h"
 #include "extensions/buildflags/buildflags.h"
+#include "net/base/features.h"
 #include "net/cookies/cookie_util.h"
 #include "net/cookies/site_for_cookies.h"
 #include "url/gurl.h"
diff --git a/components/content_settings/core/common/cookie_settings_base.cc b/components/content_settings/core/common/cookie_settings_base.cc
index 5e46cf3b..064481bc 100644
--- a/components/content_settings/core/common/cookie_settings_base.cc
+++ b/components/content_settings/core/common/cookie_settings_base.cc
@@ -21,6 +21,14 @@
 
 namespace content_settings {
 
+CookieSettingsBase::CookieSettingsBase()
+    : storage_access_api_enabled_(
+          base::FeatureList::IsEnabled(net::features::kStorageAccessAPI)),
+      storage_access_api_grants_unpartitioned_storage_(
+          net::features::kStorageAccessAPIGrantsUnpartitionedStorage.Get()),
+      is_storage_partitioned_(base::FeatureList::IsEnabled(
+          net::features::kThirdPartyStoragePartitioning)) {}
+
 // static
 bool CookieSettingsBase::IsThirdPartyRequest(
     const GURL& url,
@@ -134,18 +142,31 @@
   return net::CookieAccessSemantics::UNKNOWN;
 }
 
-// static
 bool CookieSettingsBase::ShouldConsiderStorageAccessGrants(
-    QueryReason query_reason) {
+    QueryReason query_reason) const {
+  return CookieSettingsBase::ShouldConsiderStorageAccessGrantsInternal(
+      query_reason, storage_access_api_enabled_,
+      storage_access_api_grants_unpartitioned_storage_,
+      is_storage_partitioned_);
+}
+
+// static
+bool CookieSettingsBase::ShouldConsiderStorageAccessGrantsInternal(
+    QueryReason query_reason,
+    bool storage_access_api_enabled,
+    bool storage_access_api_grants_unpartitioned_storage,
+    bool is_storage_partitioned) {
   switch (query_reason) {
     case QueryReason::kSetting:
       return false;
     case QueryReason::kPrivacySandbox:
       return false;
     case QueryReason::kSiteStorage:
-      return base::FeatureList::IsEnabled(net::features::kStorageAccessAPI);
+      return storage_access_api_enabled &&
+             (storage_access_api_grants_unpartitioned_storage ||
+              is_storage_partitioned);
     case QueryReason::kCookies:
-      return base::FeatureList::IsEnabled(net::features::kStorageAccessAPI);
+      return storage_access_api_enabled;
   }
 }
 
diff --git a/components/content_settings/core/common/cookie_settings_base.h b/components/content_settings/core/common/cookie_settings_base.h
index e75715f..deb6d61 100644
--- a/components/content_settings/core/common/cookie_settings_base.h
+++ b/components/content_settings/core/common/cookie_settings_base.h
@@ -66,7 +66,7 @@
 // |top_frame_origin|. This is done inconsistently and needs to be fixed.
 class CookieSettingsBase {
  public:
-  CookieSettingsBase() = default;
+  CookieSettingsBase();
 
   CookieSettingsBase(const CookieSettingsBase&) = delete;
   CookieSettingsBase& operator=(const CookieSettingsBase&) = delete;
@@ -205,7 +205,15 @@
   // access.
   static bool IsValidSettingForLegacyAccess(ContentSetting setting);
 
-  static bool ShouldConsiderStorageAccessGrants(QueryReason query_reason);
+  // Returns true iff the query should consider Storage Access API permission
+  // grants.
+  bool ShouldConsiderStorageAccessGrants(QueryReason query_reason) const;
+  // Static version of the above, exposed for testing.
+  static bool ShouldConsiderStorageAccessGrantsInternal(
+      QueryReason query_reason,
+      bool storage_access_api_enabled,
+      bool storage_access_api_grants_unpartitioned_storage,
+      bool is_storage_partitioned);
 
  protected:
   // Returns true iff the request is considered third-party.
@@ -225,6 +233,10 @@
       bool is_third_party_request,
       content_settings::SettingSource* source,
       QueryReason query_reason) const = 0;
+
+  bool storage_access_api_enabled_;
+  bool storage_access_api_grants_unpartitioned_storage_;
+  bool is_storage_partitioned_;
 };
 
 }  // namespace content_settings
diff --git a/components/content_settings/core/common/cookie_settings_base_unittest.cc b/components/content_settings/core/common/cookie_settings_base_unittest.cc
index 20d35ef..e71d08de 100644
--- a/components/content_settings/core/common/cookie_settings_base_unittest.cc
+++ b/components/content_settings/core/common/cookie_settings_base_unittest.cc
@@ -36,6 +36,10 @@
       std::string(), false);
 }
 
+std::string BoolToString(bool b) {
+  return b ? "true" : "false";
+}
+
 class CallbackCookieSettings : public CookieSettingsBase {
  public:
   explicit CallbackCookieSettings(GetSettingCallback callback)
@@ -225,14 +229,33 @@
 }
 
 class CookieSettingsBaseStorageAccessAPITest
-    : public testing::TestWithParam<bool> {
+    : public testing::TestWithParam<std::tuple<bool, bool, bool>> {
  public:
   CookieSettingsBaseStorageAccessAPITest() {
-    features_.InitWithFeatureState(net::features::kStorageAccessAPI,
-                                   IsStorageAccessAPIEnabled());
+    std::vector<base::test::ScopedFeatureList::FeatureAndParams> enabled;
+    std::vector<base::Feature> disabled;
+    if (IsStorageAccessAPIEnabled()) {
+      enabled.push_back({net::features::kStorageAccessAPI,
+                         {{"storage-access-api-grants-unpartitioned-storage",
+                           BoolToString(IsStorageGrantedByPermission())}}});
+    } else {
+      disabled.push_back(net::features::kStorageAccessAPI);
+    }
+    features_.InitWithFeaturesAndParameters(enabled, disabled);
   }
 
-  bool IsStorageAccessAPIEnabled() const { return GetParam(); }
+  bool IsStorageAccessAPIEnabled() const { return std::get<0>(GetParam()); }
+  bool PermissionGrantsUnpartitionedStorage() const {
+    return std::get<1>(GetParam());
+  }
+  bool IsStoragePartitioned() const { return std::get<2>(GetParam()); }
+
+  bool IsStorageGrantedByPermission() const {
+    // Storage access should only be granted if the permission grants
+    // unpartitioned storage, or if storage is partitioned.
+    return IsStorageAccessAPIEnabled() &&
+           (PermissionGrantsUnpartitionedStorage() || IsStoragePartitioned());
+  }
 
  private:
   base::test::ScopedFeatureList features_;
@@ -240,22 +263,30 @@
 
 TEST_P(CookieSettingsBaseStorageAccessAPITest,
        ShouldConsiderStorageAccessGrants) {
-  EXPECT_FALSE(CookieSettingsBase::ShouldConsiderStorageAccessGrants(
-      QueryReason::kSetting));
-  EXPECT_FALSE(CookieSettingsBase::ShouldConsiderStorageAccessGrants(
-      QueryReason::kPrivacySandbox));
+  EXPECT_FALSE(CookieSettingsBase::ShouldConsiderStorageAccessGrantsInternal(
+      QueryReason::kSetting, IsStorageAccessAPIEnabled(),
+      PermissionGrantsUnpartitionedStorage(), IsStoragePartitioned()));
 
-  EXPECT_EQ(CookieSettingsBase::ShouldConsiderStorageAccessGrants(
-                QueryReason::kSiteStorage),
-            IsStorageAccessAPIEnabled());
-  EXPECT_EQ(CookieSettingsBase::ShouldConsiderStorageAccessGrants(
-                QueryReason::kCookies),
+  EXPECT_FALSE(CookieSettingsBase::ShouldConsiderStorageAccessGrantsInternal(
+      QueryReason::kPrivacySandbox, IsStorageAccessAPIEnabled(),
+      PermissionGrantsUnpartitionedStorage(), IsStoragePartitioned()));
+
+  EXPECT_EQ(CookieSettingsBase::ShouldConsiderStorageAccessGrantsInternal(
+                QueryReason::kSiteStorage, IsStorageAccessAPIEnabled(),
+                PermissionGrantsUnpartitionedStorage(), IsStoragePartitioned()),
+            IsStorageGrantedByPermission());
+
+  EXPECT_EQ(CookieSettingsBase::ShouldConsiderStorageAccessGrantsInternal(
+                QueryReason::kCookies, IsStorageAccessAPIEnabled(),
+                PermissionGrantsUnpartitionedStorage(), IsStoragePartitioned()),
             IsStorageAccessAPIEnabled());
 }
 
 INSTANTIATE_TEST_SUITE_P(/* no prefix */,
                          CookieSettingsBaseStorageAccessAPITest,
-                         testing::Bool());
+                         testing::Combine(testing::Bool(),
+                                          testing::Bool(),
+                                          testing::Bool()));
 
 }  // namespace
 }  // namespace content_settings
diff --git a/components/exo/shared_memory.cc b/components/exo/shared_memory.cc
index f158bf1..2993e0b8 100644
--- a/components/exo/shared_memory.cc
+++ b/components/exo/shared_memory.cc
@@ -42,7 +42,7 @@
 std::unique_ptr<Buffer> SharedMemory::CreateBuffer(const gfx::Size& size,
                                                    gfx::BufferFormat format,
                                                    unsigned offset,
-                                                   int stride) {
+                                                   uint32_t stride) {
   TRACE_EVENT2("exo", "SharedMemory::CreateBuffer", "size", size.ToString(),
                "format", static_cast<int>(format));
 
@@ -52,10 +52,8 @@
     return nullptr;
   }
 
-  if (!base::IsValueInRangeForNumericType<size_t>(stride) ||
-      gfx::RowSizeForBufferFormat(size.width(), format, 0) >
-          static_cast<size_t>(stride) ||
-      static_cast<size_t>(stride) & 3) {
+  if (gfx::RowSizeForBufferFormat(size.width(), format, 0) > stride ||
+      stride & 3) {
     DLOG(WARNING) << "Failed to create shm buffer. Unsupported stride "
                   << stride;
     return nullptr;
diff --git a/components/exo/shared_memory.h b/components/exo/shared_memory.h
index 8222fa7..47fa7356 100644
--- a/components/exo/shared_memory.h
+++ b/components/exo/shared_memory.h
@@ -34,7 +34,7 @@
   std::unique_ptr<Buffer> CreateBuffer(const gfx::Size& size,
                                        gfx::BufferFormat format,
                                        unsigned offset,
-                                       int stride);
+                                       uint32_t stride);
 
   size_t GetSize() const;
   bool Resize(const size_t new_size);
diff --git a/components/exo/text_input.cc b/components/exo/text_input.cc
index bae361a..f2dccff 100644
--- a/components/exo/text_input.cc
+++ b/components/exo/text_input.cc
@@ -255,7 +255,7 @@
   return gfx::Rect();
 }
 
-bool TextInput::GetCompositionCharacterBounds(uint32_t index,
+bool TextInput::GetCompositionCharacterBounds(size_t index,
                                               gfx::Rect* rect) const {
   return false;
 }
diff --git a/components/exo/text_input.h b/components/exo/text_input.h
index 2610b8fa..eb19f262 100644
--- a/components/exo/text_input.h
+++ b/components/exo/text_input.h
@@ -182,7 +182,7 @@
   bool CanComposeInline() const override;
   gfx::Rect GetCaretBounds() const override;
   gfx::Rect GetSelectionBoundingBox() const override;
-  bool GetCompositionCharacterBounds(uint32_t index,
+  bool GetCompositionCharacterBounds(size_t index,
                                      gfx::Rect* rect) const override;
   bool HasCompositionText() const override;
   ui::TextInputClient::FocusReason GetFocusReason() const override;
diff --git a/components/favicon/content/content_favicon_driver.cc b/components/favicon/content/content_favicon_driver.cc
index f185a4f..13025e8 100644
--- a/components/favicon/content/content_favicon_driver.cc
+++ b/components/favicon/content/content_favicon_driver.cc
@@ -96,15 +96,15 @@
 }
 
 int ContentFaviconDriver::DownloadImage(const GURL& url,
-                                        int preferred_size,
+                                        int max_image_size,
                                         ImageDownloadCallback callback) {
   bool bypass_cache = (bypass_cache_page_url_ == GetActiveURL());
   bypass_cache_page_url_ = GURL();
 
-  const gfx::Size preferred_size_constraints(preferred_size, preferred_size);
-  return web_contents()->DownloadImage(url, true, preferred_size_constraints,
-                                       /*max_image_size = */ 0, bypass_cache,
-                                       std::move(callback));
+  const gfx::Size preferred_size(max_image_size, max_image_size);
+  return web_contents()->DownloadImage(url, true, preferred_size,
+                                       /*max_bitmap_size=*/max_image_size,
+                                       bypass_cache, std::move(callback));
 }
 
 void ContentFaviconDriver::DownloadManifest(const GURL& url,
diff --git a/components/favicon/content/content_favicon_driver.h b/components/favicon/content/content_favicon_driver.h
index 6cf0a23..000ac24 100644
--- a/components/favicon/content/content_favicon_driver.h
+++ b/components/favicon/content/content_favicon_driver.h
@@ -74,7 +74,7 @@
 
   // FaviconHandler::Delegate implementation.
   int DownloadImage(const GURL& url,
-                    int preferred_size,
+                    int max_image_size,
                     ImageDownloadCallback callback) override;
   void DownloadManifest(const GURL& url,
                         ManifestDownloadCallback callback) override;
diff --git a/components/favicon/core/favicon_handler.cc b/components/favicon/core/favicon_handler.cc
index 2bea93f..56f54be5 100644
--- a/components/favicon/core/favicon_handler.cc
+++ b/components/favicon/core/favicon_handler.cc
@@ -22,7 +22,6 @@
 #include "components/favicon_base/favicon_util.h"
 #include "components/favicon_base/select_favicon_frames.h"
 #include "skia/ext/image_operations.h"
-#include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/gfx/codec/png_codec.h"
 #include "ui/gfx/image/image_skia.h"
 #include "ui/gfx/image/image_util.h"
@@ -714,8 +713,9 @@
   image_download_request_.Reset(
       base::BindOnce(&FaviconHandler::OnDidDownloadFavicon,
                      base::Unretained(this), icon_type));
-  // The maximal icon size is passed in to set the preferred size for vector
-  // images. See FaviconHandler::Delegate::DownloadImage() for more info.
+  // A max bitmap size is specified to avoid receiving huge bitmaps in
+  // OnDidDownloadFavicon(). See FaviconDriver::StartDownload()
+  // for more details about the max bitmap size.
   const int download_id = delegate_->DownloadImage(
       image_url, GetMaximalIconSize(handler_type_, !manifest_url_.is_empty()),
       image_download_request_.callback());
diff --git a/components/favicon/core/favicon_handler.h b/components/favicon/core/favicon_handler.h
index 7e2dc5f..2be71b2 100644
--- a/components/favicon/core/favicon_handler.h
+++ b/components/favicon/core/favicon_handler.h
@@ -93,14 +93,12 @@
     // is called with the results. Returns the unique id of the download
     // request, which will also be passed to the callback. In case of error, 0
     // is returned and no callback will be called.
-    // For vector images, |preferred_size| will serve as a viewport into which
-    // the image will be rendered. This would usually be the dimensions of the
-    // rectangle where the bitmap will be rendered. If |preferred_size| is
-    // empty, any existing intrinsic dimensions of the image will be used. All
-    // images are downloaded (without filtering by size), and all filtering is
-    // handled by the utility functions inside select_favicon_frames.cc.
+    // Bitmaps with pixel sizes larger than |max_bitmap_size| are filtered out
+    // from the bitmap results. If there are no bitmap results <=
+    // |max_bitmap_size|, the smallest bitmap is resized to |max_bitmap_size|
+    // and is the only result. A |max_bitmap_size| of 0 means unlimited.
     virtual int DownloadImage(const GURL& url,
-                              int preferred_size,
+                              int max_image_size,
                               ImageDownloadCallback callback) = 0;
 
     // Downloads a WebManifest and returns the favicons listed there.
diff --git a/components/favicon/core/favicon_handler_unittest.cc b/components/favicon/core/favicon_handler_unittest.cc
index 5736a55..7530efa 100644
--- a/components/favicon/core/favicon_handler_unittest.cc
+++ b/components/favicon/core/favicon_handler_unittest.cc
@@ -134,12 +134,21 @@
   // Implementation of FaviconHalder::Delegate's DownloadImage(). If a given
   // URL is not known (i.e. not previously added via Add()), it produces 404s.
   int DownloadImage(const GURL& url,
-                    int preferred_size,
+                    int max_image_size,
                     FaviconHandler::Delegate::ImageDownloadCallback callback) {
     downloads_->push_back(url);
 
     Response response = responses_[url];
     DCHECK_EQ(response.bitmaps.size(), response.original_bitmap_sizes.size());
+    // Apply maximum image size.
+    for (size_t i = 0; i < response.bitmaps.size(); ++i) {
+      if (response.bitmaps[i].width() > max_image_size ||
+          response.bitmaps[i].height() > max_image_size) {
+        response.bitmaps[i] = skia::ImageOperations::Resize(
+            response.bitmaps[i], skia::ImageOperations::RESIZE_LANCZOS3,
+            max_image_size, max_image_size);
+      }
+    }
 
     int download_id = next_download_id_++;
     base::OnceClosure bound_callback = base::BindOnce(
@@ -290,9 +299,9 @@
   }
 
   int DownloadImage(const GURL& url,
-                    int preferred_size,
+                    int max_image_size,
                     ImageDownloadCallback callback) override {
-    return fake_image_downloader_.DownloadImage(url, preferred_size,
+    return fake_image_downloader_.DownloadImage(url, max_image_size,
                                                 std::move(callback));
   }
 
@@ -1718,7 +1727,7 @@
   EXPECT_THAT(delegate_.downloads(), ElementsAre(kIconURL2));
 }
 
-TEST_F(FaviconHandlerTest, TestFaviconNotScaledButSelectedCorrectly) {
+TEST_F(FaviconHandlerTest, TestFaviconWasScaledAfterDownload) {
   const int kMaximalSize = FaviconHandler::GetMaximalIconSize(
       FaviconDriverObserver::NON_TOUCH_LARGEST,
       /*candidates_from_web_manifest=*/false);
@@ -1735,10 +1744,10 @@
                                         SK_ColorBLUE);
 
   // Verify the best bitmap was selected (although smaller than |kIconURL2|)
-  // and that it was downloaded without any scaling.
+  // and that it was scaled down to |kMaximalSize|.
   EXPECT_CALL(delegate_,
               OnFaviconUpdated(_, _, kIconURL1, _,
-                               ImageSizeIs(kOriginalSize1, kOriginalSize1)));
+                               ImageSizeIs(kMaximalSize, kMaximalSize)));
 
   RunHandlerWithCandidates(
       FaviconDriverObserver::NON_TOUCH_LARGEST,
diff --git a/components/favicon/ios/web_favicon_driver.h b/components/favicon/ios/web_favicon_driver.h
index 9657294..84bbb2a 100644
--- a/components/favicon/ios/web_favicon_driver.h
+++ b/components/favicon/ios/web_favicon_driver.h
@@ -41,7 +41,7 @@
 
   // FaviconHandler::Delegate implementation.
   int DownloadImage(const GURL& url,
-                    int preferred_size,
+                    int max_image_size,
                     ImageDownloadCallback callback) override;
   void DownloadManifest(const GURL& url,
                         ManifestDownloadCallback callback) override;
diff --git a/components/favicon/ios/web_favicon_driver.mm b/components/favicon/ios/web_favicon_driver.mm
index 150c776..00ca6e83 100644
--- a/components/favicon/ios/web_favicon_driver.mm
+++ b/components/favicon/ios/web_favicon_driver.mm
@@ -48,7 +48,7 @@
 }
 
 int WebFaviconDriver::DownloadImage(const GURL& url,
-                                    int preferred_size,
+                                    int max_image_size,
                                     ImageDownloadCallback callback) {
   static int downloaded_image_count = 0;
   int local_download_id = ++downloaded_image_count;
@@ -65,10 +65,7 @@
         std::vector<SkBitmap> frames;
         std::vector<gfx::Size> sizes;
         if (data) {
-          // From FaviconHandler::ScheduleImageDownload, the preferred_size
-          // contains the maximal size while the max_image_size is set to 0 to
-          // allow all files to be downloaded by the blink renderer.
-          frames = skia::ImageDataToSkBitmapsWithMaxSize(data, preferred_size);
+          frames = skia::ImageDataToSkBitmapsWithMaxSize(data, max_image_size);
           for (const auto& frame : frames) {
             sizes.push_back(gfx::Size(frame.width(), frame.height()));
           }
diff --git a/components/history/core/browser/history_backend.cc b/components/history/core/browser/history_backend.cc
index 86bdbf7..5afb07f2 100644
--- a/components/history/core/browser/history_backend.cc
+++ b/components/history/core/browser/history_backend.cc
@@ -1775,7 +1775,7 @@
   if (!db_)
     return base::Time::Min();
   const auto clusters =
-      GetMostRecentClusters(base::Time::Min(), base::Time::Max(), 1);
+      GetMostRecentClusters(base::Time::Min(), base::Time::Max(), 1, false);
   return clusters.empty() ? base::Time::Min()
                           : clusters[0]
                                 .GetMostRecentVisit()
@@ -1796,26 +1796,30 @@
 std::vector<Cluster> HistoryBackend::GetMostRecentClusters(
     base::Time inclusive_min_time,
     base::Time exclusive_max_time,
-    int max_clusters) {
+    int max_clusters,
+    bool include_keywords) {
   TRACE_EVENT0("browser", "HistoryBackend::GetMostRecentClusters");
   if (!db_)
     return {};
   const auto cluster_ids = db_->GetMostRecentClusterIds(
       inclusive_min_time, exclusive_max_time, max_clusters);
   std::vector<Cluster> clusters;
-  base::ranges::transform(
-      cluster_ids, std::back_inserter(clusters),
-      [&](const auto& cluster_id) { return GetCluster(cluster_id); });
+  base::ranges::transform(cluster_ids, std::back_inserter(clusters),
+                          [&](const auto& cluster_id) {
+                            return GetCluster(cluster_id, include_keywords);
+                          });
   return clusters;
 }
 
-Cluster HistoryBackend::GetCluster(int64_t cluster_id) {
+Cluster HistoryBackend::GetCluster(int64_t cluster_id, bool include_keywords) {
   TRACE_EVENT0("browser", "HistoryBackend::GetCluster");
   if (!db_)
     return {};
 
   Cluster cluster = db_->GetCluster(cluster_id);
   cluster.visits = ToClusterVisits(db_->GetVisitIdsInCluster(cluster_id));
+  if (include_keywords)
+    cluster.keyword_to_data_map = db_->GetClusterKeywords(cluster_id);
   return cluster;
 }
 
diff --git a/components/history/core/browser/history_backend.h b/components/history/core/browser/history_backend.h
index 68e61b6..c0c9920ea 100644
--- a/components/history/core/browser/history_backend.h
+++ b/components/history/core/browser/history_backend.h
@@ -484,10 +484,12 @@
 
   std::vector<Cluster> GetMostRecentClusters(base::Time inclusive_min_time,
                                              base::Time exclusive_max_time,
-                                             int max_clusters);
+                                             int max_clusters,
+                                             bool include_keywords = true);
 
-  // Get a `Cluster`.
-  Cluster GetCluster(int64_t cluster_id);
+  // Get a `Cluster`. `keyword_to_data_map` isn't always useful, and it requires
+  // an extra SQL execution. It's only populated If `include_keywords` is true.
+  Cluster GetCluster(int64_t cluster_id, bool include_keywords = true);
 
   // Finds the 1st visit in the redirect chain containing `visit`.
   // Unlike `GetRedirectsToSpecificVisit()`, this only considers actual
diff --git a/components/history/core/browser/history_backend_unittest.cc b/components/history/core/browser/history_backend_unittest.cc
index 7923bc0b..ba81500f 100644
--- a/components/history/core/browser/history_backend_unittest.cc
+++ b/components/history/core/browser/history_backend_unittest.cc
@@ -3870,17 +3870,50 @@
   // returned.
   ClusterVisit visit_3;
   visit_3.annotated_visit.visit_row.visit_id = 3;
-  backend_->db_->AddClusters(
-      {{0, {visit_1, visit_2, visit_3}, {}, false, u"label"}});
 
-  const auto cluster = backend_->GetCluster(1);
+  ClusterKeywordData keyword_data_1 = {
+      ClusterKeywordData::ClusterKeywordType::kEntityAlias,
+      .4,
+      {"entity1", "entity2"}};
+  ClusterKeywordData keyword_data_2 = {
+      ClusterKeywordData::ClusterKeywordType::kEntityCategory, .6, {}};
+
+  backend_->db_->AddClusters(
+      {{0,
+        {visit_1, visit_2, visit_3},
+        {{u"keyword1", keyword_data_1}, {u"keyword2", keyword_data_2}},
+        false,
+        u"label"}});
+
+  auto cluster = backend_->GetCluster(1, true);
   VerifyCluster(cluster, {1, {2, 1}});
   EXPECT_EQ(cluster.cluster_id, 1);
   EXPECT_EQ(cluster.label, u"label");
   EXPECT_EQ(cluster.visits[1].url_for_display, u"url_for_display");
+  EXPECT_EQ(cluster.keyword_to_data_map.size(), 2u);
+  EXPECT_EQ(cluster.keyword_to_data_map[u"keyword1"].type,
+            ClusterKeywordData::ClusterKeywordType::kEntityAlias);
+  EXPECT_EQ(cluster.keyword_to_data_map[u"keyword1"].score, .4f);
+  // Only the 1st keyword entity should be preserved.
+  EXPECT_THAT(cluster.keyword_to_data_map[u"keyword1"].entity_collections,
+              UnorderedElementsAre("entity1"));
+  EXPECT_EQ(cluster.keyword_to_data_map[u"keyword2"].type,
+            ClusterKeywordData::ClusterKeywordType::kEntityCategory);
+  EXPECT_EQ(cluster.keyword_to_data_map[u"keyword2"].score, .6f);
+  EXPECT_TRUE(
+      cluster.keyword_to_data_map[u"keyword2"].entity_collections.empty());
+
+  // Verify keywords are not returned, but other info is, when the
+  // `include_keywords` param is false.
+  cluster = backend_->GetCluster(1, false);
+  VerifyCluster(cluster, {1, {2, 1}});
+  EXPECT_EQ(cluster.cluster_id, 1);
+  EXPECT_EQ(cluster.label, u"label");
+  EXPECT_EQ(cluster.visits[1].url_for_display, u"url_for_display");
+  EXPECT_TRUE(cluster.keyword_to_data_map.empty());
 
   // Verify non-existent clusters aren't returned.
-  VerifyCluster(backend_->GetCluster(2), {0});
+  VerifyCluster(backend_->GetCluster(2, true), {0});
 }
 
 TEST_F(HistoryBackendTest, GetRedirectChainStart) {
diff --git a/components/history/core/browser/history_service.cc b/components/history/core/browser/history_service.cc
index 9680c91..930e844 100644
--- a/components/history/core/browser/history_service.cc
+++ b/components/history/core/browser/history_service.cc
@@ -317,7 +317,8 @@
   return tracker->PostTaskAndReplyWithResult(
       backend_task_runner_.get(), FROM_HERE,
       base::BindOnce(&HistoryBackend::GetMostRecentClusters, history_backend_,
-                     inclusive_min_time, exclusive_max_time, max_clusters),
+                     inclusive_min_time, exclusive_max_time, max_clusters,
+                     /*include_keywords=*/true),
       std::move(callback));
 }
 
diff --git a/components/history/core/browser/history_types.h b/components/history/core/browser/history_types.h
index 658e50d..b1fd3c9 100644
--- a/components/history/core/browser/history_types.h
+++ b/components/history/core/browser/history_types.h
@@ -991,7 +991,6 @@
   std::vector<ClusterVisit> visits;
 
   // A map of keywords to additional data.
-  // TODO(manukh): Persist to db.
   base::flat_map<std::u16string, ClusterKeywordData> keyword_to_data_map;
 
   // Whether the cluster should be shown prominently on UI surfaces.
diff --git a/components/history/core/browser/visit_annotations_database.cc b/components/history/core/browser/visit_annotations_database.cc
index ff77a11..e267eb5c 100644
--- a/components/history/core/browser/visit_annotations_database.cc
+++ b/components/history/core/browser/visit_annotations_database.cc
@@ -200,6 +200,17 @@
   if (!CreateClustersAndVisitsTableAndIndex())
     return false;
 
+  // Represents the one-to-many relationship of `Cluster`s and
+  // `ClusterKeywordData`s.
+  if (!GetDB().Execute("CREATE TABLE IF NOT EXISTS cluster_keywords("
+                       "cluster_id INTEGER NOT NULL,"
+                       "keyword VARCHAR NOT NULL,"
+                       "type INTEGER NOT NULL,"
+                       "score NUMERIC NOT NULL,"
+                       "collections VARCHAR NOT NULL)")) {
+    return false;
+  }
+
   return true;
 }
 
@@ -417,6 +428,11 @@
       "(cluster_id,visit_id,score,engagement_score,url_for_deduping,"
       "normalized_url,url_for_display)"
       "VALUES(?,?,?,?,?,?,?)"));
+  sql::Statement cluster_keywords_statement(
+      GetDB().GetCachedStatement(SQL_FROM_HERE,
+                                 "INSERT INTO cluster_keywords"
+                                 "(cluster_id,keyword,type,score,collections)"
+                                 "VALUES(?,?,?,?,?)"));
 
   for (const auto& cluster : clusters) {
     if (cluster.visits.empty())
@@ -456,6 +472,23 @@
             << "cluster_id = " << cluster_id << ", visit_id = " << visit_id;
       }
     });
+
+    // Insert each keyword into 'cluster_keywords'.
+    for (const auto& [keyword, keyword_data] : cluster.keyword_to_data_map) {
+      cluster_keywords_statement.Reset(true);
+      cluster_keywords_statement.BindInt64(0, cluster_id);
+      cluster_keywords_statement.BindString16(1, keyword);
+      cluster_keywords_statement.BindInt(2, keyword_data.type);
+      cluster_keywords_statement.BindDouble(3, keyword_data.score);
+      cluster_keywords_statement.BindString(
+          4, keyword_data.entity_collections.empty()
+                 ? ""
+                 : keyword_data.entity_collections[0]);
+      if (!cluster_keywords_statement.Run()) {
+        DVLOG(0) << "Failed to execute 'cluster_keywords' insert statement:  "
+                 << "cluster_id = " << cluster_id << ", keyword = " << keyword;
+      }
+    }
   }
 }
 
@@ -572,6 +605,27 @@
   return 0;
 }
 
+base::flat_map<std::u16string, ClusterKeywordData>
+VisitAnnotationsDatabase::GetClusterKeywords(int64_t cluster_id) {
+  DCHECK_GT(cluster_id, 0);
+  sql::Statement statement(
+      GetDB().GetCachedStatement(SQL_FROM_HERE,
+                                 "SELECT keyword,type,score,collections "
+                                 "FROM cluster_keywords "
+                                 "WHERE cluster_id=?"));
+  statement.BindInt64(0, cluster_id);
+
+  base::flat_map<std::u16string, ClusterKeywordData> keyword_data;
+  while (statement.Step()) {
+    keyword_data[statement.ColumnString16(0)] = {
+        static_cast<ClusterKeywordData::ClusterKeywordType>(
+            statement.ColumnInt(1)),
+        static_cast<float>(statement.ColumnDouble(2)),
+        DeserializeFromStringColumn(statement.ColumnString(3))};
+  }
+  return keyword_data;
+}
+
 void VisitAnnotationsDatabase::DeleteClusters(
     const std::vector<int64_t>& cluster_ids) {
   if (cluster_ids.empty())
@@ -583,6 +637,9 @@
   sql::Statement clusters_and_visits_statement(GetDB().GetCachedStatement(
       SQL_FROM_HERE, "DELETE FROM clusters_and_visits WHERE cluster_id=?"));
 
+  sql::Statement cluster_keywords_statement(GetDB().GetCachedStatement(
+      SQL_FROM_HERE, "DELETE FROM cluster_keywords WHERE cluster_id=?"));
+
   for (auto cluster_id : cluster_ids) {
     clusters_statement.Reset(true);
     clusters_statement.BindInt64(0, cluster_id);
@@ -597,6 +654,13 @@
       DVLOG(0) << "Failed to execute clusters_and_visits delete statement:  "
                << "cluster_id = " << cluster_id;
     }
+
+    cluster_keywords_statement.Reset(true);
+    cluster_keywords_statement.BindInt64(0, cluster_id);
+    if (!cluster_keywords_statement.Run()) {
+      DVLOG(0) << "Failed to execute cluster_keywords delete statement:  "
+               << "cluster_id = " << cluster_id;
+    }
   }
 }
 
diff --git a/components/history/core/browser/visit_annotations_database.h b/components/history/core/browser/visit_annotations_database.h
index 8a1a6464..471755c6 100644
--- a/components/history/core/browser/visit_annotations_database.h
+++ b/components/history/core/browser/visit_annotations_database.h
@@ -76,7 +76,8 @@
   // entries for any `Cluster` that it failed to add.
   void AddClusters(const std::vector<Cluster>& clusters);
 
-  // Get a `Cluster`.
+  // Get a `Cluster`. Does not include the cluster's `visits` or
+  // `keyword_to_data_map`.
   Cluster GetCluster(int64_t cluster_id);
 
   // Get the most recent clusters within the constraints. The most recent visit
@@ -95,6 +96,10 @@
   // is not in a cluster.`
   int64_t GetClusterIdContainingVisit(VisitID visit_id);
 
+  // Return the keyword data associated with `cluster_id`.
+  base::flat_map<std::u16string, ClusterKeywordData> GetClusterKeywords(
+      int64_t cluster_id);
+
   // Delete `Cluster`s from the table.
   void DeleteClusters(const std::vector<int64_t>& cluster_ids);
 
diff --git a/components/history/core/browser/visit_annotations_database_unittest.cc b/components/history/core/browser/visit_annotations_database_unittest.cc
index a629ea7..0c50330 100644
--- a/components/history/core/browser/visit_annotations_database_unittest.cc
+++ b/components/history/core/browser/visit_annotations_database_unittest.cc
@@ -216,7 +216,8 @@
   EXPECT_EQ(final.alternative_title, "New alternative title");
 }
 
-TEST_F(VisitAnnotationsDatabaseTest, AddClusters_GetCluster_GetClusterVisit) {
+TEST_F(VisitAnnotationsDatabaseTest,
+       AddClusters_GetCluster_GetClusterVisit_GetClusterKeywords) {
   // Test `AddClusters()`.
 
   // Cluster without visits shouldn't be added.
@@ -353,7 +354,10 @@
   AddContextAnnotationsForVisit(1, {});
   AddContentAnnotationsForVisit(2, {});
   AddContextAnnotationsForVisit(2, {});
-  AddCluster({1, 2});
+  auto cluster = CreateCluster({1, 2});
+  cluster.keyword_to_data_map[u"keyword1"];
+  cluster.keyword_to_data_map[u"keyword2"];
+  AddClusters({cluster});
 
   VisitContentAnnotations got_content_annotations;
   VisitContextAnnotations got_context_annotations;
@@ -362,10 +366,11 @@
   EXPECT_TRUE(GetContextAnnotationsForVisit(1, &got_context_annotations));
   EXPECT_TRUE(GetContentAnnotationsForVisit(2, &got_content_annotations));
   EXPECT_TRUE(GetContextAnnotationsForVisit(2, &got_context_annotations));
+  EXPECT_EQ(GetCluster(1).cluster_id, 1);
   EXPECT_THAT(GetVisitIdsInCluster(1), UnorderedElementsAre(1, 2));
   EXPECT_EQ(GetClusterIdContainingVisit(1), 1);
   EXPECT_EQ(GetClusterIdContainingVisit(2), 1);
-  EXPECT_EQ(GetCluster(1).cluster_id, 1);
+  EXPECT_EQ(GetClusterKeywords(1).size(), 2u);
 
   // Delete 1 visit. Make sure the tables are updated, but the cluster remains.
   DeleteAnnotationsForVisit(1);
@@ -373,10 +378,11 @@
   EXPECT_FALSE(GetContextAnnotationsForVisit(1, &got_context_annotations));
   EXPECT_TRUE(GetContentAnnotationsForVisit(2, &got_content_annotations));
   EXPECT_TRUE(GetContextAnnotationsForVisit(2, &got_context_annotations));
+  EXPECT_EQ(GetCluster(1).cluster_id, 1);
   EXPECT_THAT(GetVisitIdsInCluster(1), UnorderedElementsAre(2));
   EXPECT_EQ(GetClusterIdContainingVisit(1), 0);
   EXPECT_EQ(GetClusterIdContainingVisit(2), 1);
-  EXPECT_EQ(GetCluster(1).cluster_id, 1);
+  EXPECT_EQ(GetClusterKeywords(1).size(), 2u);
 
   // Delete the 2nd visit. Make sure the cluster is removed.
   DeleteAnnotationsForVisit(2);
@@ -384,31 +390,56 @@
   EXPECT_FALSE(GetContextAnnotationsForVisit(1, &got_context_annotations));
   EXPECT_FALSE(GetContentAnnotationsForVisit(2, &got_content_annotations));
   EXPECT_FALSE(GetContextAnnotationsForVisit(2, &got_context_annotations));
+  EXPECT_EQ(GetCluster(1).cluster_id, 0);
   EXPECT_TRUE(GetVisitIdsInCluster(1).empty());
   EXPECT_EQ(GetClusterIdContainingVisit(1), 0);
   EXPECT_EQ(GetClusterIdContainingVisit(2), 0);
-  EXPECT_EQ(GetCluster(1).cluster_id, 0);
+  EXPECT_EQ(GetClusterKeywords(1).size(), 0u);
 }
 
 TEST_F(VisitAnnotationsDatabaseTest, AddClusters_DeleteClusters) {
   AddClusters(CreateClusters({{3, 2, 5}, {3, 2, 5}, {6}}));
 
+  auto cluster_with_keyword_data = CreateCluster({10});
+  cluster_with_keyword_data.keyword_to_data_map[u"keyword1"];
+  cluster_with_keyword_data.keyword_to_data_map[u"keyword2"];
+  AddClusters({cluster_with_keyword_data});
+
+  EXPECT_EQ(GetCluster(1).cluster_id, 1);
+  EXPECT_EQ(GetCluster(2).cluster_id, 2);
+  EXPECT_EQ(GetCluster(3).cluster_id, 3);
+  EXPECT_EQ(GetCluster(4).cluster_id, 4);
   EXPECT_THAT(GetVisitIdsInCluster(1), ElementsAre(5, 3, 2));
   EXPECT_THAT(GetVisitIdsInCluster(2), ElementsAre(5, 3, 2));
   EXPECT_THAT(GetVisitIdsInCluster(3), ElementsAre(6));
+  EXPECT_THAT(GetVisitIdsInCluster(4), ElementsAre(10));
+  EXPECT_EQ(GetClusterKeywords(4).size(), 2u);
 
   DeleteClusters({});
 
+  EXPECT_EQ(GetCluster(1).cluster_id, 1);
+  EXPECT_EQ(GetCluster(2).cluster_id, 2);
+  EXPECT_EQ(GetCluster(3).cluster_id, 3);
+  EXPECT_EQ(GetCluster(4).cluster_id, 4);
   EXPECT_THAT(GetVisitIdsInCluster(1), ElementsAre(5, 3, 2));
   EXPECT_THAT(GetVisitIdsInCluster(2), ElementsAre(5, 3, 2));
   EXPECT_THAT(GetVisitIdsInCluster(3), ElementsAre(6));
+  EXPECT_THAT(GetVisitIdsInCluster(4), ElementsAre(10));
+  EXPECT_EQ(GetClusterKeywords(4).size(), 2u);
 
-  DeleteClusters({1, 3, 4});
+  DeleteClusters({1, 3, 4, 5});
 
+  EXPECT_EQ(GetCluster(1).cluster_id, 0);
+  EXPECT_EQ(GetCluster(2).cluster_id, 2);
+  EXPECT_EQ(GetCluster(3).cluster_id, 0);
+  EXPECT_EQ(GetCluster(4).cluster_id, 0);
+  EXPECT_EQ(GetCluster(5).cluster_id, 0);
   EXPECT_THAT(GetVisitIdsInCluster(1), ElementsAre());
   EXPECT_THAT(GetVisitIdsInCluster(2), ElementsAre(5, 3, 2));
   EXPECT_THAT(GetVisitIdsInCluster(3), ElementsAre());
   EXPECT_THAT(GetVisitIdsInCluster(4), ElementsAre());
+  EXPECT_THAT(GetVisitIdsInCluster(5), ElementsAre());
+  EXPECT_TRUE(GetClusterKeywords(4).empty());
 }
 
 }  // namespace history
diff --git a/components/live_caption/caption_bubble_controller.h b/components/live_caption/caption_bubble_controller.h
index f39d1ff..84ccb7e 100644
--- a/components/live_caption/caption_bubble_controller.h
+++ b/components/live_caption/caption_bubble_controller.h
@@ -13,6 +13,8 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/native_theme/caption_style.h"
 
+class PrefService;
+
 namespace content {
 class BrowserContext;
 }
@@ -37,7 +39,8 @@
   CaptionBubbleController(const CaptionBubbleController&) = delete;
   CaptionBubbleController& operator=(const CaptionBubbleController&) = delete;
 
-  static std::unique_ptr<CaptionBubbleController> Create();
+  static std::unique_ptr<CaptionBubbleController> Create(
+      PrefService* profile_prefs);
 
   // Called when a transcription is received from the service. Returns whether
   // the transcription result was set on the caption bubble successfully.
diff --git a/components/live_caption/live_caption_controller.cc b/components/live_caption/live_caption_controller.cc
index f6be626..f4c4fc7 100644
--- a/components/live_caption/live_caption_controller.cc
+++ b/components/live_caption/live_caption_controller.cc
@@ -57,6 +57,12 @@
 void LiveCaptionController::RegisterProfilePrefs(
     user_prefs::PrefRegistrySyncable* registry) {
   registry->RegisterBooleanPref(
+      prefs::kLiveCaptionBubbleExpanded, false,
+      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
+  registry->RegisterBooleanPref(
+      prefs::kLiveCaptionBubblePinned, false,
+      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
+  registry->RegisterBooleanPref(
       prefs::kLiveCaptionEnabled, false,
       user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
 
@@ -183,7 +189,7 @@
 
   is_ui_constructed_ = true;
 
-  caption_bubble_controller_ = CaptionBubbleController::Create();
+  caption_bubble_controller_ = CaptionBubbleController::Create(profile_prefs_);
   caption_bubble_controller_->UpdateCaptionStyle(caption_style_);
 
   // Observe native theme changes for caption style updates.
diff --git a/components/live_caption/pref_names.cc b/components/live_caption/pref_names.cc
index ce280928..de77b02 100644
--- a/components/live_caption/pref_names.cc
+++ b/components/live_caption/pref_names.cc
@@ -18,6 +18,14 @@
 namespace prefs {
 
 #if !defined(ANDROID)
+// Whether the Live Caption bubble is expanded.
+const char kLiveCaptionBubbleExpanded[] =
+    "accessibility.captions.live_caption_bubble_expanded";
+
+// Whether the Live Caption bubble is pinned.
+const char kLiveCaptionBubblePinned[] =
+    "accessibility.captions.live_caption_bubble_pinned";
+
 // Whether the Live Caption feature is enabled.
 const char kLiveCaptionEnabled[] =
     "accessibility.captions.live_caption_enabled";
diff --git a/components/live_caption/pref_names.h b/components/live_caption/pref_names.h
index 1596c8e..c8dbde3 100644
--- a/components/live_caption/pref_names.h
+++ b/components/live_caption/pref_names.h
@@ -20,6 +20,8 @@
 // Live Caption is not available on Android, so exclude these unneeded
 // kLiveCaption*  prefs.
 #if !defined(ANDROID)
+extern const char kLiveCaptionBubbleExpanded[];
+extern const char kLiveCaptionBubblePinned[];
 extern const char kLiveCaptionEnabled[];
 extern const char kLiveCaptionLanguageCode[];
 extern const char kLiveCaptionMediaFoundationRendererErrorSilenced[];
diff --git a/components/live_caption/views/caption_bubble.cc b/components/live_caption/views/caption_bubble.cc
index fe55750..6e914b40 100644
--- a/components/live_caption/views/caption_bubble.cc
+++ b/components/live_caption/views/caption_bubble.cc
@@ -18,6 +18,8 @@
 #include "base/timer/timer.h"
 #include "build/build_config.h"
 #include "components/live_caption/caption_bubble_context.h"
+#include "components/live_caption/pref_names.h"
+#include "components/prefs/pref_service.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/vector_icons/vector_icons.h"
 #include "third_party/re2/src/re2/re2.h"
@@ -402,8 +404,13 @@
 };
 #endif
 
-CaptionBubble::CaptionBubble(base::OnceClosure destroyed_callback)
-    : destroyed_callback_(std::move(destroyed_callback)),
+CaptionBubble::CaptionBubble(PrefService* profile_prefs,
+                             base::OnceClosure destroyed_callback)
+    : profile_prefs_(profile_prefs),
+      destroyed_callback_(std::move(destroyed_callback)),
+      is_expanded_(
+          profile_prefs_->GetBoolean(prefs::kLiveCaptionBubbleExpanded)),
+      is_pinned_(profile_prefs_->GetBoolean(prefs::kLiveCaptionBubblePinned)),
       tick_clock_(base::DefaultTickClock::GetInstance()) {
   // Bubbles that use transparent colors should not paint their ClientViews to a
   // layer as doing so could result in visual artifacts.
@@ -696,6 +703,7 @@
 
 void CaptionBubble::ExpandOrCollapseButtonPressed() {
   is_expanded_ = !is_expanded_;
+  profile_prefs_->SetBoolean(prefs::kLiveCaptionBubbleExpanded, is_expanded_);
   base::UmaHistogramBoolean("Accessibility.LiveCaption.ExpandBubble",
                             is_expanded_);
 
@@ -708,6 +716,7 @@
 
 void CaptionBubble::PinOrUnpinButtonPressed() {
   is_pinned_ = !is_pinned_;
+  profile_prefs_->SetBoolean(prefs::kLiveCaptionBubblePinned, is_pinned_);
   base::UmaHistogramBoolean("Accessibility.LiveCaption.PinBubble", is_pinned_);
 
   SwapButtons(unpin_button_, pin_button_, is_pinned_);
diff --git a/components/live_caption/views/caption_bubble.h b/components/live_caption/views/caption_bubble.h
index 6479597aa..6b045a1 100644
--- a/components/live_caption/views/caption_bubble.h
+++ b/components/live_caption/views/caption_bubble.h
@@ -13,6 +13,7 @@
 #include "base/memory/raw_ptr.h"
 #include "build/buildflag.h"
 #include "components/live_caption/views/caption_bubble_model.h"
+#include "components/prefs/pref_service.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/gfx/font_list.h"
 #include "ui/native_theme/caption_style.h"
@@ -67,7 +68,8 @@
 class CaptionBubble : public views::BubbleDialogDelegateView {
  public:
   METADATA_HEADER(CaptionBubble);
-  explicit CaptionBubble(base::OnceClosure destroyed_callback);
+  CaptionBubble(PrefService* profile_prefs,
+                base::OnceClosure destroyed_callback);
   CaptionBubble(const CaptionBubble&) = delete;
   CaptionBubble& operator=(const CaptionBubble&) = delete;
   ~CaptionBubble() override;
@@ -211,16 +213,17 @@
 
   absl::optional<ui::CaptionStyle> caption_style_;
   raw_ptr<CaptionBubbleModel> model_ = nullptr;
+  raw_ptr<PrefService> profile_prefs_;
 
   OnErrorClickedCallback error_clicked_callback_;
   OnDoNotShowAgainClickedCallback error_silenced_callback_;
   base::ScopedClosureRunner destroyed_callback_;
 
   // Whether the caption bubble is expanded to show more lines of text.
-  bool is_expanded_ = false;
+  bool is_expanded_;
 
   // Whether the caption bubble is pinned or if it should hide on inactivity.
-  bool is_pinned_ = false;
+  bool is_pinned_;
 
   bool has_been_shown_ = false;
 
diff --git a/components/live_caption/views/caption_bubble_controller_views.cc b/components/live_caption/views/caption_bubble_controller_views.cc
index ee2f8e3..9fdf63e 100644
--- a/components/live_caption/views/caption_bubble_controller_views.cc
+++ b/components/live_caption/views/caption_bubble_controller_views.cc
@@ -12,16 +12,20 @@
 #include "components/live_caption/live_caption_controller.h"
 #include "components/live_caption/views/caption_bubble.h"
 #include "components/live_caption/views/caption_bubble_model.h"
+#include "components/prefs/pref_service.h"
 
 namespace captions {
 
 // Static
-std::unique_ptr<CaptionBubbleController> CaptionBubbleController::Create() {
-  return std::make_unique<CaptionBubbleControllerViews>();
+std::unique_ptr<CaptionBubbleController> CaptionBubbleController::Create(
+    PrefService* profile_prefs) {
+  return std::make_unique<CaptionBubbleControllerViews>(profile_prefs);
 }
 
-CaptionBubbleControllerViews::CaptionBubbleControllerViews() {
+CaptionBubbleControllerViews::CaptionBubbleControllerViews(
+    PrefService* profile_prefs) {
   caption_bubble_ = new CaptionBubble(
+      profile_prefs,
       base::BindOnce(&CaptionBubbleControllerViews::OnCaptionBubbleDestroyed,
                      base::Unretained(this)));
   caption_widget_ =
diff --git a/components/live_caption/views/caption_bubble_controller_views.h b/components/live_caption/views/caption_bubble_controller_views.h
index 6a18fa0..4ab6784 100644
--- a/components/live_caption/views/caption_bubble_controller_views.h
+++ b/components/live_caption/views/caption_bubble_controller_views.h
@@ -12,6 +12,7 @@
 #include "base/memory/raw_ptr.h"
 #include "components/live_caption/caption_bubble_controller.h"
 #include "components/live_caption/views/caption_bubble.h"
+#include "components/prefs/pref_service.h"
 #include "media/mojo/mojom/speech_recognition.mojom.h"
 
 namespace views {
@@ -30,7 +31,7 @@
 //
 class CaptionBubbleControllerViews : public CaptionBubbleController {
  public:
-  explicit CaptionBubbleControllerViews();
+  explicit CaptionBubbleControllerViews(PrefService* profile_prefs);
   ~CaptionBubbleControllerViews() override;
   CaptionBubbleControllerViews(const CaptionBubbleControllerViews&) = delete;
   CaptionBubbleControllerViews& operator=(const CaptionBubbleControllerViews&) =
diff --git a/components/page_info/page_info.cc b/components/page_info/page_info.cc
index 93cada1..295235ff 100644
--- a/components/page_info/page_info.cc
+++ b/components/page_info/page_info.cc
@@ -1076,8 +1076,10 @@
           CONTENT_SETTING_DEFAULT,
           permissions::PermissionStatusSource::UNSPECIFIED);
       if (permissions::PermissionUtil::IsPermission(permission_info.type)) {
-        permission_result =
-            delegate_->GetPermissionStatus(permission_info.type, site_url_);
+        permission_result = delegate_->GetPermissionResult(
+            permissions::PermissionUtil::ContentSettingTypeToPermissionType(
+                permission_info.type),
+            url::Origin::Create(site_url_));
       } else if (permission_info.type ==
                  ContentSettingsType::FEDERATED_IDENTITY_API) {
         absl::optional<permissions::PermissionResult> embargo_result =
diff --git a/components/page_info/page_info_delegate.h b/components/page_info/page_info_delegate.h
index ee85dc55..f5ecdd27 100644
--- a/components/page_info/page_info_delegate.h
+++ b/components/page_info/page_info_delegate.h
@@ -17,6 +17,10 @@
 #include "components/safe_browsing/core/browser/password_protection/metrics_util.h"
 #include "components/security_state/core/security_state.h"
 
+namespace blink {
+enum class PermissionType;
+}
+
 namespace permissions {
 class ObjectPermissionContextBase;
 class PermissionDecisionAutoBlocker;
@@ -30,6 +34,10 @@
 class Event;
 }  // namespace ui
 
+namespace url {
+class Origin;
+}
+
 class HostContentSettingsMap;
 class StatefulSSLHostStateDelegate;
 
@@ -54,9 +62,9 @@
 #endif
   // Get permission status for the permission associated with ContentSetting of
   // type |type|.
-  virtual permissions::PermissionResult GetPermissionStatus(
-      ContentSettingsType type,
-      const GURL& site_url) = 0;
+  virtual permissions::PermissionResult GetPermissionResult(
+      blink::PermissionType permission,
+      const url::Origin& origin) = 0;
 #if !BUILDFLAG(IS_ANDROID)
   // Creates an infobars::ContentInfoBarManager and an InfoBarDelegate using it,
   // if possible. Returns true if an InfoBarDelegate was created, false
diff --git a/components/page_info/page_info_ui.cc b/components/page_info/page_info_ui.cc
index 6a8d5db..4859faca 100644
--- a/components/page_info/page_info_ui.cc
+++ b/components/page_info/page_info_ui.cc
@@ -762,7 +762,10 @@
         CONTENT_SETTING_DEFAULT,
         permissions::PermissionStatusSource::UNSPECIFIED);
     if (permissions::PermissionUtil::IsPermission(permission.type)) {
-      permission_result = delegate->GetPermissionStatus(permission.type);
+      blink::PermissionType permission_type =
+          permissions::PermissionUtil::ContentSettingTypeToPermissionType(
+              permission.type);
+      permission_result = delegate->GetPermissionResult(permission_type);
     } else if (permission.type == ContentSettingsType::FEDERATED_IDENTITY_API) {
       absl::optional<permissions::PermissionResult> embargo_result =
           delegate->GetEmbargoResult(permission.type);
diff --git a/components/page_info/page_info_ui_delegate.h b/components/page_info/page_info_ui_delegate.h
index 641db35..bcad2092 100644
--- a/components/page_info/page_info_ui_delegate.h
+++ b/components/page_info/page_info_ui_delegate.h
@@ -10,6 +10,10 @@
 #include "components/permissions/permission_result.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
+namespace blink {
+enum class PermissionType;
+}
+
 class PageInfoUiDelegate {
  public:
   virtual ~PageInfoUiDelegate() = default;
@@ -17,8 +21,8 @@
   virtual bool IsBlockAutoPlayEnabled() = 0;
   virtual bool IsMultipleTabsOpen() = 0;
 #endif
-  virtual permissions::PermissionResult GetPermissionStatus(
-      ContentSettingsType type) = 0;
+  virtual permissions::PermissionResult GetPermissionResult(
+      blink::PermissionType permission) = 0;
   virtual absl::optional<permissions::PermissionResult> GetEmbargoResult(
       ContentSettingsType type) = 0;
 };
diff --git a/components/payments/content/installable_payment_app_crawler.cc b/components/payments/content/installable_payment_app_crawler.cc
index 841e434..0797946 100644
--- a/components/payments/content/installable_payment_app_crawler.cc
+++ b/components/payments/content/installable_payment_app_crawler.cc
@@ -21,6 +21,7 @@
 #include "content/public/browser/manifest_icon_downloader.h"
 #include "content/public/browser/payment_app_provider_util.h"
 #include "content/public/browser/permission_controller.h"
+#include "content/public/browser/permission_result.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/web_contents.h"
@@ -185,10 +186,11 @@
       continue;
     }
 
-    if (permission_controller->GetPermissionStatusForOriginWithoutContext(
-            blink::PermissionType::PAYMENT_HANDLER,
-            url::Origin::Create(web_app_manifest_url)) !=
-        blink::mojom::PermissionStatus::GRANTED) {
+    if (permission_controller
+            ->GetPermissionResultForOriginWithoutContext(
+                blink::PermissionType::PAYMENT_HANDLER,
+                url::Origin::Create(web_app_manifest_url))
+            .status != blink::mojom::PermissionStatus::GRANTED) {
       // Do not download the web app manifest if it is blocked.
       continue;
     }
diff --git a/components/performance_manager/BUILD.gn b/components/performance_manager/BUILD.gn
index 2d58f6cb..da482ff 100644
--- a/components/performance_manager/BUILD.gn
+++ b/components/performance_manager/BUILD.gn
@@ -97,7 +97,6 @@
     "graph/worker_node_impl_describer.h",
     "graph_features.cc",
     "metrics/metrics_collector.cc",
-    "metrics/metrics_provider.cc",
     "owned_objects.h",
     "performance_manager.cc",
     "performance_manager_feature_observer_client.cc",
@@ -139,7 +138,6 @@
     "public/graph/worker_node.h",
     "public/metrics/background_metrics_reporter.h",
     "public/metrics/metrics_collector.h",
-    "public/metrics/metrics_provider.h",
     "public/performance_manager.h",
     "public/performance_manager_main_thread_mechanism.h",
     "public/performance_manager_main_thread_observer.h",
@@ -285,7 +283,6 @@
     "graph/worker_node_impl_unittest.cc",
     "graph_features_unittest.cc",
     "metrics/metrics_collector_unittest.cc",
-    "metrics/metrics_provider_unittest.cc",
     "owned_objects_unittest.cc",
     "performance_manager_impl_unittest.cc",
     "performance_manager_registry_impl_unittest.cc",
diff --git a/components/performance_manager/metrics/metrics_provider.cc b/components/performance_manager/metrics/metrics_provider.cc
deleted file mode 100644
index b39f80c..0000000
--- a/components/performance_manager/metrics/metrics_provider.cc
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2022 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/performance_manager/public/metrics/metrics_provider.h"
-
-#include "base/metrics/histogram_functions.h"
-#include "components/performance_manager/public/user_tuning/prefs.h"
-#include "components/prefs/pref_service.h"
-
-namespace performance_manager {
-
-MetricsProvider::MetricsProvider(PrefService* local_state)
-    : local_state_(local_state) {
-  pref_change_registrar_.Init(local_state_);
-  pref_change_registrar_.Add(
-      performance_manager::user_tuning::prefs::kHighEfficiencyModeEnabled,
-      base::BindRepeating(&MetricsProvider::OnEfficiencyModeChanged,
-                          base::Unretained(this)));
-  pref_change_registrar_.Add(
-      performance_manager::user_tuning::prefs::kBatterySaverModeEnabled,
-      base::BindRepeating(&MetricsProvider::OnEfficiencyModeChanged,
-                          base::Unretained(this)));
-
-  current_mode_ = ComputeCurrentMode();
-}
-
-MetricsProvider::~MetricsProvider() = default;
-
-void MetricsProvider::ProvideCurrentSessionData(
-    metrics::ChromeUserMetricsExtension* uma_proto) {
-  base::UmaHistogramEnumeration("PerformanceManager.UserTuning.EfficiencyMode",
-                                current_mode_);
-
-  // Set `current_mode_` to represent the state of the modes as they are now, so
-  // that this mode is what is adequately reported at the next report, unless it
-  // changes in the meantime.
-  current_mode_ = ComputeCurrentMode();
-}
-
-void MetricsProvider::OnEfficiencyModeChanged() {
-  EfficiencyMode new_mode = ComputeCurrentMode();
-
-  // If the mode changes between UMA reports, mark it as Mixed for this
-  // interval.
-  if (current_mode_ != new_mode) {
-    current_mode_ = EfficiencyMode::kMixed;
-  }
-}
-
-MetricsProvider::EfficiencyMode MetricsProvider::ComputeCurrentMode() const {
-  bool high_efficiency_enabled = local_state_->GetBoolean(
-      performance_manager::user_tuning::prefs::kHighEfficiencyModeEnabled);
-  bool battery_saver_enabled = local_state_->GetBoolean(
-      performance_manager::user_tuning::prefs::kBatterySaverModeEnabled);
-
-  if (high_efficiency_enabled && battery_saver_enabled) {
-    return EfficiencyMode::kBoth;
-  }
-
-  if (high_efficiency_enabled) {
-    return EfficiencyMode::kHighEfficiency;
-  }
-
-  if (battery_saver_enabled) {
-    return EfficiencyMode::kBatterySaver;
-  }
-
-  return EfficiencyMode::kNormal;
-}
-
-}  // namespace performance_manager
\ No newline at end of file
diff --git a/components/performance_manager/public/metrics/metrics_provider.h b/components/performance_manager/public/metrics/metrics_provider.h
deleted file mode 100644
index d940b9f..0000000
--- a/components/performance_manager/public/metrics/metrics_provider.h
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2022 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_METRICS_METRICS_PROVIDER_H_
-#define COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_METRICS_METRICS_PROVIDER_H_
-
-#include "base/memory/raw_ptr.h"
-#include "components/metrics/metrics_provider.h"
-
-#include "components/prefs/pref_change_registrar.h"
-
-class PrefService;
-
-namespace performance_manager {
-
-// A metrics provider to add some performance manager related metrics to the UMA
-// protos on each upload.
-class MetricsProvider : public metrics::MetricsProvider {
- public:
-  enum class EfficiencyMode {
-    // No efficiency mode for the entire upload window
-    kNormal = 0,
-    // In high efficiency mode for the entire upload window
-    kHighEfficiency = 1,
-    // In battery saver mode for the entire upload window
-    kBatterySaver = 2,
-    // Both modes enabled for the entire upload window
-    kBoth = 3,
-    // The modes were changed during the upload window
-    kMixed = 4,
-    // Max value, used in UMA histograms macros
-    kMaxValue = kMixed
-  };
-
-  explicit MetricsProvider(PrefService* local_state);
-  ~MetricsProvider() override;
-
-  // metrics::MetricsProvider:
-  // This is only called from UMA code but is public for testing.
-  void ProvideCurrentSessionData(
-      metrics::ChromeUserMetricsExtension* uma_proto) override;
-
- private:
-  void OnEfficiencyModeChanged();
-  EfficiencyMode ComputeCurrentMode() const;
-
-  PrefChangeRegistrar pref_change_registrar_;
-  const raw_ptr<PrefService> local_state_;
-  EfficiencyMode current_mode_ = EfficiencyMode::kNormal;
-};
-
-}  // namespace performance_manager
-
-#endif  // COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_METRICS_METRICS_PROVIDER_H_
diff --git a/components/permissions/contexts/geolocation_permission_context_unittest.cc b/components/permissions/contexts/geolocation_permission_context_unittest.cc
index 309c4aea5..8f3c281e 100644
--- a/components/permissions/contexts/geolocation_permission_context_unittest.cc
+++ b/components/permissions/contexts/geolocation_permission_context_unittest.cc
@@ -49,6 +49,7 @@
 #include "content/public/browser/notification_registrar.h"
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/permission_controller.h"
+#include "content/public/browser/permission_result.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/mock_render_process_host.h"
@@ -240,8 +241,9 @@
     const GURL& requesting_origin) {
   return browser_context()
       ->GetPermissionController()
-      ->GetPermissionStatusForOriginWithoutContext(
-          permission, url::Origin::Create(requesting_origin));
+      ->GetPermissionResultForOriginWithoutContext(
+          permission, url::Origin::Create(requesting_origin))
+      .status;
 }
 
 void GeolocationPermissionContextTests::PermissionResponse(
diff --git a/components/permissions/permission_manager.cc b/components/permissions/permission_manager.cc
index 96d7af03..04d25e1 100644
--- a/components/permissions/permission_manager.cc
+++ b/components/permissions/permission_manager.cc
@@ -28,6 +28,8 @@
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/web_contents.h"
 #include "third_party/blink/public/common/permissions/permission_utils.h"
+#include "third_party/blink/public/mojom/permissions/permission_status.mojom.h"
+#include "url/origin.h"
 
 using blink::PermissionType;
 using blink::mojom::PermissionStatus;
@@ -35,60 +37,11 @@
 namespace permissions {
 namespace {
 
-// Represents the possible methods of delegating permissions from main frames
-// to child frames.
-enum class PermissionDelegationMode {
-  // Permissions from the main frame are delegated to child frames.
-  // This is the default delegation mode for permissions. If a main frame was
-  // granted a permission that is delegated, its child frames will inherit that
-  // permission if allowed by the permissions policy.
-  kDelegated,
-  // Permissions from the main frame are not delegated to child frames.
-  // An undelegated permission will only be granted to a child frame if the
-  // child frame's origin was previously granted access to the permission when
-  // in a main frame.
-  kUndelegated,
-  // Permission access is a function of both the requesting and embedding
-  // origins.
-  kDoubleKeyed,
-};
-
-// Helper methods to convert ContentSetting to PermissionStatus and vice versa.
-PermissionStatus ContentSettingToPermissionStatus(ContentSetting setting) {
-  switch (setting) {
-    case CONTENT_SETTING_ALLOW:
-      return PermissionStatus::GRANTED;
-    case CONTENT_SETTING_BLOCK:
-      return PermissionStatus::DENIED;
-    case CONTENT_SETTING_ASK:
-      return PermissionStatus::ASK;
-    case CONTENT_SETTING_SESSION_ONLY:
-    case CONTENT_SETTING_DETECT_IMPORTANT_CONTENT:
-    case CONTENT_SETTING_DEFAULT:
-    case CONTENT_SETTING_NUM_SETTINGS:
-      break;
-  }
-
-  NOTREACHED();
-  return PermissionStatus::DENIED;
-}
-
-PermissionDelegationMode GetPermissionDelegationMode(
-    ContentSettingsType permission) {
-  // TODO(crbug.com/987654): Generalize this to other "background permissions",
-  // that is, permissions that can be used by a service worker. This includes
-  // durable storage, background sync, etc.
-  if (permission == ContentSettingsType::NOTIFICATIONS)
-    return PermissionDelegationMode::kUndelegated;
-  if (permission == ContentSettingsType::STORAGE_ACCESS)
-    return PermissionDelegationMode::kDoubleKeyed;
-  return PermissionDelegationMode::kDelegated;
-}
-
 void SubscriptionCallbackWrapper(
     base::OnceCallback<void(PermissionStatus)> callback,
     ContentSetting content_setting) {
-  std::move(callback).Run(ContentSettingToPermissionStatus(content_setting));
+  std::move(callback).Run(
+      PermissionUtil::ContentSettingToPermissionStatus(content_setting));
 }
 
 void PermissionStatusVectorCallbackWrapper(
@@ -97,7 +50,7 @@
   std::vector<PermissionStatus> permission_statuses;
   std::transform(content_settings.begin(), content_settings.end(),
                  back_inserter(permission_statuses),
-                 ContentSettingToPermissionStatus);
+                 PermissionUtil::ContentSettingToPermissionStatus);
   std::move(callback).Run(permission_statuses);
 }
 
@@ -107,7 +60,7 @@
       content::WebContents::FromRenderFrameHost(render_frame_host);
   DCHECK(web_contents);
 
-  if (PermissionsClient::Get()->DoOriginsMatchNewTabPage(
+  if (PermissionsClient::Get()->DoURLsMatchNewTabPage(
           requesting_origin,
           web_contents->GetLastCommittedURL().DeprecatedGetOriginAsURL())) {
     return web_contents->GetLastCommittedURL().DeprecatedGetOriginAsURL();
@@ -116,36 +69,6 @@
         render_frame_host->GetMainFrame());
   }
 }
-
-// If an iframed document/worker inherits a different StoragePartition from its
-// embedder than it would use if it were a main frame, we should block
-// undelegated permissions. Because permissions are scoped to BrowserContext
-// instead of StoragePartition, without this check the aforementioned iframe
-// would be given undelegated permissions if the user had granted its origin
-// access when it was loaded as a main frame.
-bool IsPermissionBlockedInPartition(
-    ContentSettingsType permission,
-    const GURL& requesting_origin,
-    content::RenderProcessHost* render_process_host) {
-  DCHECK(render_process_host);
-  switch (GetPermissionDelegationMode(permission)) {
-    case PermissionDelegationMode::kDelegated:
-      return false;
-    case PermissionDelegationMode::kDoubleKeyed:
-      return false;
-    case PermissionDelegationMode::kUndelegated:
-      // TODO(crbug.com/1312218): This will create |requesting_origin|'s home
-      // StoragePartition if it doesn't already exist. Given how
-      // StoragePartitions are used today, this shouldn't actually be a
-      // problem, but ideally we'd compare StoragePartitionConfigs.
-      content::StoragePartition* requesting_home_partition =
-          render_process_host->GetBrowserContext()->GetStoragePartitionForUrl(
-              requesting_origin);
-      return requesting_home_partition !=
-             render_process_host->GetStoragePartition();
-  }
-}
-
 }  // anonymous namespace
 
 class PermissionManager::PendingRequest {
@@ -250,59 +173,6 @@
   DCHECK(subscriptions_.IsEmpty());
 }
 
-GURL PermissionManager::GetCanonicalOrigin(ContentSettingsType permission,
-                                           const GURL& requesting_origin,
-                                           const GURL& embedding_origin) const {
-  absl::optional<GURL> override_origin =
-      PermissionsClient::Get()->OverrideCanonicalOrigin(requesting_origin,
-                                                        embedding_origin);
-  if (override_origin)
-    return override_origin.value();
-
-  switch (GetPermissionDelegationMode(permission)) {
-    case PermissionDelegationMode::kDelegated:
-      return embedding_origin;
-    case PermissionDelegationMode::kDoubleKeyed:
-    case PermissionDelegationMode::kUndelegated:
-      return requesting_origin;
-  }
-}
-
-PermissionResult PermissionManager::GetPermissionStatusDeprecated(
-    ContentSettingsType permission,
-    const GURL& requesting_origin,
-    const GURL& embedding_origin) {
-  DCHECK_EQ(requesting_origin, embedding_origin);
-
-  return GetPermissionStatusHelper(permission,
-                                   /*render_process_host=*/nullptr,
-                                   /*render_frame_host=*/nullptr,
-                                   requesting_origin, embedding_origin);
-}
-
-PermissionResult PermissionManager::GetPermissionStatusForDisplayOnSettingsUI(
-    ContentSettingsType permission,
-    const GURL& origin) {
-  return GetPermissionStatusHelper(permission,
-                                   /*render_process_host=*/nullptr,
-                                   /*render_frame_host=*/nullptr, origin,
-                                   origin);
-}
-
-PermissionResult PermissionManager::GetPermissionStatusForCurrentDocument(
-    ContentSettingsType permission,
-    content::RenderFrameHost* render_frame_host) {
-  const GURL requesting_origin =
-      PermissionUtil::GetLastCommittedOriginAsURL(render_frame_host);
-  const GURL embedding_origin =
-      GetEmbeddingOrigin(render_frame_host, requesting_origin);
-
-  return GetPermissionStatusHelper(permission,
-                                   /*render_process_host=*/nullptr,
-                                   render_frame_host, requesting_origin,
-                                   embedding_origin);
-}
-
 void PermissionManager::Shutdown() {
   is_shutting_down_ = true;
 
@@ -357,13 +227,13 @@
                                         const GURL& embedding_origin) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   ContentSettingsType type =
-      PermissionUtil::PermissionTypeToContentSetting(permission);
+      PermissionUtil::PermissionTypeToContentSettingType(permission);
   PermissionContextBase* context = GetPermissionContext(type);
   if (!context)
     return;
-  context->ResetPermission(
-      GetCanonicalOrigin(type, requesting_origin, embedding_origin),
-      embedding_origin.DeprecatedGetOriginAsURL());
+  context->ResetPermission(PermissionUtil::GetCanonicalOrigin(
+                               type, requesting_origin, embedding_origin),
+                           embedding_origin.DeprecatedGetOriginAsURL());
 }
 
 void PermissionManager::RequestPermissionsFromCurrentDocument(
@@ -377,7 +247,7 @@
   std::vector<ContentSettingsType> permissions;
   std::transform(permissions_types.begin(), permissions_types.end(),
                  back_inserter(permissions),
-                 PermissionUtil::PermissionTypeToContentSetting);
+                 PermissionUtil::PermissionTypeToContentSettingType);
 
   base::OnceCallback<void(const std::vector<ContentSetting>&)> callback =
       base::BindOnce(&PermissionStatusVectorCallbackWrapper,
@@ -402,13 +272,13 @@
 
   for (size_t i = 0; i < permissions.size(); ++i) {
     const ContentSettingsType permission = permissions[i];
-    const GURL canonical_requesting_origin =
-        GetCanonicalOrigin(permission, requesting_origin, embedding_origin);
+    const GURL canonical_requesting_origin = PermissionUtil::GetCanonicalOrigin(
+        permission, requesting_origin, embedding_origin);
 
     auto response_callback =
         std::make_unique<PermissionResponseCallback>(this, request_local_id, i);
-    if (IsPermissionBlockedInPartition(permission, requesting_origin,
-                                       render_frame_host->GetProcess())) {
+    if (PermissionUtil::IsPermissionBlockedInPartition(
+            permission, requesting_origin, render_frame_host->GetProcess())) {
       response_callback->OnPermissionsRequestResponseStatus(
           CONTENT_SETTING_BLOCK);
       continue;
@@ -440,31 +310,53 @@
   // TODO(benwells): split this into two functions, GetPermissionStatus and
   // GetPermissionStatusForPermissionsAPI.
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  PermissionResult result = GetPermissionStatusHelper(
-      PermissionUtil::PermissionTypeToContentSetting(permission),
+  PermissionResult result = GetPermissionStatusInternal(
+      PermissionUtil::PermissionTypeToContentSettingType(permission),
       /*render_process_host=*/nullptr,
       /*render_frame_host=*/nullptr, requesting_origin, embedding_origin);
-  return ContentSettingToPermissionStatus(result.content_setting);
+  return PermissionUtil::ContentSettingToPermissionStatus(
+      result.content_setting);
+}
+
+content::PermissionResult
+PermissionManager::GetPermissionResultForOriginWithoutContext(
+    blink::PermissionType permission,
+    const url::Origin& origin) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  PermissionResult result = GetPermissionStatusInternal(
+      PermissionUtil::PermissionTypeToContentSettingType(permission),
+      /*render_process_host=*/nullptr,
+      /*render_frame_host=*/nullptr, origin.GetURL(), origin.GetURL());
+
+  return PermissionUtil::ToContentPermissionResult(result);
 }
 
 PermissionStatus PermissionManager::GetPermissionStatusForCurrentDocument(
     PermissionType permission,
     content::RenderFrameHost* render_frame_host) {
+  return GetPermissionResultForCurrentDocument(permission, render_frame_host)
+      .status;
+}
+
+content::PermissionResult
+PermissionManager::GetPermissionResultForCurrentDocument(
+    blink::PermissionType permission,
+    content::RenderFrameHost* render_frame_host) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   ContentSettingsType type =
-      PermissionUtil::PermissionTypeToContentSetting(permission);
+      PermissionUtil::PermissionTypeToContentSettingType(permission);
 
   const GURL requesting_origin =
       PermissionUtil::GetLastCommittedOriginAsURL(render_frame_host);
   const GURL embedding_origin =
       GetEmbeddingOrigin(render_frame_host, requesting_origin);
 
-  PermissionResult result = GetPermissionStatusHelper(
+  PermissionResult result = GetPermissionStatusInternal(
       type,
       /*render_process_host=*/nullptr, render_frame_host, requesting_origin,
       embedding_origin);
 
-  return ContentSettingToPermissionStatus(result.content_setting);
+  return PermissionUtil::ToContentPermissionResult(result);
 }
 
 PermissionStatus PermissionManager::GetPermissionStatusForWorker(
@@ -473,19 +365,20 @@
     const GURL& worker_origin) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   ContentSettingsType type =
-      PermissionUtil::PermissionTypeToContentSetting(permission);
-  PermissionResult result = GetPermissionStatusHelper(
+      PermissionUtil::PermissionTypeToContentSettingType(permission);
+  PermissionResult result = GetPermissionStatusInternal(
       type, render_process_host,
       /*render_frame_host=*/nullptr, worker_origin, worker_origin);
 
-  return ContentSettingToPermissionStatus(result.content_setting);
+  return PermissionUtil::ContentSettingToPermissionStatus(
+      result.content_setting);
 }
 
 bool PermissionManager::IsPermissionOverridableByDevTools(
     PermissionType permission,
     const absl::optional<url::Origin>& origin) {
   ContentSettingsType type =
-      PermissionUtil::PermissionTypeToContentSettingSafe(permission);
+      PermissionUtil::PermissionTypeToContentSettingTypeSafe(permission);
   PermissionContextBase* context = GetPermissionContext(type);
 
   if (!context || context->IsPermissionKillSwitchOn())
@@ -508,7 +401,7 @@
     return SubscriptionId();
 
   ContentSettingsType content_type =
-      PermissionUtil::PermissionTypeToContentSetting(permission);
+      PermissionUtil::PermissionTypeToContentSettingType(permission);
   auto& type_count = subscription_type_counts_[content_type];
   if (type_count == 0) {
     PermissionContextBase* context = GetPermissionContext(content_type);
@@ -528,10 +421,10 @@
     subscription->render_frame_id = render_frame_host->GetRoutingID();
     subscription->render_process_id = render_frame_host->GetProcess()->GetID();
     subscription->current_value =
-        GetPermissionStatusHelper(content_type,
-                                  /*render_process_host=*/nullptr,
-                                  render_frame_host, requesting_origin,
-                                  embedding_origin)
+        GetPermissionStatusInternal(content_type,
+                                    /*render_process_host=*/nullptr,
+                                    render_frame_host, requesting_origin,
+                                    embedding_origin)
             .content_setting;
 
   } else {
@@ -540,15 +433,15 @@
     subscription->render_process_id =
         render_process_host ? render_process_host->GetID() : -1;
     subscription->current_value =
-        GetPermissionStatusHelper(content_type, render_process_host,
-                                  /*render_frame_host=*/nullptr,
-                                  requesting_origin, embedding_origin)
+        GetPermissionStatusInternal(content_type, render_process_host,
+                                    /*render_frame_host=*/nullptr,
+                                    requesting_origin, embedding_origin)
             .content_setting;
   }
 
   subscription->permission = content_type;
-  subscription->requesting_origin =
-      GetCanonicalOrigin(content_type, requesting_origin, embedding_origin);
+  subscription->requesting_origin = PermissionUtil::GetCanonicalOrigin(
+      content_type, requesting_origin, embedding_origin);
   subscription->callback =
       base::BindRepeating(&SubscriptionCallbackWrapper, std::move(callback));
 
@@ -635,9 +528,9 @@
                   subscription->render_process_id);
 
     ContentSetting new_value =
-        GetPermissionStatusHelper(subscription->permission, rph, rfh,
-                                  subscription->requesting_origin,
-                                  embedding_origin)
+        GetPermissionStatusInternal(subscription->permission, rph, rfh,
+                                    subscription->requesting_origin,
+                                    embedding_origin)
             .content_setting;
 
     if (subscription->current_value == new_value)
@@ -654,7 +547,7 @@
     std::move(callback).Run();
 }
 
-PermissionResult PermissionManager::GetPermissionStatusHelper(
+PermissionResult PermissionManager::GetPermissionStatusInternal(
     ContentSettingsType permission,
     content::RenderProcessHost* render_process_host,
     content::RenderFrameHost* render_frame_host,
@@ -665,14 +558,14 @@
   // TODO(crbug.com/1307044): Move this to PermissionContextBase.
   content::RenderProcessHost* rph =
       render_frame_host ? render_frame_host->GetProcess() : render_process_host;
-  if (rph &&
-      IsPermissionBlockedInPartition(permission, requesting_origin, rph)) {
+  if (rph && PermissionUtil::IsPermissionBlockedInPartition(
+                 permission, requesting_origin, rph)) {
     return PermissionResult(CONTENT_SETTING_BLOCK,
                             PermissionStatusSource::UNSPECIFIED);
   }
 
-  GURL canonical_requesting_origin =
-      GetCanonicalOrigin(permission, requesting_origin, embedding_origin);
+  GURL canonical_requesting_origin = PermissionUtil::GetCanonicalOrigin(
+      permission, requesting_origin, embedding_origin);
   auto status = GetPermissionOverrideForDevTools(
       url::Origin::Create(canonical_requesting_origin), permission);
   if (status != CONTENT_SETTING_DEFAULT)
@@ -711,7 +604,7 @@
   ContentSettingsTypeOverrides result;
   for (const auto& item : overrides) {
     ContentSettingsType content_setting =
-        PermissionUtil::PermissionTypeToContentSettingSafe(item.first);
+        PermissionUtil::PermissionTypeToContentSettingTypeSafe(item.first);
     if (content_setting != ContentSettingsType::DEFAULT)
       result[content_setting] =
           PermissionUtil::PermissionStatusToContentSetting(item.second);
diff --git a/components/permissions/permission_manager.h b/components/permissions/permission_manager.h
index bace981..ed0b5ba 100644
--- a/components/permissions/permission_manager.h
+++ b/components/permissions/permission_manager.h
@@ -19,6 +19,7 @@
 #include "components/permissions/permission_request_id.h"
 #include "components/permissions/permission_util.h"
 #include "content/public/browser/permission_controller_delegate.h"
+#include "content/public/browser/permission_result.h"
 #include "url/origin.h"
 
 namespace blink {
@@ -55,42 +56,6 @@
 
   ~PermissionManager() override;
 
-  // Converts from |url|'s actual origin to the "canonical origin" that should
-  // be used for the purpose of requesting/storing permissions. For example, the
-  // origin of the local NTP gets mapped to the Google base URL instead. With
-  // Permission Delegation it will transform the requesting origin into
-  // the embedding origin because all permission checks happen on the top level
-  // origin.
-  //
-  // All the public methods below, such as RequestPermission or
-  // GetPermissionStatus, take the actual origin and do the canonicalization
-  // internally. You only need to call this directly if you do something else
-  // with the origin, such as display it in the UI.
-  GURL GetCanonicalOrigin(ContentSettingsType permission,
-                          const GURL& requesting_origin,
-                          const GURL& embedding_origin) const;
-
-  // This method is deprecated. Use `GetPermissionStatusForCurrentDocument`
-  // instead or `GetPermissionStatusForDisplayOnSettingsUI`.
-  PermissionResult GetPermissionStatusDeprecated(ContentSettingsType permission,
-                                                 const GURL& requesting_origin,
-                                                 const GURL& embedding_origin);
-
-  // Returns the permission status for a given `permission` and displayed,
-  // top-level `origin`. This should be used only for displaying on the
-  // browser's native UI (PageInfo, Settings, etc.). This method does not take
-  // context specific restrictions (e.g. permission policy) into consideration.
-  PermissionResult GetPermissionStatusForDisplayOnSettingsUI(
-      ContentSettingsType permission,
-      const GURL& origin);
-
-  // Returns the status for the given `permission` on behalf of the last
-  // committed document in `render_frame_host`, also performing additional
-  // checks such as Permission Policy.
-  PermissionResult GetPermissionStatusForCurrentDocument(
-      ContentSettingsType permission,
-      content::RenderFrameHost* render_frame_host);
-
   // KeyedService implementation.
   void Shutdown() override;
 
@@ -154,9 +119,15 @@
       blink::PermissionType permission,
       const GURL& requesting_origin,
       const GURL& embedding_origin) override;
+  content::PermissionResult GetPermissionResultForOriginWithoutContext(
+      blink::PermissionType permission,
+      const url::Origin& origin) override;
   blink::mojom::PermissionStatus GetPermissionStatusForCurrentDocument(
       blink::PermissionType permission,
       content::RenderFrameHost* render_frame_host) override;
+  content::PermissionResult GetPermissionResultForCurrentDocument(
+      blink::PermissionType permission,
+      content::RenderFrameHost* render_frame_host) override;
   blink::mojom::PermissionStatus GetPermissionStatusForWorker(
       blink::PermissionType permission,
       content::RenderProcessHost* render_process_host,
@@ -192,7 +163,7 @@
 
   // Only one of |render_process_host| and |render_frame_host| should be set,
   // or neither. RenderProcessHost will be inferred from |render_frame_host|.
-  PermissionResult GetPermissionStatusHelper(
+  PermissionResult GetPermissionStatusInternal(
       ContentSettingsType permission,
       content::RenderProcessHost* render_process_host,
       content::RenderFrameHost* render_frame_host,
diff --git a/components/permissions/permission_manager_unittest.cc b/components/permissions/permission_manager_unittest.cc
index a4e3f51..a277dff9 100644
--- a/components/permissions/permission_manager_unittest.cc
+++ b/components/permissions/permission_manager_unittest.cc
@@ -17,10 +17,12 @@
 #include "components/permissions/permission_context_base.h"
 #include "components/permissions/permission_request_manager.h"
 #include "components/permissions/permission_result.h"
+#include "components/permissions/permission_util.h"
 #include "components/permissions/test/mock_permission_prompt_factory.h"
 #include "components/permissions/test/permission_test_util.h"
 #include "components/permissions/test/test_permissions_client.h"
 #include "content/public/browser/content_browser_client.h"
+#include "content/public/browser/permission_result.h"
 #include "content/public/common/content_client.h"
 #include "content/public/test/browser_task_environment.h"
 #include "content/public/test/mock_render_process_host.h"
@@ -82,8 +84,8 @@
 auto GetDefaultProtectedMediaIdentifierContentSetting() {
   return base::android::BuildInfo::GetInstance()->sdk_int() >=
                  base::android::SDK_VERSION_MARSHMALLOW
-             ? CONTENT_SETTING_ALLOW
-             : CONTENT_SETTING_ASK;
+             ? PermissionStatus::GRANTED
+             : PermissionStatus::ASK;
 }
 #endif  // BUILDFLAG(IS_ANDROID)
 
@@ -113,31 +115,41 @@
   }
 
   void CheckPermissionStatus(PermissionType type, PermissionStatus expected) {
-    EXPECT_EQ(expected, GetPermissionManager()->GetPermissionStatus(
-                            type, url_.DeprecatedGetOriginAsURL(),
-                            url_.DeprecatedGetOriginAsURL()));
+    EXPECT_EQ(expected, GetPermissionManager()
+                            ->GetPermissionResultForOriginWithoutContext(
+                                type, url::Origin::Create(url_))
+                            .status);
   }
 
-  void CheckPermissionResult(ContentSettingsType type,
-                             ContentSetting expected_status,
-                             PermissionStatusSource expected_status_source) {
-    PermissionResult result =
-        GetPermissionManager()->GetPermissionStatusDeprecated(
-            type, url_.DeprecatedGetOriginAsURL(),
-            url_.DeprecatedGetOriginAsURL());
-    EXPECT_EQ(expected_status, result.content_setting);
+  void CheckPermissionResult(
+      PermissionType type,
+      PermissionStatus expected_status,
+      content::PermissionStatusSource expected_status_source) {
+    content::PermissionResult result =
+        GetPermissionManager()->GetPermissionResultForOriginWithoutContext(
+            type, url::Origin::Create(url_));
+    EXPECT_EQ(expected_status, result.status);
     EXPECT_EQ(expected_status_source, result.source);
   }
 
-  void SetPermission(ContentSettingsType type, ContentSetting value) {
-    SetPermission(url_, type, value);
+  void SetPermission(PermissionType type, PermissionStatus value) {
+    SetPermission(url_, url_, type, value);
   }
 
   void SetPermission(const GURL& origin,
-                     ContentSettingsType type,
-                     ContentSetting value) {
-    GetHostContentSettingsMap()->SetContentSettingDefaultScope(origin, origin,
-                                                               type, value);
+                     PermissionType type,
+                     PermissionStatus value) {
+    SetPermission(origin, origin, type, value);
+  }
+
+  void SetPermission(const GURL& requesting_origin,
+                     const GURL& embedding_origin,
+                     PermissionType type,
+                     PermissionStatus value) {
+    GetHostContentSettingsMap()->SetContentSettingDefaultScope(
+        requesting_origin, embedding_origin,
+        permissions::PermissionUtil::PermissionTypeToContentSettingType(type),
+        permissions::PermissionUtil::PermissionStatusToContentSetting(value));
   }
 
   void RequestPermissionFromCurrentDocument(PermissionType type,
@@ -181,6 +193,13 @@
         permission, render_frame_host);
   }
 
+  content::PermissionResult GetPermissionResultForCurrentDocument(
+      PermissionType permission,
+      content::RenderFrameHost* render_frame_host) {
+    return GetPermissionManager()->GetPermissionResultForCurrentDocument(
+        permission, render_frame_host);
+  }
+
   PermissionStatus GetPermissionStatusForWorker(
       PermissionType permission,
       content::RenderProcessHost* render_process_host,
@@ -324,66 +343,66 @@
 }
 
 TEST_F(PermissionManagerTest, GetPermissionStatusAfterSet) {
-  SetPermission(ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
+  SetPermission(PermissionType::GEOLOCATION, PermissionStatus::GRANTED);
   CheckPermissionStatus(PermissionType::GEOLOCATION, PermissionStatus::GRANTED);
 
-  SetPermission(ContentSettingsType::NOTIFICATIONS, CONTENT_SETTING_ALLOW);
+  SetPermission(PermissionType::NOTIFICATIONS, PermissionStatus::GRANTED);
   CheckPermissionStatus(PermissionType::NOTIFICATIONS,
                         PermissionStatus::GRANTED);
 
-  SetPermission(ContentSettingsType::MIDI_SYSEX, CONTENT_SETTING_ALLOW);
+  SetPermission(PermissionType::MIDI_SYSEX, PermissionStatus::GRANTED);
   CheckPermissionStatus(PermissionType::MIDI_SYSEX, PermissionStatus::GRANTED);
 
 #if BUILDFLAG(IS_ANDROID)
-  SetPermission(ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER,
-                CONTENT_SETTING_ALLOW);
+  SetPermission(PermissionType::PROTECTED_MEDIA_IDENTIFIER,
+                PermissionStatus::GRANTED);
   CheckPermissionStatus(PermissionType::PROTECTED_MEDIA_IDENTIFIER,
                         PermissionStatus::GRANTED);
 
-  SetPermission(ContentSettingsType::WINDOW_PLACEMENT, CONTENT_SETTING_ALLOW);
+  SetPermission(PermissionType::WINDOW_PLACEMENT, PermissionStatus::GRANTED);
   CheckPermissionStatus(PermissionType::WINDOW_PLACEMENT,
                         PermissionStatus::DENIED);
 #else
-  SetPermission(ContentSettingsType::WINDOW_PLACEMENT, CONTENT_SETTING_ALLOW);
+  SetPermission(PermissionType::WINDOW_PLACEMENT, PermissionStatus::GRANTED);
   CheckPermissionStatus(PermissionType::WINDOW_PLACEMENT,
                         PermissionStatus::GRANTED);
 #endif
 }
 
 TEST_F(PermissionManagerTest, CheckPermissionResultDefault) {
-  CheckPermissionResult(ContentSettingsType::MIDI_SYSEX, CONTENT_SETTING_ASK,
-                        PermissionStatusSource::UNSPECIFIED);
-  CheckPermissionResult(ContentSettingsType::NOTIFICATIONS, CONTENT_SETTING_ASK,
-                        PermissionStatusSource::UNSPECIFIED);
-  CheckPermissionResult(ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ASK,
-                        PermissionStatusSource::UNSPECIFIED);
+  CheckPermissionResult(PermissionType::MIDI_SYSEX, PermissionStatus::ASK,
+                        content::PermissionStatusSource::UNSPECIFIED);
+  CheckPermissionResult(PermissionType::NOTIFICATIONS, PermissionStatus::ASK,
+                        content::PermissionStatusSource::UNSPECIFIED);
+  CheckPermissionResult(PermissionType::GEOLOCATION, PermissionStatus::ASK,
+                        content::PermissionStatusSource::UNSPECIFIED);
 #if BUILDFLAG(IS_ANDROID)
-  CheckPermissionResult(ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER,
+  CheckPermissionResult(PermissionType::PROTECTED_MEDIA_IDENTIFIER,
                         GetDefaultProtectedMediaIdentifierContentSetting(),
-                        PermissionStatusSource::UNSPECIFIED);
+                        content::PermissionStatusSource::UNSPECIFIED);
 #endif
 }
 
 TEST_F(PermissionManagerTest, CheckPermissionResultAfterSet) {
-  SetPermission(ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
-  CheckPermissionResult(ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW,
-                        PermissionStatusSource::UNSPECIFIED);
+  SetPermission(PermissionType::GEOLOCATION, PermissionStatus::GRANTED);
+  CheckPermissionResult(PermissionType::GEOLOCATION, PermissionStatus::GRANTED,
+                        content::PermissionStatusSource::UNSPECIFIED);
 
-  SetPermission(ContentSettingsType::NOTIFICATIONS, CONTENT_SETTING_ALLOW);
-  CheckPermissionResult(ContentSettingsType::NOTIFICATIONS,
-                        CONTENT_SETTING_ALLOW,
-                        PermissionStatusSource::UNSPECIFIED);
+  SetPermission(PermissionType::NOTIFICATIONS, PermissionStatus::GRANTED);
+  CheckPermissionResult(PermissionType::NOTIFICATIONS,
+                        PermissionStatus::GRANTED,
+                        content::PermissionStatusSource::UNSPECIFIED);
 
-  SetPermission(ContentSettingsType::MIDI_SYSEX, CONTENT_SETTING_ALLOW);
-  CheckPermissionResult(ContentSettingsType::MIDI_SYSEX, CONTENT_SETTING_ALLOW,
-                        PermissionStatusSource::UNSPECIFIED);
+  SetPermission(PermissionType::MIDI_SYSEX, PermissionStatus::GRANTED);
+  CheckPermissionResult(PermissionType::MIDI_SYSEX, PermissionStatus::GRANTED,
+                        content::PermissionStatusSource::UNSPECIFIED);
 
 #if BUILDFLAG(IS_ANDROID)
-  SetPermission(ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER,
-                CONTENT_SETTING_ALLOW);
-  CheckPermissionResult(ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER,
-                        CONTENT_SETTING_ALLOW,
-                        PermissionStatusSource::UNSPECIFIED);
+  SetPermission(PermissionType::PROTECTED_MEDIA_IDENTIFIER,
+                PermissionStatus::GRANTED);
+  CheckPermissionResult(PermissionType::PROTECTED_MEDIA_IDENTIFIER,
+                        PermissionStatus::GRANTED,
+                        content::PermissionStatusSource::UNSPECIFIED);
 #endif
 }
 
@@ -431,8 +450,7 @@
           base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
                               base::Unretained(this)));
 
-  GetHostContentSettingsMap()->SetContentSettingDefaultScope(
-      url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
+  SetPermission(PermissionType::GEOLOCATION, PermissionStatus::GRANTED);
 
   EXPECT_TRUE(callback_called());
   EXPECT_EQ(PermissionStatus::GRANTED, callback_result());
@@ -448,8 +466,8 @@
           base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
                               base::Unretained(this)));
 
-  GetHostContentSettingsMap()->SetContentSettingDefaultScope(
-      url(), GURL(), ContentSettingsType::NOTIFICATIONS, CONTENT_SETTING_ALLOW);
+  SetPermission(url(), GURL(), PermissionType::NOTIFICATIONS,
+                PermissionStatus::GRANTED);
 
   EXPECT_FALSE(callback_called());
 
@@ -466,8 +484,8 @@
 
   UnsubscribePermissionStatusChange(subscription_id);
 
-  GetHostContentSettingsMap()->SetContentSettingDefaultScope(
-      url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
+  SetPermission(url(), url(), PermissionType::GEOLOCATION,
+                PermissionStatus::GRANTED);
 
   EXPECT_FALSE(callback_called());
 }
@@ -489,8 +507,8 @@
 
   UnsubscribePermissionStatusChange(subscription_id);
 
-  GetHostContentSettingsMap()->SetContentSettingDefaultScope(
-      url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
+  SetPermission(url(), url(), PermissionType::GEOLOCATION,
+                PermissionStatus::GRANTED);
 
   EXPECT_EQ(callback_count(), 1);
 }
@@ -503,9 +521,8 @@
           base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
                               base::Unretained(this)));
 
-  GetHostContentSettingsMap()->SetContentSettingDefaultScope(
-      other_url(), url(), ContentSettingsType::GEOLOCATION,
-      CONTENT_SETTING_ALLOW);
+  SetPermission(other_url(), url(), PermissionType::GEOLOCATION,
+                PermissionStatus::GRANTED);
 
   EXPECT_FALSE(callback_called());
 
@@ -520,9 +537,8 @@
           base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
                               base::Unretained(this)));
 
-  GetHostContentSettingsMap()->SetContentSettingDefaultScope(
-      url(), other_url(), ContentSettingsType::STORAGE_ACCESS,
-      CONTENT_SETTING_ALLOW);
+  SetPermission(url(), other_url(), PermissionType::STORAGE_ACCESS_GRANT,
+                PermissionStatus::GRANTED);
 
   EXPECT_FALSE(callback_called());
 
@@ -547,8 +563,8 @@
 }
 
 TEST_F(PermissionManagerTest, ClearSettingsNotifies) {
-  GetHostContentSettingsMap()->SetContentSettingDefaultScope(
-      url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
+  SetPermission(url(), url(), PermissionType::GEOLOCATION,
+                PermissionStatus::GRANTED);
 
   content::PermissionControllerDelegate::SubscriptionId subscription_id =
       SubscribePermissionStatusChange(
@@ -574,8 +590,7 @@
           base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
                               base::Unretained(this)));
 
-  GetHostContentSettingsMap()->SetContentSettingDefaultScope(
-      url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_BLOCK);
+  SetPermission(PermissionType::GEOLOCATION, PermissionStatus::DENIED);
 
   EXPECT_TRUE(callback_called());
   EXPECT_EQ(PermissionStatus::DENIED, callback_result());
@@ -584,8 +599,7 @@
 }
 
 TEST_F(PermissionManagerTest, ChangeWithoutPermissionChangeDoesNotNotify) {
-  GetHostContentSettingsMap()->SetContentSettingDefaultScope(
-      url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
+  SetPermission(PermissionType::GEOLOCATION, PermissionStatus::GRANTED);
 
   content::PermissionControllerDelegate::SubscriptionId subscription_id =
       SubscribePermissionStatusChange(
@@ -594,8 +608,8 @@
           base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
                               base::Unretained(this)));
 
-  GetHostContentSettingsMap()->SetContentSettingDefaultScope(
-      url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
+  SetPermission(url(), url(), PermissionType::GEOLOCATION,
+                PermissionStatus::GRANTED);
 
   EXPECT_FALSE(callback_called());
 
@@ -603,8 +617,8 @@
 }
 
 TEST_F(PermissionManagerTest, ChangesBackAndForth) {
-  GetHostContentSettingsMap()->SetContentSettingDefaultScope(
-      url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ASK);
+  SetPermission(url(), url(), PermissionType::GEOLOCATION,
+                PermissionStatus::ASK);
 
   content::PermissionControllerDelegate::SubscriptionId subscription_id =
       SubscribePermissionStatusChange(
@@ -613,16 +627,15 @@
           base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
                               base::Unretained(this)));
 
-  GetHostContentSettingsMap()->SetContentSettingDefaultScope(
-      url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
+  SetPermission(url(), url(), PermissionType::GEOLOCATION,
+                PermissionStatus::GRANTED);
 
   EXPECT_TRUE(callback_called());
   EXPECT_EQ(PermissionStatus::GRANTED, callback_result());
 
   Reset();
 
-  GetHostContentSettingsMap()->SetContentSettingDefaultScope(
-      url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ASK);
+  SetPermission(PermissionType::GEOLOCATION, PermissionStatus::ASK);
 
   EXPECT_TRUE(callback_called());
   EXPECT_EQ(PermissionStatus::ASK, callback_result());
@@ -631,8 +644,7 @@
 }
 
 TEST_F(PermissionManagerTest, ChangesBackAndForthWorker) {
-  GetHostContentSettingsMap()->SetContentSettingDefaultScope(
-      url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ASK);
+  SetPermission(PermissionType::GEOLOCATION, PermissionStatus::ASK);
 
   content::PermissionControllerDelegate::SubscriptionId subscription_id =
       SubscribePermissionStatusChange(
@@ -641,16 +653,14 @@
           base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
                               base::Unretained(this)));
 
-  GetHostContentSettingsMap()->SetContentSettingDefaultScope(
-      url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
+  SetPermission(PermissionType::GEOLOCATION, PermissionStatus::GRANTED);
 
   EXPECT_TRUE(callback_called());
   EXPECT_EQ(PermissionStatus::GRANTED, callback_result());
 
   Reset();
 
-  GetHostContentSettingsMap()->SetContentSettingDefaultScope(
-      url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ASK);
+  SetPermission(PermissionType::GEOLOCATION, PermissionStatus::ASK);
 
   EXPECT_TRUE(callback_called());
   EXPECT_EQ(PermissionStatus::ASK, callback_result());
@@ -667,8 +677,7 @@
                               base::Unretained(this)));
 
   CheckPermissionStatus(PermissionType::GEOLOCATION, PermissionStatus::ASK);
-  GetHostContentSettingsMap()->SetContentSettingDefaultScope(
-      url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
+  SetPermission(PermissionType::GEOLOCATION, PermissionStatus::GRANTED);
   CheckPermissionStatus(PermissionType::GEOLOCATION, PermissionStatus::GRANTED);
 
   EXPECT_FALSE(callback_called());
@@ -702,22 +711,20 @@
   GURL insecure_frame("http://www.example.com/geolocation");
   NavigateAndCommit(insecure_frame);
 
-  PermissionResult result =
-      GetPermissionManager()->GetPermissionStatusForCurrentDocument(
-          ContentSettingsType::GEOLOCATION,
-          web_contents()->GetPrimaryMainFrame());
+  content::PermissionResult result = GetPermissionResultForCurrentDocument(
+      PermissionType::GEOLOCATION, web_contents()->GetPrimaryMainFrame());
 
-  EXPECT_EQ(CONTENT_SETTING_BLOCK, result.content_setting);
-  EXPECT_EQ(PermissionStatusSource::INSECURE_ORIGIN, result.source);
+  EXPECT_EQ(PermissionStatus::DENIED, result.status);
+  EXPECT_EQ(content::PermissionStatusSource::INSECURE_ORIGIN, result.source);
 
   GURL secure_frame("https://www.example.com/geolocation");
   NavigateAndCommit(secure_frame);
 
-  result = GetPermissionManager()->GetPermissionStatusForCurrentDocument(
-      ContentSettingsType::GEOLOCATION, web_contents()->GetPrimaryMainFrame());
+  result = GetPermissionResultForCurrentDocument(
+      PermissionType::GEOLOCATION, web_contents()->GetPrimaryMainFrame());
 
-  EXPECT_EQ(CONTENT_SETTING_ASK, result.content_setting);
-  EXPECT_EQ(PermissionStatusSource::UNSPECIFIED, result.source);
+  EXPECT_EQ(PermissionStatus::ASK, result.status);
+  EXPECT_EQ(content::PermissionStatusSource::UNSPECIFIED, result.source);
 }
 
 TEST_F(PermissionManagerTest, InsecureOriginIsNotOverridable) {
@@ -766,7 +773,7 @@
 TEST_F(PermissionManagerTest, ResetPermission) {
 #if BUILDFLAG(IS_ANDROID)
   CheckPermissionStatus(PermissionType::NOTIFICATIONS, PermissionStatus::ASK);
-  SetPermission(ContentSettingsType::NOTIFICATIONS, CONTENT_SETTING_ALLOW);
+  SetPermission(PermissionType::NOTIFICATIONS, PermissionStatus::GRANTED);
   CheckPermissionStatus(PermissionType::NOTIFICATIONS,
                         PermissionStatus::GRANTED);
 
@@ -886,8 +893,7 @@
                                           PermissionType::GEOLOCATION, child));
 
   // Allow access for the top level origin.
-  GetHostContentSettingsMap()->SetContentSettingDefaultScope(
-      url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
+  SetPermission(PermissionType::GEOLOCATION, PermissionStatus::GRANTED);
 
   // The child's permission should still be block and no callback should be run.
   EXPECT_EQ(PermissionStatus::DENIED, GetPermissionStatusForCurrentDocument(
@@ -911,8 +917,7 @@
 
   // Blocking access to the parent should trigger the callback to be run for the
   // child also.
-  GetHostContentSettingsMap()->SetContentSettingDefaultScope(
-      url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_BLOCK);
+  SetPermission(PermissionType::GEOLOCATION, PermissionStatus::DENIED);
 
   EXPECT_TRUE(callback_called());
   EXPECT_EQ(PermissionStatus::DENIED, callback_result());
@@ -935,8 +940,7 @@
                               base::Unretained(this)));
   EXPECT_EQ(callback_count(), 0);
 
-  GetHostContentSettingsMap()->SetContentSettingDefaultScope(
-      url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
+  SetPermission(PermissionType::GEOLOCATION, PermissionStatus::GRANTED);
 
   EXPECT_EQ(callback_count(), 1);
   EXPECT_EQ(PermissionStatus::GRANTED, callback_result());
@@ -944,10 +948,8 @@
   UnsubscribePermissionStatusChange(subscription_id);
 
   // ensure no callbacks are received when unsubscribed.
-  GetHostContentSettingsMap()->SetContentSettingDefaultScope(
-      url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_BLOCK);
-  GetHostContentSettingsMap()->SetContentSettingDefaultScope(
-      url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_ALLOW);
+  SetPermission(PermissionType::GEOLOCATION, PermissionStatus::DENIED);
+  SetPermission(PermissionType::GEOLOCATION, PermissionStatus::GRANTED);
 
   EXPECT_EQ(callback_count(), 1);
 
@@ -959,8 +961,7 @@
                               base::Unretained(this)));
   EXPECT_EQ(callback_count(), 1);
 
-  GetHostContentSettingsMap()->SetContentSettingDefaultScope(
-      url(), url(), ContentSettingsType::GEOLOCATION, CONTENT_SETTING_BLOCK);
+  SetPermission(PermissionType::GEOLOCATION, PermissionStatus::DENIED);
 
   EXPECT_EQ(callback_count(), 2);
   EXPECT_EQ(PermissionStatus::DENIED, callback_result());
@@ -973,13 +974,13 @@
   GURL embedding("https://embedding.example.com");
 
   EXPECT_EQ(embedding,
-            GetPermissionManager()->GetCanonicalOrigin(
+            permissions::PermissionUtil::GetCanonicalOrigin(
                 ContentSettingsType::COOKIES, requesting, embedding));
   EXPECT_EQ(requesting,
-            GetPermissionManager()->GetCanonicalOrigin(
+            permissions::PermissionUtil::GetCanonicalOrigin(
                 ContentSettingsType::NOTIFICATIONS, requesting, embedding));
   EXPECT_EQ(requesting,
-            GetPermissionManager()->GetCanonicalOrigin(
+            permissions::PermissionUtil::GetCanonicalOrigin(
                 ContentSettingsType::STORAGE_ACCESS, requesting, embedding));
 }
 
@@ -989,18 +990,18 @@
   const GURL kPartitionedOrigin("https://partitioned.com");
   ScopedPartitionedOriginBrowserClient browser_client(kPartitionedOrigin);
 
-  SetPermission(kOrigin, ContentSettingsType::GEOLOCATION,
-                ContentSetting::CONTENT_SETTING_ALLOW);
+  SetPermission(kOrigin, PermissionType::GEOLOCATION,
+                PermissionStatus::GRANTED);
 
-  SetPermission(kOrigin2, ContentSettingsType::GEOLOCATION,
-                ContentSetting::CONTENT_SETTING_BLOCK);
-  SetPermission(kOrigin2, ContentSettingsType::NOTIFICATIONS,
-                ContentSetting::CONTENT_SETTING_ALLOW);
+  SetPermission(kOrigin2, PermissionType::GEOLOCATION,
+                PermissionStatus::DENIED);
+  SetPermission(kOrigin2, PermissionType::NOTIFICATIONS,
+                PermissionStatus::GRANTED);
 
-  SetPermission(kPartitionedOrigin, ContentSettingsType::GEOLOCATION,
-                ContentSetting::CONTENT_SETTING_BLOCK);
-  SetPermission(kPartitionedOrigin, ContentSettingsType::NOTIFICATIONS,
-                ContentSetting::CONTENT_SETTING_ALLOW);
+  SetPermission(kPartitionedOrigin, PermissionType::GEOLOCATION,
+                PermissionStatus::DENIED);
+  SetPermission(kPartitionedOrigin, PermissionType::NOTIFICATIONS,
+                PermissionStatus::GRANTED);
 
   NavigateAndCommit(kOrigin);
   content::RenderFrameHost* parent = main_rfh();
diff --git a/components/permissions/permission_util.cc b/components/permissions/permission_util.cc
index f13780e9..5f1e5ad 100644
--- a/components/permissions/permission_util.cc
+++ b/components/permissions/permission_util.cc
@@ -10,7 +10,12 @@
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "components/permissions/features.h"
+#include "components/permissions/permission_result.h"
+#include "components/permissions/permissions_client.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/permission_result.h"
 #include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
 #include "content/public/browser/web_contents.h"
 #include "third_party/blink/public/common/permissions/permission_utils.h"
 #include "third_party/blink/public/common/web_preferences/web_preferences.h"
@@ -21,6 +26,38 @@
 
 namespace permissions {
 
+namespace {
+// Represents the possible methods of delegating permissions from main frames
+// to child frames.
+enum class PermissionDelegationMode {
+  // Permissions from the main frame are delegated to child frames.
+  // This is the default delegation mode for permissions. If a main frame was
+  // granted a permission that is delegated, its child frames will inherit that
+  // permission if allowed by the permissions policy.
+  kDelegated,
+  // Permissions from the main frame are not delegated to child frames.
+  // An undelegated permission will only be granted to a child frame if the
+  // child frame's origin was previously granted access to the permission when
+  // in a main frame.
+  kUndelegated,
+  // Permission access is a function of both the requesting and embedding
+  // origins.
+  kDoubleKeyed,
+};
+
+PermissionDelegationMode GetPermissionDelegationMode(
+    ContentSettingsType permission) {
+  // TODO(crbug.com/987654): Generalize this to other "background permissions",
+  // that is, permissions that can be used by a service worker. This includes
+  // durable storage, background sync, etc.
+  if (permission == ContentSettingsType::NOTIFICATIONS)
+    return PermissionDelegationMode::kUndelegated;
+  if (permission == ContentSettingsType::STORAGE_ACCESS)
+    return PermissionDelegationMode::kDoubleKeyed;
+  return PermissionDelegationMode::kDelegated;
+}
+}  // namespace
+
 // The returned strings must match any Field Trial configs for the Permissions
 // kill switch e.g. Permissions.Action.Geolocation etc..
 std::string PermissionUtil::GetPermissionString(
@@ -264,7 +301,7 @@
   return render_frame_host->GetLastCommittedOrigin().GetURL();
 }
 
-ContentSettingsType PermissionUtil::PermissionTypeToContentSettingSafe(
+ContentSettingsType PermissionUtil::PermissionTypeToContentSettingTypeSafe(
     PermissionType permission) {
   switch (permission) {
     case PermissionType::MIDI:
@@ -332,16 +369,79 @@
   return ContentSettingsType::DEFAULT;
 }
 
-ContentSettingsType PermissionUtil::PermissionTypeToContentSetting(
+ContentSettingsType PermissionUtil::PermissionTypeToContentSettingType(
     PermissionType permission) {
   ContentSettingsType content_setting =
-      PermissionTypeToContentSettingSafe(permission);
+      PermissionTypeToContentSettingTypeSafe(permission);
   DCHECK_NE(content_setting, ContentSettingsType::DEFAULT)
       << "Unknown content setting for permission "
       << static_cast<int>(permission);
   return content_setting;
 }
 
+PermissionType PermissionUtil::ContentSettingTypeToPermissionType(
+    ContentSettingsType permission) {
+  switch (permission) {
+    case ContentSettingsType::GEOLOCATION:
+      return PermissionType::GEOLOCATION;
+    case ContentSettingsType::NOTIFICATIONS:
+      return PermissionType::NOTIFICATIONS;
+    case ContentSettingsType::MIDI:
+      return PermissionType::MIDI;
+    case ContentSettingsType::MIDI_SYSEX:
+      return PermissionType::MIDI_SYSEX;
+    case ContentSettingsType::DURABLE_STORAGE:
+      return PermissionType::DURABLE_STORAGE;
+    case ContentSettingsType::MEDIASTREAM_CAMERA:
+      return PermissionType::VIDEO_CAPTURE;
+    case ContentSettingsType::MEDIASTREAM_MIC:
+      return PermissionType::AUDIO_CAPTURE;
+    case ContentSettingsType::BACKGROUND_SYNC:
+      return PermissionType::BACKGROUND_SYNC;
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN)
+    case ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER:
+      return PermissionType::PROTECTED_MEDIA_IDENTIFIER;
+#endif
+    case ContentSettingsType::SENSORS:
+      return PermissionType::SENSORS;
+    case ContentSettingsType::ACCESSIBILITY_EVENTS:
+      return PermissionType::ACCESSIBILITY_EVENTS;
+    case ContentSettingsType::CLIPBOARD_READ_WRITE:
+      return PermissionType::CLIPBOARD_READ_WRITE;
+    case ContentSettingsType::PAYMENT_HANDLER:
+      return PermissionType::PAYMENT_HANDLER;
+    case ContentSettingsType::BACKGROUND_FETCH:
+      return PermissionType::BACKGROUND_FETCH;
+    case ContentSettingsType::PERIODIC_BACKGROUND_SYNC:
+      return PermissionType::PERIODIC_BACKGROUND_SYNC;
+    case ContentSettingsType::WAKE_LOCK_SCREEN:
+      return PermissionType::WAKE_LOCK_SCREEN;
+    case ContentSettingsType::WAKE_LOCK_SYSTEM:
+      return PermissionType::WAKE_LOCK_SYSTEM;
+    case ContentSettingsType::NFC:
+      return PermissionType::NFC;
+    case ContentSettingsType::VR:
+      return PermissionType::VR;
+    case ContentSettingsType::AR:
+      return PermissionType::AR;
+    case ContentSettingsType::STORAGE_ACCESS:
+      return PermissionType::STORAGE_ACCESS_GRANT;
+    case ContentSettingsType::CAMERA_PAN_TILT_ZOOM:
+      return PermissionType::CAMERA_PAN_TILT_ZOOM;
+    case ContentSettingsType::WINDOW_PLACEMENT:
+      return PermissionType::WINDOW_PLACEMENT;
+    case ContentSettingsType::LOCAL_FONTS:
+      return PermissionType::LOCAL_FONTS;
+    case ContentSettingsType::IDLE_DETECTION:
+      return PermissionType::IDLE_DETECTION;
+    case ContentSettingsType::DISPLAY_CAPTURE:
+      return PermissionType::DISPLAY_CAPTURE;
+    default:
+      NOTREACHED();
+      return PermissionType::NUM;
+  }
+}
+
 ContentSetting PermissionUtil::PermissionStatusToContentSetting(
     blink::mojom::PermissionStatus status) {
   switch (status) {
@@ -358,4 +458,83 @@
   return CONTENT_SETTING_DEFAULT;
 }
 
+blink::mojom::PermissionStatus PermissionUtil::ContentSettingToPermissionStatus(
+    ContentSetting setting) {
+  switch (setting) {
+    case CONTENT_SETTING_ALLOW:
+      return blink::mojom::PermissionStatus::GRANTED;
+    case CONTENT_SETTING_BLOCK:
+      return blink::mojom::PermissionStatus::DENIED;
+    case CONTENT_SETTING_ASK:
+      return blink::mojom::PermissionStatus::ASK;
+    case CONTENT_SETTING_SESSION_ONLY:
+    case CONTENT_SETTING_DETECT_IMPORTANT_CONTENT:
+    case CONTENT_SETTING_DEFAULT:
+    case CONTENT_SETTING_NUM_SETTINGS:
+      break;
+  }
+
+  NOTREACHED();
+  return blink::mojom::PermissionStatus::DENIED;
+}
+
+content::PermissionResult PermissionUtil::ToContentPermissionResult(
+    PermissionResult result) {
+  content::PermissionStatusSource source =
+      (content::PermissionStatusSource)result.source;
+  blink::mojom::PermissionStatus status =
+      ContentSettingToPermissionStatus(result.content_setting);
+
+  return content::PermissionResult(status, source);
+}
+
+PermissionResult PermissionUtil::ToPermissionResult(
+    content::PermissionResult result) {
+  PermissionStatusSource source = (PermissionStatusSource)result.source;
+  ContentSetting setting = PermissionStatusToContentSetting(result.status);
+
+  return PermissionResult(setting, source);
+}
+
+bool PermissionUtil::IsPermissionBlockedInPartition(
+    ContentSettingsType permission,
+    const GURL& requesting_origin,
+    content::RenderProcessHost* render_process_host) {
+  DCHECK(render_process_host);
+  switch (GetPermissionDelegationMode(permission)) {
+    case PermissionDelegationMode::kDelegated:
+      return false;
+    case PermissionDelegationMode::kDoubleKeyed:
+      return false;
+    case PermissionDelegationMode::kUndelegated:
+      // TODO(crbug.com/1312218): This will create |requesting_origin|'s home
+      // StoragePartition if it doesn't already exist. Given how
+      // StoragePartitions are used today, this shouldn't actually be a
+      // problem, but ideally we'd compare StoragePartitionConfigs.
+      content::StoragePartition* requesting_home_partition =
+          render_process_host->GetBrowserContext()->GetStoragePartitionForUrl(
+              requesting_origin);
+      return requesting_home_partition !=
+             render_process_host->GetStoragePartition();
+  }
+}
+
+GURL PermissionUtil::GetCanonicalOrigin(ContentSettingsType permission,
+                                        const GURL& requesting_origin,
+                                        const GURL& embedding_origin) {
+  absl::optional<GURL> override_origin =
+      PermissionsClient::Get()->OverrideCanonicalOrigin(requesting_origin,
+                                                        embedding_origin);
+  if (override_origin)
+    return override_origin.value();
+
+  switch (GetPermissionDelegationMode(permission)) {
+    case PermissionDelegationMode::kDelegated:
+      return embedding_origin;
+    case PermissionDelegationMode::kDoubleKeyed:
+    case PermissionDelegationMode::kUndelegated:
+      return requesting_origin;
+  }
+}
+
 }  // namespace permissions
diff --git a/components/permissions/permission_util.h b/components/permissions/permission_util.h
index 4a44362..c4f73498 100644
--- a/components/permissions/permission_util.h
+++ b/components/permissions/permission_util.h
@@ -19,11 +19,14 @@
 
 namespace content {
 class RenderFrameHost;
+class RenderProcessHost;
+struct PermissionResult;
 }  // namespace content
 
 class GURL;
 
 namespace permissions {
+struct PermissionResult;
 
 // This enum backs a UMA histogram, so it must be treated as append-only.
 enum class PermissionAction {
@@ -79,19 +82,59 @@
   static GURL GetLastCommittedOriginAsURL(
       content::RenderFrameHost* render_frame_host);
 
-  // Helper method to convert PermissionType to ContentSettingType.
-  // If PermissionType is not supported or found, returns
+  // Helper method to convert `PermissionType` to `ContentSettingType`.
+  // If `PermissionType` is not supported or found, returns
   // ContentSettingsType::DEFAULT.
-  static ContentSettingsType PermissionTypeToContentSettingSafe(
+  static ContentSettingsType PermissionTypeToContentSettingTypeSafe(
       blink::PermissionType permission);
 
-  // Helper method to convert PermissionType to ContentSettingType.
-  static ContentSettingsType PermissionTypeToContentSetting(
+  // Helper method to convert `PermissionType` to `ContentSettingType`.
+  static ContentSettingsType PermissionTypeToContentSettingType(
       blink::PermissionType permission);
 
+  // Helper method to convert `ContentSettingType` to `PermissionType`.
+  static blink::PermissionType ContentSettingTypeToPermissionType(
+      ContentSettingsType permission);
+
   // Helper method to convert PermissionStatus to ContentSetting.
   static ContentSetting PermissionStatusToContentSetting(
       blink::mojom::PermissionStatus status);
+
+  // Helper methods to convert ContentSetting to PermissionStatus and vice
+  // versa.
+  static blink::mojom::PermissionStatus ContentSettingToPermissionStatus(
+      ContentSetting setting);
+
+  static content::PermissionResult ToContentPermissionResult(
+      PermissionResult result);
+
+  static PermissionResult ToPermissionResult(content::PermissionResult result);
+
+  // If an iframed document/worker inherits a different StoragePartition from
+  // its embedder than it would use if it were a main frame, we should block
+  // undelegated permissions. Because permissions are scoped to BrowserContext
+  // instead of StoragePartition, without this check the aforementioned iframe
+  // would be given undelegated permissions if the user had granted its origin
+  // access when it was loaded as a main frame.
+  static bool IsPermissionBlockedInPartition(
+      ContentSettingsType permission,
+      const GURL& requesting_origin,
+      content::RenderProcessHost* render_process_host);
+
+  // Converts from |url|'s actual origin to the "canonical origin" that should
+  // be used for the purpose of requesting/storing permissions. For example, the
+  // origin of the local NTP gets mapped to the Google base URL instead. With
+  // Permission Delegation it will transform the requesting origin into
+  // the embedding origin because all permission checks happen on the top level
+  // origin.
+  //
+  // All the public methods below, such as RequestPermission or
+  // GetPermissionStatus, take the actual origin and do the canonicalization
+  // internally. You only need to call this directly if you do something else
+  // with the origin, such as display it in the UI.
+  static GURL GetCanonicalOrigin(ContentSettingsType permission,
+                                 const GURL& requesting_origin,
+                                 const GURL& embedding_origin);
 };
 
 }  // namespace permissions
diff --git a/components/permissions/permissions_client.cc b/components/permissions/permissions_client.cc
index f7273857..24b10f0 100644
--- a/components/permissions/permissions_client.cc
+++ b/components/permissions/permissions_client.cc
@@ -115,8 +115,8 @@
   return absl::nullopt;
 }
 
-bool PermissionsClient::DoOriginsMatchNewTabPage(const GURL& requesting_origin,
-                                                 const GURL& embedding_origin) {
+bool PermissionsClient::DoURLsMatchNewTabPage(const GURL& requesting_origin,
+                                              const GURL& embedding_origin) {
   return false;
 }
 
diff --git a/components/permissions/permissions_client.h b/components/permissions/permissions_client.h
index 3cd33e2..d8d8792 100644
--- a/components/permissions/permissions_client.h
+++ b/components/permissions/permissions_client.h
@@ -44,7 +44,6 @@
 class ObjectPermissionContextBase;
 class PermissionActionsHistory;
 class PermissionDecisionAutoBlocker;
-class PermissionManager;
 class PermissionPromptAndroid;
 
 // Interface to be implemented by permissions embedder to access embedder
@@ -88,11 +87,6 @@
   virtual PermissionDecisionAutoBlocker* GetPermissionDecisionAutoBlocker(
       content::BrowserContext* browser_context) = 0;
 
-  // Retrieves the PermissionManager for this context. The returned
-  // pointer has the same lifetime as |browser_context|.
-  virtual PermissionManager* GetPermissionManager(
-      content::BrowserContext* browser_context) = 0;
-
   // Gets the ObjectPermissionContextBase for the given type and context, which
   // must be a
   // *_CHOOSER_DATA value. May return null if the context does not exist.
@@ -196,8 +190,8 @@
 
   // Checks if `requesting_origin` and `embedding_origin` are the new tab page
   // origins.
-  virtual bool DoOriginsMatchNewTabPage(const GURL& requesting_origin,
-                                        const GURL& embedding_origin);
+  virtual bool DoURLsMatchNewTabPage(const GURL& requesting_origin,
+                                     const GURL& embedding_origin);
 
 #if BUILDFLAG(IS_ANDROID)
   // Returns whether the given origin matches the default search engine (DSE)
diff --git a/components/permissions/test/test_permissions_client.cc b/components/permissions/test/test_permissions_client.cc
index 03ba8f79f..024e8990 100644
--- a/components/permissions/test/test_permissions_client.cc
+++ b/components/permissions/test/test_permissions_client.cc
@@ -61,11 +61,6 @@
   return &autoblocker_;
 }
 
-PermissionManager* TestPermissionsClient::GetPermissionManager(
-    content::BrowserContext* browser_context) {
-  return nullptr;
-}
-
 ObjectPermissionContextBase* TestPermissionsClient::GetChooserContext(
     content::BrowserContext* browser_context,
     ContentSettingsType type) {
diff --git a/components/permissions/test/test_permissions_client.h b/components/permissions/test/test_permissions_client.h
index 88d8517..2ae0877a 100644
--- a/components/permissions/test/test_permissions_client.h
+++ b/components/permissions/test/test_permissions_client.h
@@ -31,8 +31,6 @@
       content::BrowserContext* browser_context) override;
   PermissionDecisionAutoBlocker* GetPermissionDecisionAutoBlocker(
       content::BrowserContext* browser_context) override;
-  PermissionManager* GetPermissionManager(
-      content::BrowserContext* browser_context) override;
   ObjectPermissionContextBase* GetChooserContext(
       content::BrowserContext* browser_context,
       ContentSettingsType type) override;
diff --git a/components/policy/core/common/policy_service_impl.cc b/components/policy/core/common/policy_service_impl.cc
index 13ab61ce..83f04b9 100644
--- a/components/policy/core/common/policy_service_impl.cc
+++ b/components/policy/core/common/policy_service_impl.cc
@@ -95,7 +95,7 @@
 }
 
 bool IsUserCloudMergingAllowed(const PolicyMap& policies) {
-#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_IOS)
+#if BUILDFLAG(IS_CHROMEOS)
   return false;
 #else
   const base::Value* cloud_user_policy_merge_value =
diff --git a/components/policy/proto/chrome_device_policy.proto b/components/policy/proto/chrome_device_policy.proto
index 9902d37..a8a01cf 100644
--- a/components/policy/proto/chrome_device_policy.proto
+++ b/components/policy/proto/chrome_device_policy.proto
@@ -910,14 +910,16 @@
   // every time a user authenticates via SAML during login. If false, cookies
   // are transferred during each user's first login only.
   optional bool transfer_saml_cookies = 1;
+}
 
+message SAMLUsernameProto {
   // If this policy is not configured or set to a blank string, users will have
   // to manually enter their username on SAML IdP page during online
   // authentication on the sign-in screen and the lock screen.
   // Otherwise, this string is expected to contain a url parameter name which
-  // should be used on IdP web page as $|autofill_saml_username|=[user's email]
-  // to autofill the username.
-  optional string url_parameter_to_autofill_saml_username = 2;
+  // should be used on IdP's login page with user's email as a value to autofill
+  // the username.
+  optional string url_parameter_to_autofill_saml_username = 1;
 }
 
 message RebootOnShutdownProto {
@@ -1864,4 +1866,5 @@
   optional BooleanPolicyProto device_run_automatic_cleanup_on_login = 133;
   optional EncryptedReportingPipelineConfigurationProto
       device_encrypted_reporting_pipeline_enabled = 134;
+  optional SAMLUsernameProto saml_username = 135;
 }
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index c06185b7..ecb21b21 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -10564,7 +10564,7 @@
       'type': 'main',
       'schema': { 'type': 'boolean' },
       'supported_on': ['chrome.*:92-', 'android:97-'],
-      'future_on': ['fuchsia'],
+      'future_on': ['fuchsia', 'ios'],
       'features': {
         'dynamic_refresh': True,
         'per_profile': True,
@@ -33060,7 +33060,7 @@
     'DeviceKeylockerForStorageEncryptionEnabled': 'keylocker_for_storage_encryption_enabled.enabled',
     'DeviceRunAutomaticCleanupOnLogin': 'device_run_automatic_cleanup_on_login.value',
     'DeviceEncryptedReportingPipelineEnabled': 'device_reporting.encrypted_reporting_pipeline_enabled',
-    'DeviceAutofillSAMLUsername': 'saml_settings.url_parameter_to_autofill_saml_username'
+    'DeviceAutofillSAMLUsername': 'saml_username.url_parameter_to_autofill_saml_username'
   },
   'policy_atomic_group_definitions': [
     {
diff --git a/components/power_bookmarks/core/power_bookmark_service.cc b/components/power_bookmarks/core/power_bookmark_service.cc
index 7b9b35a..3b733b5 100644
--- a/components/power_bookmarks/core/power_bookmark_service.cc
+++ b/components/power_bookmarks/core/power_bookmark_service.cc
@@ -4,6 +4,8 @@
 
 #include "components/power_bookmarks/core/power_bookmark_service.h"
 
+#include <algorithm>
+
 namespace power_bookmarks {
 
 PowerBookmarkService::PowerBookmarkService() = default;
diff --git a/components/resources/protobufs/OWNERS b/components/resources/protobufs/OWNERS
index f93bbac4..11e138a1 100644
--- a/components/resources/protobufs/OWNERS
+++ b/components/resources/protobufs/OWNERS
@@ -1,4 +1,3 @@
-drubery@chromium.org
+file://components/safe_browsing/OWNERS
+
 meacer@chromium.org
-nparker@chromium.org
-vakh@chromium.org
diff --git a/components/safe_browsing/README.md b/components/safe_browsing/README.md
index 844bfdf..94e96ef 100644
--- a/components/safe_browsing/README.md
+++ b/components/safe_browsing/README.md
@@ -7,8 +7,7 @@
 * core/: shared code that does not depend on either {src/content, src/ios} or {./content,
 ./ios}
     * browser/: Browser process code
-    * common/: Code shared by the browser and the renderer (on platforms other than
-  iOS)
+    * common/: Code shared by the browser and the renderer
 * content/: non-iOS code layered above core/ that integrates with the Content API
     *  browser/: Browser process code
     *  common/: Code shared by the browser and the renderer
diff --git a/components/sync/protocol/autofill_wallet_usage_specifics.proto b/components/sync/protocol/autofill_wallet_usage_specifics.proto
index 66ab481d..d705567 100644
--- a/components/sync/protocol/autofill_wallet_usage_specifics.proto
+++ b/components/sync/protocol/autofill_wallet_usage_specifics.proto
@@ -30,7 +30,7 @@
   // specific Autofill wallet data model. These timestamps are collected when a
   // user selects an option in a Autofill suggestion dropdown and the client
   // records the time.
-  repeated int64 retrieval_time_unix_epoch_micros = 3;
+  repeated int64 retrieval_time_unix_epoch_micros = 3 [packed = true];
 
   message VirtualCardUsageData {
     // The instrument id of the actual card that the virtual card is related to.
diff --git a/components/viz/common/quads/render_pass_io.cc b/components/viz/common/quads/render_pass_io.cc
index 8cd18e09..f8388592 100644
--- a/components/viz/common/quads/render_pass_io.cc
+++ b/components/viz/common/quads/render_pass_io.cc
@@ -385,8 +385,8 @@
   base::Value::List steps;
   for (size_t i = 0; i < gradient_mask.step_count(); ++i) {
     base::Value::Dict step_dict;
-    step_dict.Set("percent",
-                  static_cast<double>(gradient_mask.steps()[i].percent));
+    step_dict.Set("fraction",
+                  static_cast<double>(gradient_mask.steps()[i].fraction));
     step_dict.Set("alpha", static_cast<int>(gradient_mask.steps()[i].alpha));
     steps.Append(std::move(step_dict));
   }
@@ -411,12 +411,12 @@
     if (!step)
       return false;
 
-    absl::optional<double> percent = step->FindDouble("percent");
+    absl::optional<double> fraction = step->FindDouble("fraction");
     absl::optional<int> alpha = step->FindInt("alpha");
-    if (!percent || !alpha)
+    if (!fraction || !alpha)
       return false;
 
-    gradient_mask.AddStep(*percent, *alpha);
+    gradient_mask.AddStep(*fraction, *alpha);
   }
 
   *out = gradient_mask;
diff --git a/components/viz/common/quads/render_pass_io_unittest.cc b/components/viz/common/quads/render_pass_io_unittest.cc
index 1f7e8b8a..340b6eca 100644
--- a/components/viz/common/quads/render_pass_io_unittest.cc
+++ b/components/viz/common/quads/render_pass_io_unittest.cc
@@ -127,7 +127,7 @@
     transform.MakeIdentity();
     gfx::LinearGradient gradient_mask(40);
     gradient_mask.AddStep(/*percent=*/0, /*alpha=*/0);
-    gradient_mask.AddStep(100, 255);
+    gradient_mask.AddStep(1, 255);
     sqs1->SetAll(
         transform, gfx::Rect(0, 0, 640, 480), gfx::Rect(10, 10, 600, 400),
         gfx::MaskFilterInfo(gfx::RRectF(gfx::RectF(2.f, 3.f, 4.f, 5.f), 1.5f),
@@ -173,7 +173,7 @@
     EXPECT_EQ(2u, sqs1->mask_filter_info.gradient_mask()->step_count());
     EXPECT_EQ(gfx::LinearGradient::Step({0, 0}),
               sqs1->mask_filter_info.gradient_mask()->steps()[0]);
-    EXPECT_EQ(gfx::LinearGradient::Step({100, 255}),
+    EXPECT_EQ(gfx::LinearGradient::Step({1, 255}),
               sqs1->mask_filter_info.gradient_mask()->steps()[1]);
     EXPECT_EQ(gfx::RectF(2.f, 3.f, 4.f, 5.f), sqs1->mask_filter_info.bounds());
     EXPECT_EQ(gfx::Rect(5, 20, 1000, 200), sqs1->clip_rect);
diff --git a/components/viz/common/resources/resource_format_utils.cc b/components/viz/common/resources/resource_format_utils.cc
index f31da5e..0962122 100644
--- a/components/viz/common/resources/resource_format_utils.cc
+++ b/components/viz/common/resources/resource_format_utils.cc
@@ -542,7 +542,6 @@
 bool GLSupportsFormat(ResourceFormat format) {
   switch (format) {
     case BGR_565:
-    case BGRX_8888:
     case YVU_420:
     case YUV_420_BIPLANAR:
     case P010:
diff --git a/components/viz/service/display/renderer_pixeltest.cc b/components/viz/service/display/renderer_pixeltest.cc
index 4a3aea7c..51d9222 100644
--- a/components/viz/service/display/renderer_pixeltest.cc
+++ b/components/viz/service/display/renderer_pixeltest.cc
@@ -4579,8 +4579,8 @@
   gfx::RRectF rounded_corner_bounds(gfx::RectF(pass_rect), kCornerRadius);
   gfx::LinearGradient gradient_mask(330);
   gradient_mask.AddStep(/*percent=*/0, /*alpha=*/0);
-  gradient_mask.AddStep(50, 255);
-  gradient_mask.AddStep(100, 255);
+  gradient_mask.AddStep(.5, 255);
+  gradient_mask.AddStep(1, 255);
   SharedQuadState* pass_shared_state = CreateTestSharedQuadState(
       gfx::Transform(), pass_rect, root_pass.get(),
       gfx::MaskFilterInfo(rounded_corner_bounds, gradient_mask));
@@ -4626,7 +4626,7 @@
   blue_rrect.Offset(blue_offset_from_target);
   gfx::LinearGradient blue_gradient(0);
   blue_gradient.AddStep(/*percent=*/0, /*alpha=*/255);
-  blue_gradient.AddStep(100, 0);
+  blue_gradient.AddStep(1, 0);
 
   gfx::Transform quad_to_target_transform;
   quad_to_target_transform.Translate(blue_offset_from_target);
@@ -4648,8 +4648,8 @@
   gfx::RRectF rounded_corner_bounds(gfx::RectF(pass_rect), kCornerRadius);
   gfx::LinearGradient gradient_mask(-30);
   gradient_mask.AddStep(/*percent=*/0, /*alpha=*/0);
-  gradient_mask.AddStep(50, 255);
-  gradient_mask.AddStep(100, 255);
+  gradient_mask.AddStep(.5, 255);
+  gradient_mask.AddStep(1, 255);
   SharedQuadState* pass_shared_state = CreateTestSharedQuadState(
       gfx::Transform(), pass_rect, root_pass.get(),
       gfx::MaskFilterInfo(rounded_corner_bounds, gradient_mask));
diff --git a/components/viz/service/display/skia_renderer.cc b/components/viz/service/display/skia_renderer.cc
index 02ae6e938..01ed5054 100644
--- a/components/viz/service/display/skia_renderer.cc
+++ b/components/viz/service/display/skia_renderer.cc
@@ -1292,7 +1292,7 @@
 
   size_t i = 0;
   for (; i < gradient_mask->step_count(); ++i) {
-    positions[i] = gradient_mask->steps()[i].percent / 100.f;
+    positions[i] = gradient_mask->steps()[i].fraction;
     gradient_colors[i] = MaskColor(gradient_mask->steps()[i].alpha);
   }
 
diff --git a/components/viz/test/test_gpu_memory_buffer_manager.cc b/components/viz/test/test_gpu_memory_buffer_manager.cc
index 20626b7..ce49bce 100644
--- a/components/viz/test/test_gpu_memory_buffer_manager.cc
+++ b/components/viz/test/test_gpu_memory_buffer_manager.cc
@@ -75,7 +75,7 @@
     handle.type = gfx::SHARED_MEMORY_BUFFER;
     handle.region = region_.Duplicate();
     handle.offset = base::checked_cast<uint32_t>(offset_);
-    handle.stride = base::checked_cast<int32_t>(stride_);
+    handle.stride = base::checked_cast<uint32_t>(stride_);
     return handle;
   }
   void OnMemoryDump(
diff --git a/components/viz/test/test_image_factory.cc b/components/viz/test/test_image_factory.cc
index 663dd18..8302da7a 100644
--- a/components/viz/test/test_image_factory.cc
+++ b/components/viz/test/test_image_factory.cc
@@ -26,7 +26,7 @@
   DCHECK_EQ(handle.type, gfx::SHARED_MEMORY_BUFFER);
   auto image = base::MakeRefCounted<gl::GLImageSharedMemory>(size);
   if (!image->Initialize(handle.region, handle.id, format, handle.offset,
-                         base::checked_cast<size_t>(handle.stride)))
+                         handle.stride))
     return nullptr;
 
   return image;
diff --git a/components/webapps/browser/installable/installable_manager.cc b/components/webapps/browser/installable/installable_manager.cc
index c85ce7a..8af8c3d6 100644
--- a/components/webapps/browser/installable/installable_manager.cc
+++ b/components/webapps/browser/installable/installable_manager.cc
@@ -922,12 +922,14 @@
                                ? kMinimumScreenshotSizeInPx
                                : std::max(url->image.sizes[0].width(),
                                           url->image.sizes[0].height());
-    // Passing in kMaximumScreenshotSizeInPx ensures that screenshots above that
-    // size are filtered out by the blink image_downloader implementation.
-    // Thus, we no longer need them to be resized in OnScreenshotFetched().
+    // Do not pass in a maximum icon size so that screenshots larger than
+    // kMaximumScreenshotSizeInPx are not downscaled to the maximum size by
+    // `ManifestIconDownloader::Download`. Screenshots with size larger than
+    // kMaximumScreenshotSizeInPx get filtered out by OnScreenshotFetched.
     bool can_download = content::ManifestIconDownloader::Download(
         GetWebContents(), url->image.src, ideal_size_in_px,
-        kMinimumScreenshotSizeInPx, kMaximumScreenshotSizeInPx,
+        kMinimumScreenshotSizeInPx,
+        /*maximum_icon_size_in_px=*/0,
         base::BindOnce(&InstallableManager::OnScreenshotFetched,
                        weak_factory_.GetWeakPtr(), url->image.src),
         /*square_only=*/false);
@@ -969,7 +971,12 @@
       auto iter = downloaded_screenshots_.find(url->image.src);
       if (iter == downloaded_screenshots_.end())
         continue;
+
       auto screenshot = iter->second;
+      if (screenshot.dimensions().width() > kMaximumScreenshotSizeInPx ||
+          screenshot.dimensions().height() > kMaximumScreenshotSizeInPx) {
+        continue;
+      }
 
       // Screenshots must have the same aspect ratio. Cross-multiplying
       // dimensions checks portrait vs landscape mode (1:2 vs 2:1 for instance).
diff --git a/components/webrtc/media_stream_devices_controller.cc b/components/webrtc/media_stream_devices_controller.cc
index 13bbeaa..71eb2ed 100644
--- a/components/webrtc/media_stream_devices_controller.cc
+++ b/components/webrtc/media_stream_devices_controller.cc
@@ -10,11 +10,10 @@
 
 #include "base/bind.h"
 #include "build/build_config.h"
-#include "components/permissions/permission_manager.h"
-#include "components/permissions/permission_result.h"
-#include "components/permissions/permissions_client.h"
+#include "components/permissions/permission_util.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/permission_controller.h"
+#include "content/public/browser/permission_result.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/render_widget_host_view.h"
@@ -36,15 +35,15 @@
 
 namespace {
 
-// Returns true if the given ContentSettingsType is being requested in
+// Returns true if the given PermissionType is being requested in
 // |request|.
-bool ContentTypeIsRequested(ContentSettingsType type,
-                            const content::MediaStreamRequest& request) {
-  if (type == ContentSettingsType::MEDIASTREAM_MIC)
+bool PermissionIsRequested(blink::PermissionType permission,
+                           const content::MediaStreamRequest& request) {
+  if (permission == blink::PermissionType::AUDIO_CAPTURE)
     return request.audio_type ==
            blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE;
 
-  if (type == ContentSettingsType::MEDIASTREAM_CAMERA)
+  if (permission == blink::PermissionType::VIDEO_CAPTURE)
     return request.video_type ==
            blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE;
 
@@ -93,42 +92,39 @@
 
   std::vector<blink::PermissionType> permission_types;
 
-  permissions::PermissionManager* permission_manager =
-      permissions::PermissionsClient::Get()->GetPermissionManager(
-          web_contents->GetBrowserContext());
+  content::PermissionController* permission_controller =
+      web_contents->GetBrowserContext()->GetPermissionController();
 
   if (controller->ShouldRequestAudio()) {
-    permissions::PermissionResult permission_status =
-        permission_manager->GetPermissionStatusForCurrentDocument(
-            ContentSettingsType::MEDIASTREAM_MIC, rfh);
-    if (permission_status.content_setting == CONTENT_SETTING_BLOCK) {
+    content::PermissionResult permission_status =
+        permission_controller->GetPermissionResultForCurrentDocument(
+            blink::PermissionType::AUDIO_CAPTURE, rfh);
+    if (permission_status.status == blink::mojom::PermissionStatus::DENIED) {
       controller->denial_reason_ =
           blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
-      controller->RunCallback(
-          permission_status.source ==
-          permissions::PermissionStatusSource::FEATURE_POLICY);
+      controller->RunCallback(permission_status.source ==
+                              content::PermissionStatusSource::FEATURE_POLICY);
       return;
     }
 
     permission_types.push_back(blink::PermissionType::AUDIO_CAPTURE);
   }
   if (controller->ShouldRequestVideo()) {
-    permissions::PermissionResult permission_status =
-        permission_manager->GetPermissionStatusForCurrentDocument(
-            ContentSettingsType::MEDIASTREAM_CAMERA, rfh);
-    if (permission_status.content_setting == CONTENT_SETTING_BLOCK) {
+    content::PermissionResult permission_status =
+        permission_controller->GetPermissionResultForCurrentDocument(
+            blink::PermissionType::VIDEO_CAPTURE, rfh);
+    if (permission_status.status == blink::mojom::PermissionStatus::DENIED) {
       controller->denial_reason_ =
           blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
-      controller->RunCallback(
-          permission_status.source ==
-          permissions::PermissionStatusSource::FEATURE_POLICY);
+      controller->RunCallback(permission_status.source ==
+                              content::PermissionStatusSource::FEATURE_POLICY);
       return;
     }
 
     permission_types.push_back(blink::PermissionType::VIDEO_CAPTURE);
 
     bool has_pan_tilt_zoom_camera = controller->HasAvailableDevices(
-        ContentSettingsType::CAMERA_PAN_TILT_ZOOM,
+        blink::PermissionType::CAMERA_PAN_TILT_ZOOM,
         request.requested_video_device_id);
 
     // Request CAMERA_PAN_TILT_ZOOM only if the website requested the
@@ -136,9 +132,9 @@
     // available.
     if (request.request_pan_tilt_zoom_permission && has_pan_tilt_zoom_camera) {
       permission_status =
-          permission_manager->GetPermissionStatusForCurrentDocument(
-              ContentSettingsType::CAMERA_PAN_TILT_ZOOM, rfh);
-      if (permission_status.content_setting == CONTENT_SETTING_BLOCK) {
+          permission_controller->GetPermissionResultForCurrentDocument(
+              blink::PermissionType::CAMERA_PAN_TILT_ZOOM, rfh);
+      if (permission_status.status == blink::mojom::PermissionStatus::DENIED) {
         controller->denial_reason_ =
             blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
         controller->RunCallback(/*blocked_by_permissions_policy=*/false);
@@ -188,9 +184,9 @@
     enumerator_ = &owned_enumerator_;
 
   denial_reason_ = blink::mojom::MediaStreamRequestResult::OK;
-  audio_setting_ = GetContentSetting(ContentSettingsType::MEDIASTREAM_MIC,
+  audio_setting_ = GetContentSetting(blink::PermissionType::AUDIO_CAPTURE,
                                      request, &denial_reason_);
-  video_setting_ = GetContentSetting(ContentSettingsType::MEDIASTREAM_CAMERA,
+  video_setting_ = GetContentSetting(blink::PermissionType::VIDEO_CAPTURE,
                                      request, &denial_reason_);
 }
 
@@ -342,37 +338,37 @@
 }
 
 ContentSetting MediaStreamDevicesController::GetContentSetting(
-    ContentSettingsType content_type,
+    blink::PermissionType permission,
     const content::MediaStreamRequest& request,
     blink::mojom::MediaStreamRequestResult* denial_reason) const {
-  DCHECK(content_type == ContentSettingsType::MEDIASTREAM_MIC ||
-         content_type == ContentSettingsType::MEDIASTREAM_CAMERA);
+  DCHECK(permission == blink::PermissionType::AUDIO_CAPTURE ||
+         permission == blink::PermissionType::VIDEO_CAPTURE);
   DCHECK(!request_.security_origin.is_empty());
   DCHECK(network::IsUrlPotentiallyTrustworthy(request_.security_origin) ||
          request_.request_type == blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY);
-  if (!ContentTypeIsRequested(content_type, request)) {
+  if (!PermissionIsRequested(permission, request)) {
     // No denial reason set as it will have been previously set.
     return CONTENT_SETTING_DEFAULT;
   }
 
   std::string device_id;
-  if (content_type == ContentSettingsType::MEDIASTREAM_MIC)
+  if (permission == blink::PermissionType::AUDIO_CAPTURE)
     device_id = request.requested_audio_device_id;
   else
     device_id = request.requested_video_device_id;
-  if (!HasAvailableDevices(content_type, device_id)) {
+  if (!HasAvailableDevices(permission, device_id)) {
     *denial_reason = blink::mojom::MediaStreamRequestResult::NO_HARDWARE;
     return CONTENT_SETTING_BLOCK;
   }
 
-  if (!IsUserAcceptAllowed(content_type)) {
+  if (!IsUserAcceptAllowed(permission)) {
     *denial_reason = blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
     return CONTENT_SETTING_BLOCK;
   }
 
   // Don't request if the kill switch is on.
   if (PermissionIsBlockedForReason(
-          content_type, permissions::PermissionStatusSource::KILL_SWITCH)) {
+          permission, content::PermissionStatusSource::KILL_SWITCH)) {
     *denial_reason = blink::mojom::MediaStreamRequestResult::KILL_SWITCH_ON;
     return CONTENT_SETTING_BLOCK;
   }
@@ -381,13 +377,27 @@
 }
 
 bool MediaStreamDevicesController::IsUserAcceptAllowed(
-    ContentSettingsType content_type) const {
+    blink::PermissionType permission) const {
 #if BUILDFLAG(IS_ANDROID)
   ui::WindowAndroid* window_android =
       web_contents_->GetNativeView()->GetWindowAndroid();
   if (!window_android)
     return false;
 
+  ContentSettingsType content_type;
+
+  switch (permission) {
+    case blink::PermissionType::AUDIO_CAPTURE:
+      content_type = ContentSettingsType::MEDIASTREAM_MIC;
+      break;
+    case blink::PermissionType::VIDEO_CAPTURE:
+      content_type = ContentSettingsType::MEDIASTREAM_CAMERA;
+      break;
+    default:
+      NOTREACHED();
+      return false;
+  }
+
   std::vector<std::string> required_android_permissions;
   permissions::AppendRequiredAndroidPermissionsForContentSetting(
       content_type, &required_android_permissions);
@@ -408,21 +418,22 @@
 }
 
 bool MediaStreamDevicesController::PermissionIsBlockedForReason(
-    ContentSettingsType content_type,
-    permissions::PermissionStatusSource reason) const {
-  // TODO(raymes): This function wouldn't be needed if
-  // PermissionManager::RequestPermissions returned a denial reason.
+    blink::PermissionType permission,
+    content::PermissionStatusSource reason) const {
   content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(
       request_.render_process_id, request_.render_frame_id);
   if (rfh->GetLastCommittedOrigin().GetURL() != request_.security_origin) {
     return false;
   }
-  permissions::PermissionResult result =
-      permissions::PermissionsClient::Get()
-          ->GetPermissionManager(web_contents_->GetBrowserContext())
-          ->GetPermissionStatusForCurrentDocument(content_type, rfh);
+
+  // TODO(raymes): This function wouldn't be needed if
+  // PermissionManager::RequestPermissions returned a denial reason.
+  content::PermissionResult result =
+      web_contents_->GetBrowserContext()
+          ->GetPermissionController()
+          ->GetPermissionResultForCurrentDocument(permission, rfh);
   if (result.source == reason) {
-    DCHECK_EQ(CONTENT_SETTING_BLOCK, result.content_setting);
+    DCHECK_EQ(blink::mojom::PermissionStatus::DENIED, result.status);
     return true;
   }
   return false;
@@ -452,8 +463,8 @@
     blocked_by_permissions_policy &=
         audio_setting_ == CONTENT_SETTING_BLOCK &&
         PermissionIsBlockedForReason(
-            ContentSettingsType::MEDIASTREAM_MIC,
-            permissions::PermissionStatusSource::FEATURE_POLICY);
+            blink::PermissionType::AUDIO_CAPTURE,
+            content::PermissionStatusSource::FEATURE_POLICY);
   }
 
   if (need_video) {
@@ -461,8 +472,8 @@
     blocked_by_permissions_policy &=
         video_setting_ == CONTENT_SETTING_BLOCK &&
         PermissionIsBlockedForReason(
-            ContentSettingsType::MEDIASTREAM_CAMERA,
-            permissions::PermissionStatusSource::FEATURE_POLICY);
+            blink::PermissionType::VIDEO_CAPTURE,
+            content::PermissionStatusSource::FEATURE_POLICY);
   }
 
   for (ContentSetting response : responses) {
@@ -478,13 +489,13 @@
 }
 
 bool MediaStreamDevicesController::HasAvailableDevices(
-    ContentSettingsType content_type,
+    blink::PermissionType permission,
     const std::string& device_id) const {
   const MediaStreamDevices* devices = nullptr;
-  if (content_type == ContentSettingsType::MEDIASTREAM_MIC) {
+  if (permission == blink::PermissionType::AUDIO_CAPTURE) {
     devices = &enumerator_->GetAudioCaptureDevices();
-  } else if (content_type == ContentSettingsType::MEDIASTREAM_CAMERA ||
-             content_type == ContentSettingsType::CAMERA_PAN_TILT_ZOOM) {
+  } else if (permission == blink::PermissionType::VIDEO_CAPTURE ||
+             permission == blink::PermissionType::CAMERA_PAN_TILT_ZOOM) {
     devices = &enumerator_->GetVideoCaptureDevices();
   } else {
     NOTREACHED();
@@ -500,7 +511,7 @@
 
   // If there are no particular device requirements, all devices will do.
   if (device_id.empty() &&
-      content_type != ContentSettingsType::CAMERA_PAN_TILT_ZOOM) {
+      permission != blink::PermissionType::CAMERA_PAN_TILT_ZOOM) {
     return true;
   }
 
@@ -509,7 +520,7 @@
     if (!device_id.empty() && device.id != device_id) {
       continue;
     }
-    if (content_type == ContentSettingsType::CAMERA_PAN_TILT_ZOOM &&
+    if (permission == blink::PermissionType::CAMERA_PAN_TILT_ZOOM &&
         !device.video_control_support.pan &&
         !device.video_control_support.tilt &&
         !device.video_control_support.zoom) {
diff --git a/components/webrtc/media_stream_devices_controller.h b/components/webrtc/media_stream_devices_controller.h
index 1787b41..da70bfb 100644
--- a/components/webrtc/media_stream_devices_controller.h
+++ b/components/webrtc/media_stream_devices_controller.h
@@ -18,11 +18,12 @@
 #include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
 #include "third_party/blink/public/mojom/permissions/permission_status.mojom.h"
 
-namespace permissions {
-enum class PermissionStatusSource;
+namespace blink {
+enum class PermissionType;
 }
 
 namespace content {
+enum class PermissionStatusSource;
 class WebContents;
 }
 
@@ -81,25 +82,25 @@
   // Runs |callback_| with the current audio/video permission settings.
   void RunCallback(bool blocked_by_permissions_policy);
 
-  // Returns the content settings for the given content type and request.
+  // Returns the content settings for the given permission type and request.
   ContentSetting GetContentSetting(
-      ContentSettingsType content_type,
+      blink::PermissionType permission,
       const content::MediaStreamRequest& request,
       blink::mojom::MediaStreamRequestResult* denial_reason) const;
 
   // Returns true if clicking allow on the dialog should give access to the
   // requested devices.
-  bool IsUserAcceptAllowed(ContentSettingsType content_type) const;
+  bool IsUserAcceptAllowed(blink::PermissionType permission) const;
 
   bool PermissionIsBlockedForReason(
-      ContentSettingsType content_type,
-      permissions::PermissionStatusSource reason) const;
+      blink::PermissionType permission,
+      content::PermissionStatusSource reason) const;
 
   // Called when a permission prompt is answered through the PermissionManager.
   void PromptAnsweredGroupedRequest(
       const std::vector<blink::mojom::PermissionStatus>& permissions_status);
 
-  bool HasAvailableDevices(ContentSettingsType content_type,
+  bool HasAvailableDevices(blink::PermissionType permission,
                            const std::string& device_id) const;
 
   // The current state of the audio/video content settings which may be updated
diff --git a/content/browser/accessibility/accessibility_ipc_error_browsertest.cc b/content/browser/accessibility/accessibility_ipc_error_browsertest.cc
index 63d8a4a..6e78961 100644
--- a/content/browser/accessibility/accessibility_ipc_error_browsertest.cc
+++ b/content/browser/accessibility/accessibility_ipc_error_browsertest.cc
@@ -14,6 +14,7 @@
 #include "content/public/test/content_browser_test.h"
 #include "content/public/test/content_browser_test_utils.h"
 #include "content/shell/browser/shell.h"
+#include "ui/accessibility/ax_common.h"
 #include "ui/accessibility/ax_node.h"
 #include "ui/accessibility/ax_tree.h"
 
@@ -42,7 +43,9 @@
 };
 
 // Failed on Android x86 in crbug.com/1123641.
-#if BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_X86)
+// Do not test on AX_FAIL_FAST_BUILDS, where the BAD IPC will simply assert.
+#if (BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_X86)) || \
+    defined(AX_FAIL_FAST_BUILD)
 #define MAYBE_ResetBrowserAccessibilityManager \
   DISABLED_ResetBrowserAccessibilityManager
 #else
@@ -142,7 +145,8 @@
   EXPECT_EQ(ax::mojom::Role::kButton, button->GetRole());
 }
 
-#if BUILDFLAG(IS_LINUX)
+// Do not test on AX_FAIL_FAST_BUILDS, where the BAD IPC will simply assert.
+#if BUILDFLAG(IS_LINUX) && !defined(AX_FAIL_FAST_BUILD)
 #define MAYBE_MultipleBadAccessibilityIPCsKillsRenderer \
   MultipleBadAccessibilityIPCsKillsRenderer
 #else
diff --git a/content/browser/accessibility/browser_accessibility_manager.cc b/content/browser/accessibility/browser_accessibility_manager.cc
index f8cac33..d2d270c 100644
--- a/content/browser/accessibility/browser_accessibility_manager.cc
+++ b/content/browser/accessibility/browser_accessibility_manager.cc
@@ -13,7 +13,6 @@
 
 #include "base/auto_reset.h"
 #include "base/containers/adapters.h"
-#include "base/debug/crash_logging.h"
 #include "base/logging.h"
 #include "base/metrics/user_metrics.h"
 #include "base/no_destructor.h"
@@ -229,32 +228,12 @@
   ParentConnectionChanged(parent);
 }
 
-bool BrowserAccessibilityManager::Unserialize(
-    const ui::AXTreeUpdate& tree_update) {
-  if (ax_tree()->Unserialize(tree_update))
-    return true;
-
-  LOG(ERROR) << ax_tree()->error();
-  LOG(ERROR) << tree_update.ToString();
-
-  static auto* const ax_tree_error = base::debug::AllocateCrashKeyString(
-      "ax_tree_error", base::debug::CrashKeySize::Size256);
-  static auto* const ax_tree_update = base::debug::AllocateCrashKeyString(
-      "ax_tree_update", base::debug::CrashKeySize::Size256);
-  // Temporarily log some additional crash keys so we can try to
-  // figure out why we're getting bad accessibility trees here.
-  // http://crbug.com/765490, https://crbug.com/1094848.
-  // Be sure to re-enable BrowserAccessibilityManagerTest.TestFatalError
-  // when done (or delete it if no longer needed).
-  base::debug::SetCrashKeyString(ax_tree_error, ax_tree()->error());
-  base::debug::SetCrashKeyString(ax_tree_update, tree_update.ToString());
-  return false;
-}
-
 void BrowserAccessibilityManager::Initialize(
     const ui::AXTreeUpdate& initial_tree) {
-  if (!Unserialize(initial_tree))
-    LOG(FATAL) << ax_tree()->error();
+  if (!ax_tree()->Unserialize(initial_tree)) {
+    LOG(FATAL) << "No recovery is possible if the initial tree is broken: "
+               << ax_tree()->error();
+  }
 }
 
 // A flag for use in tests to ensure events aren't suppressed or delayed.
@@ -552,7 +531,7 @@
 
   // Process all changes to the accessibility tree first.
   for (const ui::AXTreeUpdate& tree_update : *tree_updates) {
-    if (!Unserialize(tree_update)) {
+    if (!ax_tree()->Unserialize(tree_update)) {
       // This is a fatal error, but if there is a delegate, it will handle the
       // error result and recover by re-creating the manager. After a max
       // threshold number of errors is reached, it will crash the browser.
diff --git a/content/browser/accessibility/browser_accessibility_manager.h b/content/browser/accessibility/browser_accessibility_manager.h
index 6e4bed1..bc6e77f 100644
--- a/content/browser/accessibility/browser_accessibility_manager.h
+++ b/content/browser/accessibility/browser_accessibility_manager.h
@@ -324,8 +324,7 @@
   // Called when the renderer process has notified us of tree changes. Returns
   // false in fatal-error conditions, in which case the caller should destroy
   // the manager.
-  [[nodiscard]] virtual bool OnAccessibilityEvents(
-      const AXEventNotificationDetails& details);
+  virtual bool OnAccessibilityEvents(const AXEventNotificationDetails& details);
 
   // Allows derived classes to do event pre-processing
   virtual void BeforeAccessibilityEvents();
@@ -684,10 +683,6 @@
 #endif  // DCHECK_IS_ON()
 
  private:
-  // Helper that calls AXTree::Unserialize(). On failure it populates crash data
-  // with error information.
-  bool Unserialize(const ui::AXTreeUpdate& tree_update);
-
   void BuildAXTreeHitTestCacheInternal(
       const BrowserAccessibility* node,
       std::vector<const BrowserAccessibility*>* storage);
diff --git a/content/browser/accessibility/browser_accessibility_manager_unittest.cc b/content/browser/accessibility/browser_accessibility_manager_unittest.cc
index 5cbc46a..aa408d5 100644
--- a/content/browser/accessibility/browser_accessibility_manager_unittest.cc
+++ b/content/browser/accessibility/browser_accessibility_manager_unittest.cc
@@ -80,8 +80,7 @@
       std::make_unique<TestBrowserAccessibilityDelegate>();
 }
 
-// Temporarily disabled due to bug http://crbug.com/765490
-TEST_F(BrowserAccessibilityManagerTest, DISABLED_TestFatalError) {
+TEST_F(BrowserAccessibilityManagerTest, TestErrorOnCreateIsFatal) {
   // Test that BrowserAccessibilityManager raises a fatal error
   // (which will crash the renderer) if the same id is used in
   // two places in the tree.
@@ -93,42 +92,52 @@
   root.child_ids.push_back(2);
 
   std::unique_ptr<BrowserAccessibilityManager> manager;
-  ASSERT_FALSE(test_browser_accessibility_delegate_->got_fatal_error());
-  manager.reset(BrowserAccessibilityManager::Create(
-      MakeAXTreeUpdate(root), test_browser_accessibility_delegate_.get()));
-  ASSERT_TRUE(test_browser_accessibility_delegate_->got_fatal_error());
+  EXPECT_DEATH_IF_SUPPORTED(
+      manager.reset(BrowserAccessibilityManager::Create(
+          MakeAXTreeUpdate(root), test_browser_accessibility_delegate_.get())),
+      "Node 1 has duplicate child id 2");
+}
 
-  ui::AXNodeData root2;
-  root2.id = 1;
-  root2.role = ax::mojom::Role::kRootWebArea;
-  root2.child_ids.push_back(2);
-  root2.child_ids.push_back(3);
+TEST_F(BrowserAccessibilityManagerTest, TestErrorOnUpdate) {
+  ui::AXNodeData root;
+  root.id = 1;
+  root.role = ax::mojom::Role::kRootWebArea;
 
-  ui::AXNodeData child1;
-  child1.id = 2;
-  child1.child_ids.push_back(4);
-  child1.child_ids.push_back(5);
+  ui::AXNodeData node2;
+  node2.id = 2;
+  root.child_ids.push_back(2);
 
-  ui::AXNodeData child2;
-  child2.id = 3;
-  child2.child_ids.push_back(6);
-  child2.child_ids.push_back(5);  // Duplicate
+  ui::AXNodeData node3;
+  node3.id = 3;
+  root.child_ids.push_back(3);
 
-  ui::AXNodeData grandchild4;
-  grandchild4.id = 4;
+  ui::AXNodeData node4;
+  node4.id = 4;
+  node3.child_ids.push_back(4);
 
-  ui::AXNodeData grandchild5;
-  grandchild5.id = 5;
+  ui::AXNodeData node5;
+  node5.id = 5;
+  root.child_ids.push_back(5);
 
-  ui::AXNodeData grandchild6;
-  grandchild6.id = 6;
+  std::unique_ptr<BrowserAccessibilityManager> manager(
+      BrowserAccessibilityManager::Create(
+          MakeAXTreeUpdate(root, node2, node3, node4, node5),
+          test_browser_accessibility_delegate_.get()));
 
-  test_browser_accessibility_delegate_->reset_got_fatal_error();
-  manager.reset(BrowserAccessibilityManager::Create(
-      MakeAXTreeUpdate(root2, child1, child2, grandchild4, grandchild5,
-                       grandchild6),
-      test_browser_accessibility_delegate_.get()));
-  ASSERT_TRUE(test_browser_accessibility_delegate_->got_fatal_error());
+  // node4 has two child ids now.
+  node4.child_ids.push_back(5);
+  node4.child_ids.push_back(5);
+  ui::AXTreeUpdate update = MakeAXTreeUpdate(node4, node5);
+  AXEventNotificationDetails events;
+  events.updates = {update};
+
+#if defined(AX_FAIL_FAST_BUILD)
+  // Update errors are fatal in AX_FAIL_FAST_BUILD builds.
+  EXPECT_DEATH_IF_SUPPORTED(manager->OnAccessibilityEvents(events),
+                            "Node 4 has duplicate child id 5");
+#else
+  ASSERT_FALSE(manager->OnAccessibilityEvents(events));
+#endif
 }
 
 // This test depends on hypertext, which is only used on
diff --git a/content/browser/accessibility/test_browser_accessibility_delegate.cc b/content/browser/accessibility/test_browser_accessibility_delegate.cc
index 6c8b894..583993c 100644
--- a/content/browser/accessibility/test_browser_accessibility_delegate.cc
+++ b/content/browser/accessibility/test_browser_accessibility_delegate.cc
@@ -7,9 +7,7 @@
 namespace content {
 
 TestBrowserAccessibilityDelegate::TestBrowserAccessibilityDelegate()
-    : is_root_frame_(true),
-      accelerated_widget_(gfx::kNullAcceleratedWidget),
-      got_fatal_error_(false) {}
+    : is_root_frame_(true), accelerated_widget_(gfx::kNullAcceleratedWidget) {}
 
 void TestBrowserAccessibilityDelegate::AccessibilityPerformAction(
     const ui::AXActionData& data) {}
@@ -28,9 +26,7 @@
   return 1.0f;
 }
 
-void TestBrowserAccessibilityDelegate::AccessibilityFatalError() {
-  got_fatal_error_ = true;
-}
+void TestBrowserAccessibilityDelegate::AccessibilityFatalError() {}
 
 gfx::AcceleratedWidget
 TestBrowserAccessibilityDelegate::AccessibilityGetAcceleratedWidget() {
@@ -68,12 +64,4 @@
   return nullptr;
 }
 
-bool TestBrowserAccessibilityDelegate::got_fatal_error() const {
-  return got_fatal_error_;
-}
-
-void TestBrowserAccessibilityDelegate::reset_got_fatal_error() {
-  got_fatal_error_ = false;
-}
-
 }  // namespace content
diff --git a/content/browser/accessibility/test_browser_accessibility_delegate.h b/content/browser/accessibility/test_browser_accessibility_delegate.h
index a63cc452..1b7c189 100644
--- a/content/browser/accessibility/test_browser_accessibility_delegate.h
+++ b/content/browser/accessibility/test_browser_accessibility_delegate.h
@@ -33,14 +33,8 @@
                               int hit_node_id)> opt_callback) override;
   WebContentsAccessibility* AccessibilityGetWebContentsAccessibility() override;
 
-  bool got_fatal_error() const;
-  void reset_got_fatal_error();
-
   bool is_root_frame_;
   gfx::AcceleratedWidget accelerated_widget_;
-
- private:
-  bool got_fatal_error_;
 };
 
 }  // namespace content
diff --git a/content/browser/android/content_feature_list.cc b/content/browser/android/content_feature_list.cc
index d2531979b..9a53afd 100644
--- a/content/browser/android/content_feature_list.cc
+++ b/content/browser/android/content_feature_list.cc
@@ -30,6 +30,7 @@
     &features::kFedCm,
     &features::kOnDemandAccessibilityEvents,
     &features::kProcessSharingWithStrictSiteInstances,
+    &features::kReduceGpuPriorityOnBackground,
     &features::kRequestDesktopSiteAdditions,
     &features::kRequestDesktopSiteExceptions,
     &features::kTouchDragAndContextMenu,
diff --git a/content/browser/attribution_reporting/attribution_src_browsertest.cc b/content/browser/attribution_reporting/attribution_src_browsertest.cc
index 73d94f3..42733483 100644
--- a/content/browser/attribution_reporting/attribution_src_browsertest.cc
+++ b/content/browser/attribution_reporting/attribution_src_browsertest.cc
@@ -5,6 +5,7 @@
 #include <memory>
 #include <utility>
 
+#include "base/containers/contains.h"
 #include "base/memory/raw_ptr.h"
 #include "base/run_loop.h"
 #include "base/strings/strcat.h"
@@ -802,6 +803,41 @@
             "event-source, trigger");
 }
 
+// Regression test for crbug.com/1345955.
+IN_PROC_BROWSER_TEST_F(AttributionSrcBrowserTest,
+                       UntrustworthyUrl_DoesNotSetEligibleHeader) {
+  auto http_server = std::make_unique<net::EmbeddedTestServer>();
+  net::test_server::RegisterDefaultHandlers(http_server.get());
+
+  auto response1 = std::make_unique<net::test_server::ControllableHttpResponse>(
+      http_server.get(), "/register_source1");
+  auto response2 = std::make_unique<net::test_server::ControllableHttpResponse>(
+      http_server.get(), "/register_source2");
+  ASSERT_TRUE(http_server->Start());
+
+  GURL page_url =
+      https_server()->GetURL("b.test", "/page_with_impression_creator.html");
+  ASSERT_TRUE(NavigateToURL(web_contents(), page_url));
+
+  GURL register_url1 = http_server->GetURL("d.test", "/register_source1");
+  ASSERT_TRUE(ExecJs(web_contents(), JsReplace(R"(
+  createAndClickAttributionSrcAnchor({url: $1, attributionsrc: '', target: '_blank'});)",
+                                               register_url1)));
+
+  response1->WaitForRequest();
+  ASSERT_FALSE(base::Contains(response1->http_request()->headers,
+                              "Attribution-Reporting-Eligible"));
+
+  GURL register_url2 = http_server->GetURL("d.test", "/register_source2");
+  ASSERT_TRUE(ExecJs(web_contents(), JsReplace(R"(
+    window.open($1, '_blank', 'attributionsrc=');)",
+                                               register_url2)));
+
+  response2->WaitForRequest();
+  ASSERT_FALSE(base::Contains(response2->http_request()->headers,
+                              "Attribution-Reporting-Eligible"));
+}
+
 IN_PROC_BROWSER_TEST_F(AttributionSrcBrowserTest,
                        ReferrerPolicy_RespectsDocument) {
   // Create a separate server as we cannot register a `ControllableHttpResponse`
diff --git a/content/browser/devtools/devtools_instrumentation.cc b/content/browser/devtools/devtools_instrumentation.cc
index e7b8168..d0d6020 100644
--- a/content/browser/devtools/devtools_instrumentation.cc
+++ b/content/browser/devtools/devtools_instrumentation.cc
@@ -228,9 +228,6 @@
     case FederatedAuthRequestResult::kErrorFetchingIdTokenInvalidResponse: {
       return FederatedAuthRequestIssueReasonEnum::IdTokenInvalidResponse;
     }
-    case FederatedAuthRequestResult::kErrorFetchingIdTokenInvalidRequest: {
-      return FederatedAuthRequestIssueReasonEnum::IdTokenInvalidRequest;
-    }
     case FederatedAuthRequestResult::kErrorCanceled: {
       return FederatedAuthRequestIssueReasonEnum::Canceled;
     }
diff --git a/content/browser/first_party_sets/first_party_set_parser.cc b/content/browser/first_party_sets/first_party_set_parser.cc
index 610b587..4b5a110a 100644
--- a/content/browser/first_party_sets/first_party_set_parser.cc
+++ b/content/browser/first_party_sets/first_party_set_parser.cc
@@ -4,6 +4,7 @@
 
 #include "content/browser/first_party_sets/first_party_set_parser.h"
 
+#include <cstdint>
 #include <string>
 #include <utility>
 #include <vector>
@@ -81,6 +82,7 @@
 // returns an appropriate FirstPartySetParser::ParseError.
 base::expected<FirstPartySetParser::SingleSet, FirstPartySetParser::ParseError>
 ParseSet(const base::Value& value,
+         bool keep_indices,
          base::flat_set<net::SchemefulSite>& elements) {
   if (!value.is_dict())
     return base::unexpected(FirstPartySetParser::ParseError::kInvalidType);
@@ -112,8 +114,10 @@
   std::vector<std::pair<net::SchemefulSite, net::FirstPartySetEntry>> sites;
   sites.emplace_back(
       *canonical_owner,
-      net::FirstPartySetEntry(*canonical_owner, net::SiteType::kPrimary));
+      net::FirstPartySetEntry(*canonical_owner, net::SiteType::kPrimary,
+                              absl::nullopt));
   // Add each member to our mapping (assuming the member is a string).
+  uint32_t index = 0;
   for (const auto& item : maybe_members_list->GetListDeprecated()) {
     // Members may not be a member of another set, and may not be an owner of
     // another set.
@@ -140,7 +144,12 @@
 
     sites.emplace_back(
         *member,
-        net::FirstPartySetEntry(*canonical_owner, net::SiteType::kAssociated));
+        net::FirstPartySetEntry(
+            *canonical_owner, net::SiteType::kAssociated,
+            keep_indices
+                ? absl::make_optional(net::FirstPartySetEntry::SiteIndex(index))
+                : absl::nullopt));
+    ++index;
   }
 
   for (const std::pair<net::SchemefulSite, net::FirstPartySetEntry>&
@@ -168,7 +177,7 @@
   for (int i = 0; i < static_cast<int>(policy_sets->size()); i++) {
     base::expected<FirstPartySetParser::SingleSet,
                    FirstPartySetParser::ParseError>
-        parsed = ParseSet((*policy_sets)[i], elements);
+        parsed = ParseSet((*policy_sets)[i], /*keep_indices=*/false, elements);
     if (!parsed.has_value()) {
       return base::unexpected(
           FirstPartySetParser::PolicyParsingError{parsed.error(), set_type, i});
@@ -229,9 +238,9 @@
       continue;
     }
     if (!owner_set.contains(maybe_owner)) {
-      map.emplace_back(
-          *maybe_owner,
-          net::FirstPartySetEntry(*maybe_owner, net::SiteType::kPrimary));
+      map.emplace_back(*maybe_owner, net::FirstPartySetEntry(
+                                         *maybe_owner, net::SiteType::kPrimary,
+                                         absl::nullopt));
     }
     // Check disjointness. Note that we are relying on the JSON Parser to
     // eliminate the possibility of a site being used as a key more than once,
@@ -242,9 +251,12 @@
     }
     owner_set.insert(*maybe_owner);
     member_set.insert(*maybe_member);
-    map.emplace_back(std::move(*maybe_member),
-                     net::FirstPartySetEntry(std::move(*maybe_owner),
-                                             net::SiteType::kAssociated));
+    // TODO(https://crbug.com/1219656): preserve ordering information when
+    // persisting set info.
+    map.emplace_back(
+        std::move(*maybe_member),
+        net::FirstPartySetEntry(std::move(*maybe_owner),
+                                net::SiteType::kAssociated, absl::nullopt));
   }
   return map;
 }
@@ -286,7 +298,7 @@
       return {};
     base::expected<FirstPartySetParser::SingleSet,
                    FirstPartySetParser::ParseError>
-        parsed = ParseSet(*maybe_value, elements);
+        parsed = ParseSet(*maybe_value, /*keep_indices=*/true, elements);
     if (!parsed.has_value()) {
       if (parsed.error() == FirstPartySetParser::ParseError::kInvalidOrigin) {
         // Ignore sets that include an invalid domain (which might have been
diff --git a/content/browser/first_party_sets/first_party_set_parser_unittest.cc b/content/browser/first_party_sets/first_party_set_parser_unittest.cc
index 1d65699..91441cd6 100644
--- a/content/browser/first_party_sets/first_party_set_parser_unittest.cc
+++ b/content/browser/first_party_sets/first_party_set_parser_unittest.cc
@@ -68,11 +68,11 @@
                   Pair(SerializesTo("https://example.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://aaaa.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated))));
+                           net::SiteType::kAssociated, 0))));
 }
 
 TEST(FirstPartySetParser, RejectsMissingOwner) {
@@ -116,19 +116,19 @@
                   Pair(SerializesTo("https://example2.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example2.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member2.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example2.test")),
-                           net::SiteType::kAssociated)),
+                           net::SiteType::kAssociated, 0)),
                   Pair(SerializesTo("https://example.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://aaaa.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated))));
+                           net::SiteType::kAssociated, 0))));
 }
 
 TEST(FirstPartySetParser, RejectsOwnerWithoutRegisteredDomain) {
@@ -180,19 +180,19 @@
                   Pair(SerializesTo("https://example2.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example2.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member2.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example2.test")),
-                           net::SiteType::kAssociated)),
+                           net::SiteType::kAssociated, 0)),
                   Pair(SerializesTo("https://example.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member3.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated))));
+                           net::SiteType::kAssociated, 0))));
 }
 
 TEST(FirstPartySetParser, RejectsMemberWithoutRegisteredDomain) {
@@ -211,11 +211,11 @@
                   Pair(SerializesTo("https://example.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://aaaa.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated))));
+                           net::SiteType::kAssociated, 0))));
 }
 
 TEST(FirstPartySetParser, TruncatesSubdomain_Member) {
@@ -227,11 +227,11 @@
                   Pair(SerializesTo("https://example.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://aaaa.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated))));
+                           net::SiteType::kAssociated, 0))));
 }
 
 TEST(FirstPartySetParser, AcceptsMultipleSets) {
@@ -247,19 +247,19 @@
                   Pair(SerializesTo("https://example.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member1.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated)),
+                           net::SiteType::kAssociated, 0)),
                   Pair(SerializesTo("https://foo.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://foo.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member2.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://foo.test")),
-                           net::SiteType::kAssociated))));
+                           net::SiteType::kAssociated, 0))));
 }
 
 TEST(FirstPartySetParser, AcceptsMultipleSetsWithWhitespace) {
@@ -277,19 +277,19 @@
                   Pair(SerializesTo("https://example.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member1.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated)),
+                           net::SiteType::kAssociated, 0)),
                   Pair(SerializesTo("https://foo.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://foo.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member2.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://foo.test")),
-                           net::SiteType::kAssociated))));
+                           net::SiteType::kAssociated, 0))));
 }
 
 TEST(FirstPartySetParser, RejectsInvalidSets_InvalidOwner) {
@@ -315,11 +315,11 @@
                   Pair(SerializesTo("https://example.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member1.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated))));
+                           net::SiteType::kAssociated, 0))));
 }
 
 TEST(FirstPartySetParser, Rejects_SameOwner) {
@@ -362,19 +362,20 @@
                 {{net::SchemefulSite(GURL("https://member1.test")),
                   net::FirstPartySetEntry(
                       net::SchemefulSite(GURL("https://example1.test")),
-                      net::SiteType::kAssociated)},
+                      net::SiteType::kAssociated, 0)},
                  {net::SchemefulSite(GURL("https://example1.test")),
                   net::FirstPartySetEntry(
                       net::SchemefulSite(GURL("https://example1.test")),
-                      net::SiteType::kPrimary)}}));
+                      net::SiteType::kPrimary, absl::nullopt)}}));
 }
 
 TEST(FirstPartySetParser, SerializeFirstPartySetsWithOpaqueOrigin) {
-  EXPECT_EQ(R"({"https://member1.test":"null"})",
-            FirstPartySetParser::SerializeFirstPartySets(
-                {{net::SchemefulSite(GURL("https://member1.test")),
-                  net::FirstPartySetEntry(net::SchemefulSite(GURL("")),
-                                          net::SiteType::kPrimary)}}));
+  EXPECT_EQ(
+      R"({"https://member1.test":"null"})",
+      FirstPartySetParser::SerializeFirstPartySets(
+          {{net::SchemefulSite(GURL("https://member1.test")),
+            net::FirstPartySetEntry(net::SchemefulSite(GURL("")),
+                                    net::SiteType::kPrimary, absl::nullopt)}}));
 }
 
 TEST(FirstPartySetParser, SerializeFirstPartySetsEmptySet) {
@@ -394,23 +395,23 @@
                   Pair(SerializesTo("https://member1.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example1.test")),
-                           net::SiteType::kAssociated)),
+                           net::SiteType::kAssociated, absl::nullopt)),
                   Pair(SerializesTo("https://member3.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example1.test")),
-                           net::SiteType::kAssociated)),
+                           net::SiteType::kAssociated, absl::nullopt)),
                   Pair(SerializesTo("https://example1.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example1.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member2.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example2.test")),
-                           net::SiteType::kAssociated)),
+                           net::SiteType::kAssociated, absl::nullopt)),
                   Pair(SerializesTo("https://example2.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example2.test")),
-                           net::SiteType::kPrimary))));
+                           net::SiteType::kPrimary, absl::nullopt))));
 }
 
 TEST(FirstPartySetParser, DeserializeFirstPartySetsEmptySet) {
@@ -430,11 +431,11 @@
                   Pair(SerializesTo("https://member1.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example2.test")),
-                           net::SiteType::kAssociated)),
+                           net::SiteType::kAssociated, absl::nullopt)),
                   Pair(SerializesTo("https://example2.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example2.test")),
-                           net::SiteType::kPrimary))));
+                           net::SiteType::kPrimary, absl::nullopt))));
 }
 
 // Singleton set is ignored.
@@ -449,11 +450,11 @@
                   Pair(SerializesTo("https://member1.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example2.test")),
-                           net::SiteType::kAssociated)),
+                           net::SiteType::kAssociated, absl::nullopt)),
                   Pair(SerializesTo("https://example2.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example2.test")),
-                           net::SiteType::kPrimary))));
+                           net::SiteType::kPrimary, absl::nullopt))));
 }
 
 class FirstPartySetParserInvalidContentTest
@@ -506,7 +507,8 @@
                         R"({"https://member1.test":"https://example1.test",
             "https://example1.test":"https://example2.test"})")));
 
-TEST(ParseSetsFromEnterprisePolicyTest, Accepts_MissingSetLists) {
+TEST(FirstPartySets_ParseSetsFromEnterprisePolicyTest,
+     Accepts_MissingSetLists) {
   base::Value policy_value = base::JSONReader::Read(R"(
               {
               }
@@ -518,7 +520,7 @@
       FirstPartySetParser::ParsedPolicySetLists({}, {}));
 }
 
-TEST(ParseSetsFromEnterprisePolicyTest, Accepts_EmptyLists) {
+TEST(FirstPartySets_ParseSetsFromEnterprisePolicyTest, Accepts_EmptyLists) {
   base::Value policy_value = base::JSONReader::Read(R"(
               {
                 "replacements": [],
@@ -532,7 +534,8 @@
       FirstPartySetParser::ParsedPolicySetLists({}, {}));
 }
 
-TEST(ParseSetsFromEnterprisePolicyTest, InvalidTypeError_MissingOwner) {
+TEST(FirstPartySets_ParseSetsFromEnterprisePolicyTest,
+     InvalidTypeError_MissingOwner) {
   base::Value policy_value = base::JSONReader::Read(R"(
               {
                 "replacements": [
@@ -552,7 +555,8 @@
            FirstPartySetParser::PolicySetType::kReplacement, 0}));
 }
 
-TEST(ParseSetsFromEnterprisePolicyTest, InvalidTypeError_MissingMembers) {
+TEST(FirstPartySets_ParseSetsFromEnterprisePolicyTest,
+     InvalidTypeError_MissingMembers) {
   base::Value policy_value = base::JSONReader::Read(R"(
               {
                 "replacements": [
@@ -572,7 +576,8 @@
            FirstPartySetParser::PolicySetType::kReplacement, 0}));
 }
 
-TEST(ParseSetsFromEnterprisePolicyTest, InvalidTypeError_WrongOwnerType) {
+TEST(FirstPartySets_ParseSetsFromEnterprisePolicyTest,
+     InvalidTypeError_WrongOwnerType) {
   base::Value policy_value = base::JSONReader::Read(R"(
               {
                 "replacements": [
@@ -593,7 +598,7 @@
            FirstPartySetParser::PolicySetType::kReplacement, 0}));
 }
 
-TEST(ParseSetsFromEnterprisePolicyTest,
+TEST(FirstPartySets_ParseSetsFromEnterprisePolicyTest,
      InvalidTypeError_WrongMembersFieldType) {
   base::Value policy_value = base::JSONReader::Read(R"(
               {
@@ -615,7 +620,8 @@
            FirstPartySetParser::PolicySetType::kReplacement, 0}));
 }
 
-TEST(ParseSetsFromEnterprisePolicyTest, InvalidTypeError_WrongMemberType) {
+TEST(FirstPartySets_ParseSetsFromEnterprisePolicyTest,
+     InvalidTypeError_WrongMemberType) {
   base::Value policy_value = base::JSONReader::Read(R"(
               {
           "replacements": [
@@ -637,7 +643,8 @@
            FirstPartySetParser::PolicySetType::kReplacement, 0}));
 }
 
-TEST(ParseSetsFromEnterprisePolicyTest, InvalidOriginError_OwnerOpaque) {
+TEST(FirstPartySets_ParseSetsFromEnterprisePolicyTest,
+     InvalidOriginError_OwnerOpaque) {
   base::Value policy_value = base::JSONReader::Read(R"(
               {
                 "replacements": [
@@ -658,7 +665,8 @@
            FirstPartySetParser::PolicySetType::kReplacement, 0}));
 }
 
-TEST(ParseSetsFromEnterprisePolicyTest, InvalidOriginError_MemberOpaque) {
+TEST(FirstPartySets_ParseSetsFromEnterprisePolicyTest,
+     InvalidOriginError_MemberOpaque) {
   base::Value policy_value = base::JSONReader::Read(R"(
                {
                 "replacements": [
@@ -679,7 +687,8 @@
            FirstPartySetParser::PolicySetType::kReplacement, 0}));
 }
 
-TEST(ParseSetsFromEnterprisePolicyTest, InvalidOriginError_OwnerNonHttps) {
+TEST(FirstPartySets_ParseSetsFromEnterprisePolicyTest,
+     InvalidOriginError_OwnerNonHttps) {
   base::Value policy_value = base::JSONReader::Read(R"(
                  {
                 "replacements": [
@@ -700,7 +709,8 @@
            FirstPartySetParser::PolicySetType::kReplacement, 0}));
 }
 
-TEST(ParseSetsFromEnterprisePolicyTest, InvalidOriginError_MemberNonHttps) {
+TEST(FirstPartySets_ParseSetsFromEnterprisePolicyTest,
+     InvalidOriginError_MemberNonHttps) {
   base::Value policy_value = base::JSONReader::Read(R"(
                {
                 "replacements": [
@@ -721,7 +731,7 @@
            FirstPartySetParser::PolicySetType::kReplacement, 0}));
 }
 
-TEST(ParseSetsFromEnterprisePolicyTest,
+TEST(FirstPartySets_ParseSetsFromEnterprisePolicyTest,
      InvalidOriginError_OwnerNonRegisteredDomain) {
   base::Value policy_value = base::JSONReader::Read(R"(
                 {
@@ -743,7 +753,7 @@
            FirstPartySetParser::PolicySetType::kReplacement, 0}));
 }
 
-TEST(ParseSetsFromEnterprisePolicyTest,
+TEST(FirstPartySets_ParseSetsFromEnterprisePolicyTest,
      InvalidOriginError_MemberNonRegisteredDomain) {
   base::Value policy_value = base::JSONReader::Read(R"(
               {
@@ -765,7 +775,8 @@
            FirstPartySetParser::PolicySetType::kReplacement, 0}));
 }
 
-TEST(ParseSetsFromEnterprisePolicyTest, SingletonSetError_EmptyMembers) {
+TEST(FirstPartySets_ParseSetsFromEnterprisePolicyTest,
+     SingletonSetError_EmptyMembers) {
   base::Value policy_value = base::JSONReader::Read(R"(
              {
                 "replacements": [
@@ -786,7 +797,7 @@
            FirstPartySetParser::PolicySetType::kReplacement, 0}));
 }
 
-TEST(ParseSetsFromEnterprisePolicyTest,
+TEST(FirstPartySets_ParseSetsFromEnterprisePolicyTest,
      RepeatedDomainError_WithinReplacements) {
   base::Value policy_value = base::JSONReader::Read(R"(
               {
@@ -808,7 +819,8 @@
            FirstPartySetParser::PolicySetType::kReplacement, 0}));
 }
 
-TEST(ParseSetsFromEnterprisePolicyTest, NonDisjointError_WithinReplacements) {
+TEST(FirstPartySets_ParseSetsFromEnterprisePolicyTest,
+     NonDisjointError_WithinReplacements) {
   base::Value policy_value = base::JSONReader::Read(R"(
                    {
                 "replacements": [
@@ -833,7 +845,8 @@
            FirstPartySetParser::PolicySetType::kReplacement, 1}));
 }
 
-TEST(ParseSetsFromEnterprisePolicyTest, NonDisjointError_WithinAdditions) {
+TEST(FirstPartySets_ParseSetsFromEnterprisePolicyTest,
+     NonDisjointError_WithinAdditions) {
   base::Value policy_value = base::JSONReader::Read(R"(
                    {
                 "replacements": [],
@@ -858,7 +871,8 @@
            FirstPartySetParser::PolicySetType::kAddition, 1}));
 }
 
-TEST(ParseSetsFromEnterprisePolicyTest, NonDisjointError_AcrossBothLists) {
+TEST(FirstPartySets_ParseSetsFromEnterprisePolicyTest,
+     NonDisjointError_AcrossBothLists) {
   base::Value policy_value = base::JSONReader::Read(R"(
                {
                 "replacements": [
@@ -884,7 +898,8 @@
            FirstPartySetParser::PolicySetType::kAddition, 0}));
 }
 
-TEST(ParseSetsFromEnterprisePolicyTest, SuccessfulMapping_SameList) {
+TEST(FirstPartySets_ParseSetsFromEnterprisePolicyTest,
+     SuccessfulMapping_SameList) {
   net::SchemefulSite owner1(GURL("https://owner1.test"));
   net::SchemefulSite member1(GURL("https://member1.test"));
   net::SchemefulSite owner2(GURL("https://owner2.test"));
@@ -910,21 +925,24 @@
           .value(),
       FirstPartySetParser::ParsedPolicySetLists(
           {FirstPartySetParser::SetsMap({
-               {owner1,
-                net::FirstPartySetEntry(owner1, net::SiteType::kPrimary)},
+               {owner1, net::FirstPartySetEntry(owner1, net::SiteType::kPrimary,
+                                                absl::nullopt)},
                {member1,
-                net::FirstPartySetEntry(owner1, net::SiteType::kAssociated)},
+                net::FirstPartySetEntry(owner1, net::SiteType::kAssociated,
+                                        absl::nullopt)},
            }),
            FirstPartySetParser::SetsMap({
-               {owner2,
-                net::FirstPartySetEntry(owner2, net::SiteType::kPrimary)},
+               {owner2, net::FirstPartySetEntry(owner2, net::SiteType::kPrimary,
+                                                absl::nullopt)},
                {member2,
-                net::FirstPartySetEntry(owner2, net::SiteType::kAssociated)},
+                net::FirstPartySetEntry(owner2, net::SiteType::kAssociated,
+                                        absl::nullopt)},
            })},
           {}));
 }
 
-TEST(ParseSetsFromEnterprisePolicyTest, SuccessfulMapping_CrossList) {
+TEST(FirstPartySets_ParseSetsFromEnterprisePolicyTest,
+     SuccessfulMapping_CrossList) {
   net::SchemefulSite owner1(GURL("https://owner1.test"));
   net::SchemefulSite member1(GURL("https://member1.test"));
   net::SchemefulSite owner2(GURL("https://owner2.test"));
@@ -958,22 +976,24 @@
           .value(),
       FirstPartySetParser::ParsedPolicySetLists(
           {FirstPartySetParser::SetsMap({
-               {owner1,
-                net::FirstPartySetEntry(owner1, net::SiteType::kPrimary)},
+               {owner1, net::FirstPartySetEntry(owner1, net::SiteType::kPrimary,
+                                                absl::nullopt)},
                {member1,
-                net::FirstPartySetEntry(owner1, net::SiteType::kAssociated)},
+                net::FirstPartySetEntry(owner1, net::SiteType::kAssociated,
+                                        absl::nullopt)},
            }),
            FirstPartySetParser::SetsMap({
-               {owner2,
-                net::FirstPartySetEntry(owner2, net::SiteType::kPrimary)},
+               {owner2, net::FirstPartySetEntry(owner2, net::SiteType::kPrimary,
+                                                absl::nullopt)},
                {member2,
-                net::FirstPartySetEntry(owner2, net::SiteType::kAssociated)},
+                net::FirstPartySetEntry(owner2, net::SiteType::kAssociated,
+                                        absl::nullopt)},
            })},
           {FirstPartySetParser::SetsMap({
-              {owner3,
-               net::FirstPartySetEntry(owner3, net::SiteType::kPrimary)},
-              {member3,
-               net::FirstPartySetEntry(owner3, net::SiteType::kAssociated)},
+              {owner3, net::FirstPartySetEntry(owner3, net::SiteType::kPrimary,
+                                               absl::nullopt)},
+              {member3, net::FirstPartySetEntry(
+                            owner3, net::SiteType::kAssociated, absl::nullopt)},
           })}));
 }
 
diff --git a/content/browser/first_party_sets/first_party_sets_handler_impl.cc b/content/browser/first_party_sets/first_party_sets_handler_impl.cc
index c4e3ee2..b150d77d 100644
--- a/content/browser/first_party_sets/first_party_sets_handler_impl.cc
+++ b/content/browser/first_party_sets/first_party_sets_handler_impl.cc
@@ -149,9 +149,10 @@
       for (const auto& child_site_and_entry : addition_sets[child_set_idx]) {
         bool inserted =
             normalized
-                .emplace(child_site_and_entry.first,
-                         net::FirstPartySetEntry(rep_primary,
-                                                 net::SiteType::kAssociated))
+                .emplace(
+                    child_site_and_entry.first,
+                    net::FirstPartySetEntry(
+                        rep_primary, net::SiteType::kAssociated, absl::nullopt))
                 .second;
         DCHECK(inserted);
       }
@@ -305,7 +306,8 @@
           member, net::FirstPartySetEntry(entry->second.primary(),
                                           member == entry->second.primary()
                                               ? net::SiteType::kPrimary
-                                              : net::SiteType::kAssociated));
+                                              : net::SiteType::kAssociated,
+                                          absl::nullopt));
     }
     if (member == set_entry.primary())
       continue;
diff --git a/content/browser/first_party_sets/first_party_sets_handler_impl_unittest.cc b/content/browser/first_party_sets/first_party_sets_handler_impl_unittest.cc
index 9c09a6c..af509bd 100644
--- a/content/browser/first_party_sets/first_party_sets_handler_impl_unittest.cc
+++ b/content/browser/first_party_sets/first_party_sets_handler_impl_unittest.cc
@@ -49,32 +49,37 @@
   for (const auto& [owner, members] : owners_to_members) {
     net::SchemefulSite owner_site((GURL(owner)));
     result.insert(std::make_pair(
-        owner_site,
-        net::FirstPartySetEntry(owner_site, net::SiteType::kPrimary)));
+        owner_site, net::FirstPartySetEntry(owner_site, net::SiteType::kPrimary,
+                                            absl::nullopt)));
+    uint32_t index = 0;
     for (const std::string& member : members) {
       net::SchemefulSite member_site((GURL(member)));
       result.insert(std::make_pair(
-          member_site,
-          net::FirstPartySetEntry(owner_site, net::SiteType::kAssociated)));
+          member_site, net::FirstPartySetEntry(
+                           owner_site, net::SiteType::kAssociated, index)));
+      ++index;
     }
   }
   return result;
 }
 
 // Parses `input` as a collection of primaries and their associated sites, and
-// appends the results to `output`.
+// appends the results to `output`. Does not preserve indices (so it is only
+// suitable for creating enterprise policy sets).
 void ParseAndAppend(
     const base::flat_map<std::string, std::vector<std::string>>& input,
     std::vector<SingleSet>& output) {
   for (auto& [owner, members] : input) {
     std::vector<std::pair<net::SchemefulSite, net::FirstPartySetEntry>> sites;
     net::SchemefulSite owner_site((GURL(owner)));
-    sites.emplace_back(owner_site, net::FirstPartySetEntry(
-                                       owner_site, net::SiteType::kPrimary));
+    sites.emplace_back(
+        owner_site, net::FirstPartySetEntry(owner_site, net::SiteType::kPrimary,
+                                            absl::nullopt));
     for (const std::string& member : members) {
       sites.emplace_back(
           GURL(member),
-          net::FirstPartySetEntry(owner_site, net::SiteType::kAssociated));
+          net::FirstPartySetEntry(owner_site, net::SiteType::kAssociated,
+                                  absl::nullopt));
     }
     output.emplace_back(sites);
   }
@@ -109,13 +114,13 @@
   FirstPartySetsHandlerImpl::FlattenedSets old_sets = {
       {net::SchemefulSite(GURL("https://example.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://example.test")),
-                               net::SiteType::kPrimary)},
+                               net::SiteType::kPrimary, absl::nullopt)},
       {net::SchemefulSite(GURL("https://member1.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://example.test")),
-                               net::SiteType::kAssociated)},
+                               net::SiteType::kAssociated, 0)},
       {net::SchemefulSite(GURL("https://member3.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://example.test")),
-                               net::SiteType::kAssociated)}};
+                               net::SiteType::kAssociated, 1)}};
   // Consistency check the reviewer-friendly format matches the input.
   ASSERT_THAT(ParseSetsFromStream(
                   R"({"owner": "https://example.test", "members": )"
@@ -125,19 +130,19 @@
   FirstPartySetsHandlerImpl::FlattenedSets current_sets = {
       {net::SchemefulSite(GURL("https://example.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://example.test")),
-                               net::SiteType::kPrimary)},
+                               net::SiteType::kPrimary, absl::nullopt)},
       {net::SchemefulSite(GURL("https://member1.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://example.test")),
-                               net::SiteType::kAssociated)},
+                               net::SiteType::kAssociated, 0)},
       {net::SchemefulSite(GURL("https://member3.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://example.test")),
-                               net::SiteType::kAssociated)},
+                               net::SiteType::kAssociated, 1)},
       {net::SchemefulSite(GURL("https://foo.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                               net::SiteType::kPrimary)},
+                               net::SiteType::kPrimary, absl::nullopt)},
       {net::SchemefulSite(GURL("https://member2.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                               net::SiteType::kAssociated)},
+                               net::SiteType::kAssociated, 0)},
   };
   // Consistency check the reviewer-friendly format matches the input.
   ASSERT_THAT(
@@ -159,19 +164,19 @@
   FirstPartySetsHandlerImpl::FlattenedSets old_sets = {
       {net::SchemefulSite(GURL("https://example.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://example.test")),
-                               net::SiteType::kPrimary)},
+                               net::SiteType::kPrimary, absl::nullopt)},
       {net::SchemefulSite(GURL("https://member1.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://example.test")),
-                               net::SiteType::kAssociated)},
+                               net::SiteType::kAssociated, 0)},
       {net::SchemefulSite(GURL("https://member3.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://example.test")),
-                               net::SiteType::kAssociated)},
+                               net::SiteType::kAssociated, 1)},
       {net::SchemefulSite(GURL("https://foo.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                               net::SiteType::kPrimary)},
+                               net::SiteType::kPrimary, absl::nullopt)},
       {net::SchemefulSite(GURL("https://member2.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                               net::SiteType::kAssociated)}};
+                               net::SiteType::kAssociated, 0)}};
   // Consistency check the reviewer-friendly format matches the input.
   ASSERT_THAT(
       ParseSetsFromStream(R"({"owner": "https://example.test", "members": )"
@@ -182,10 +187,10 @@
   FirstPartySetsHandlerImpl::FlattenedSets current_sets = {
       {net::SchemefulSite(GURL("https://example.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://example.test")),
-                               net::SiteType::kPrimary)},
+                               net::SiteType::kPrimary, absl::nullopt)},
       {net::SchemefulSite(GURL("https://member1.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://example.test")),
-                               net::SiteType::kAssociated)}};
+                               net::SiteType::kAssociated, 0)}};
   // Consistency check the reviewer-friendly format matches the input.
   ASSERT_THAT(ParseSetsFromStream(R"({"owner": "https://example.test", )"
                                   R"("members": ["https://member1.test"]})"),
@@ -205,19 +210,19 @@
   FirstPartySetsHandlerImpl::FlattenedSets old_sets = {
       {net::SchemefulSite(GURL("https://example.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://example.test")),
-                               net::SiteType::kPrimary)},
+                               net::SiteType::kPrimary, absl::nullopt)},
       {net::SchemefulSite(GURL("https://member1.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://example.test")),
-                               net::SiteType::kAssociated)},
+                               net::SiteType::kAssociated, 0)},
       {net::SchemefulSite(GURL("https://foo.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                               net::SiteType::kPrimary)},
+                               net::SiteType::kPrimary, absl::nullopt)},
       {net::SchemefulSite(GURL("https://member2.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                               net::SiteType::kAssociated)},
+                               net::SiteType::kAssociated, 0)},
       {net::SchemefulSite(GURL("https://member3.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                               net::SiteType::kAssociated)}};
+                               net::SiteType::kAssociated, 1)}};
   // Consistency check the reviewer-friendly format matches the input.
   ASSERT_THAT(ParseSetsFromStream(
                   R"({"owner": "https://example.test", "members": )"
@@ -229,19 +234,19 @@
   FirstPartySetsHandlerImpl::FlattenedSets current_sets = {
       {net::SchemefulSite(GURL("https://example.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://example.test")),
-                               net::SiteType::kPrimary)},
+                               net::SiteType::kPrimary, absl::nullopt)},
       {net::SchemefulSite(GURL("https://member1.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://example.test")),
-                               net::SiteType::kAssociated)},
+                               net::SiteType::kAssociated, 0)},
       {net::SchemefulSite(GURL("https://member3.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://example.test")),
-                               net::SiteType::kAssociated)},
+                               net::SiteType::kAssociated, 1)},
       {net::SchemefulSite(GURL("https://foo.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                               net::SiteType::kPrimary)},
+                               net::SiteType::kPrimary, absl::nullopt)},
       {net::SchemefulSite(GURL("https://member2.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                               net::SiteType::kAssociated)}};
+                               net::SiteType::kAssociated, 0)}};
   // Consistency check the reviewer-friendly format matches the input.
   ASSERT_THAT(
       ParseSetsFromStream(R"({"owner": "https://example.test", "members": )"
@@ -260,13 +265,13 @@
   FirstPartySetsHandlerImpl::FlattenedSets old_sets = {
       {net::SchemefulSite(GURL("https://example.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://example.test")),
-                               net::SiteType::kPrimary)},
+                               net::SiteType::kPrimary, absl::nullopt)},
       {net::SchemefulSite(GURL("https://foo.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://example.test")),
-                               net::SiteType::kAssociated)},
+                               net::SiteType::kAssociated, 0)},
       {net::SchemefulSite(GURL("https://bar.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://example.test")),
-                               net::SiteType::kAssociated)}};
+                               net::SiteType::kAssociated, 1)}};
   // Consistency check the reviewer-friendly format matches the input.
   ASSERT_THAT(
       ParseSetsFromStream(R"({"owner": "https://example.test", "members": )"
@@ -276,10 +281,10 @@
   FirstPartySetsHandlerImpl::FlattenedSets current_sets = {
       {net::SchemefulSite(GURL("https://foo.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                               net::SiteType::kPrimary)},
+                               net::SiteType::kPrimary, absl::nullopt)},
       {net::SchemefulSite(GURL("https://bar.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                               net::SiteType::kAssociated)}};
+                               net::SiteType::kAssociated, 0)}};
   // Consistency check the reviewer-friendly format matches the input.
   ASSERT_THAT(ParseSetsFromStream(R"(
       {"owner": "https://foo.test", "members": ["https://bar.test"]})"),
@@ -302,10 +307,10 @@
   FirstPartySetsHandlerImpl::FlattenedSets old_sets = {
       {net::SchemefulSite(GURL("https://example.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://example.test")),
-                               net::SiteType::kPrimary)},
+                               net::SiteType::kPrimary, absl::nullopt)},
       {net::SchemefulSite(GURL("https://foo.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://example.test")),
-                               net::SiteType::kAssociated)}};
+                               net::SiteType::kAssociated, 0)}};
   // Consistency check the reviewer-friendly format matches the input.
   ASSERT_THAT(
       ParseSetsFromStream(R"({"owner": "https://example.test", "members": )"
@@ -315,10 +320,10 @@
   FirstPartySetsHandlerImpl::FlattenedSets current_sets = {
       {net::SchemefulSite(GURL("https://example.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                               net::SiteType::kAssociated)},
+                               net::SiteType::kAssociated, 0)},
       {net::SchemefulSite(GURL("https://foo.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                               net::SiteType::kPrimary)}};
+                               net::SiteType::kPrimary, absl::nullopt)}};
   // Consistency check the reviewer-friendly format matches the input.
   ASSERT_THAT(
       ParseSetsFromStream(
@@ -340,10 +345,10 @@
   FirstPartySetsHandlerImpl::FlattenedSets current_sets = {
       {net::SchemefulSite(GURL("https://example.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://example.test")),
-                               net::SiteType::kPrimary)},
+                               net::SiteType::kPrimary, absl::nullopt)},
       {net::SchemefulSite(GURL("https://member1.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://example.test")),
-                               net::SiteType::kAssociated)}};
+                               net::SiteType::kAssociated, 0)}};
   // Consistency check the reviewer-friendly format matches the input.
   ASSERT_THAT(ParseSetsFromStream(R"({"owner": "https://example.test", )"
                                   R"("members": ["https://member1.test"]})"),
@@ -360,10 +365,10 @@
   FirstPartySetsHandlerImpl::FlattenedSets old_sets = {
       {net::SchemefulSite(GURL("https://example.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://example.test")),
-                               net::SiteType::kPrimary)},
+                               net::SiteType::kPrimary, absl::nullopt)},
       {net::SchemefulSite(GURL("https://member1.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://example.test")),
-                               net::SiteType::kAssociated)}};
+                               net::SiteType::kAssociated, 0)}};
   // Consistency check the reviewer-friendly format matches the input.
   ASSERT_THAT(ParseSetsFromStream(R"({"owner": "https://example.test", )"
                                   R"("members": ["https://member1.test"]})"),
@@ -379,10 +384,10 @@
   FirstPartySetsHandlerImpl::PolicyCustomization current_policy = {
       {net::SchemefulSite(GURL("https://foo.test")),
        {net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                                net::SiteType::kPrimary)}},
+                                net::SiteType::kPrimary, absl::nullopt)}},
       {net::SchemefulSite(GURL("https://member2.test")),
        {net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                                net::SiteType::kAssociated)}},
+                                net::SiteType::kAssociated, 0)}},
   };
 
   // "https://example.test" and "https://member2.test" joined FPSs via
@@ -398,10 +403,10 @@
   FirstPartySetsHandlerImpl::FlattenedSets sets = {
       {net::SchemefulSite(GURL("https://example.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://example.test")),
-                               net::SiteType::kPrimary)},
+                               net::SiteType::kPrimary, absl::nullopt)},
       {net::SchemefulSite(GURL("https://member1.test")),
        net::FirstPartySetEntry(net::SchemefulSite(GURL("https://example.test")),
-                               net::SiteType::kAssociated)}};
+                               net::SiteType::kAssociated, 0)}};
   // Consistency check the reviewer-friendly format matches the input.
   ASSERT_THAT(ParseSetsFromStream(R"({"owner": "https://example.test",)"
                                   R"("members": ["https://member1.test"]})"),
@@ -411,10 +416,10 @@
   FirstPartySetsHandlerImpl::PolicyCustomization old_policy = {
       {net::SchemefulSite(GURL("https://foo.test")),
        {net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                                net::SiteType::kPrimary)}},
+                                net::SiteType::kPrimary, absl::nullopt)}},
       {net::SchemefulSite(GURL("https://member1.test")),
        {net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                                net::SiteType::kAssociated)}},
+                                net::SiteType::kAssociated, 0)}},
       {net::SchemefulSite(GURL("https://example.test")), absl::nullopt},
   };
 
@@ -422,13 +427,13 @@
   FirstPartySetsHandlerImpl::PolicyCustomization current_policy = {
       {net::SchemefulSite(GURL("https://foo.test")),
        {net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                                net::SiteType::kPrimary)}},
+                                net::SiteType::kPrimary, absl::nullopt)}},
       {net::SchemefulSite(GURL("https://member1.test")),
        {net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                                net::SiteType::kAssociated)}},
+                                net::SiteType::kAssociated, 0)}},
       {net::SchemefulSite(GURL("https://example.test")),
        {net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                                net::SiteType::kAssociated)}},
+                                net::SiteType::kAssociated, 0)}},
   };
 
   // We don't clear site data upon joining, so the computed diff should be
@@ -443,23 +448,23 @@
   FirstPartySetsHandlerImpl::PolicyCustomization old_policy = {
       {net::SchemefulSite(GURL("https://foo.test")),
        {net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                                net::SiteType::kPrimary)}},
+                                net::SiteType::kPrimary, absl::nullopt)}},
       {net::SchemefulSite(GURL("https://member1.test")),
        {net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                                net::SiteType::kAssociated)}},
+                                net::SiteType::kAssociated, 0)}},
       {net::SchemefulSite(GURL("https://member2.test")),
        {net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                                net::SiteType::kAssociated)}},
+                                net::SiteType::kAssociated, 0)}},
   };
 
   // "https://member2.test" left FPSs via enterprise policy.
   FirstPartySetsHandlerImpl::PolicyCustomization current_policy = {
       {net::SchemefulSite(GURL("https://foo.test")),
        {net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                                net::SiteType::kPrimary)}},
+                                net::SiteType::kPrimary, absl::nullopt)}},
       {net::SchemefulSite(GURL("https://member1.test")),
        {net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                                net::SiteType::kAssociated)}},
+                                net::SiteType::kAssociated, 0)}},
   };
 
   EXPECT_THAT(
@@ -473,26 +478,26 @@
       {net::SchemefulSite(GURL("https://example.test")),
        {net::FirstPartySetEntry(
            net::SchemefulSite(GURL("https://example.test")),
-           net::SiteType::kPrimary)}},
+           net::SiteType::kPrimary, absl::nullopt)}},
       {net::SchemefulSite(GURL("https://member1.test")),
        {net::FirstPartySetEntry(
            net::SchemefulSite(GURL("https://example.test")),
-           net::SiteType::kAssociated)}},
+           net::SiteType::kAssociated, 0)}},
       {net::SchemefulSite(GURL("https://member2.test")),
        {net::FirstPartySetEntry(
            net::SchemefulSite(GURL("https://example.test")),
-           net::SiteType::kAssociated)}},
+           net::SiteType::kAssociated, 0)}},
   };
 
   FirstPartySetsHandlerImpl::PolicyCustomization current_policy = {
       {net::SchemefulSite(GURL("https://member1.test")),
        {net::FirstPartySetEntry(
            net::SchemefulSite(GURL("https://member1.test")),
-           net::SiteType::kPrimary)}},
+           net::SiteType::kPrimary, absl::nullopt)}},
       {net::SchemefulSite(GURL("https://member2.test")),
        {net::FirstPartySetEntry(
            net::SchemefulSite(GURL("https://member1.test")),
-           net::SiteType::kAssociated)}},
+           net::SiteType::kAssociated, 0)}},
   };
 
   // Expected diff: "https://example.test" left FPSs, "https://member1.test" and
@@ -512,31 +517,31 @@
   FirstPartySetsHandlerImpl::PolicyCustomization old_policy = {
       {net::SchemefulSite(GURL("https://foo.test")),
        {net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                                net::SiteType::kPrimary)}},
+                                net::SiteType::kPrimary, absl::nullopt)}},
       {net::SchemefulSite(GURL("https://member1.test")),
        {net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                                net::SiteType::kAssociated)}},
+                                net::SiteType::kAssociated, 0)}},
       {net::SchemefulSite(GURL("https://bar.test")),
        {net::FirstPartySetEntry(net::SchemefulSite(GURL("https://bar.test")),
-                                net::SiteType::kPrimary)}},
+                                net::SiteType::kPrimary, absl::nullopt)}},
       {net::SchemefulSite(GURL("https://member2.test")),
        {net::FirstPartySetEntry(net::SchemefulSite(GURL("https://bar.test")),
-                                net::SiteType::kAssociated)}},
+                                net::SiteType::kAssociated, 0)}},
   };
 
   FirstPartySetsHandlerImpl::PolicyCustomization current_policy = {
       {net::SchemefulSite(GURL("https://foo.test")),
        {net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                                net::SiteType::kPrimary)}},
+                                net::SiteType::kPrimary, absl::nullopt)}},
       {net::SchemefulSite(GURL("https://member2.test")),
        {net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                                net::SiteType::kAssociated)}},
+                                net::SiteType::kAssociated, 0)}},
       {net::SchemefulSite(GURL("https://bar.test")),
        {net::FirstPartySetEntry(net::SchemefulSite(GURL("https://bar.test")),
-                                net::SiteType::kPrimary)}},
+                                net::SiteType::kPrimary, absl::nullopt)}},
       {net::SchemefulSite(GURL("https://member1.test")),
        {net::FirstPartySetEntry(net::SchemefulSite(GURL("https://bar.test")),
-                                net::SiteType::kAssociated)}},
+                                net::SiteType::kAssociated, 0)}},
   };
 
   EXPECT_THAT(
@@ -668,11 +673,11 @@
                   Pair(SerializesTo("https://example.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member1.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated))));
+                           net::SiteType::kAssociated, 0))));
 }
 
 TEST_F(FirstPartySetsHandlerImplEnabledTest,
@@ -685,34 +690,51 @@
   FirstPartySetsHandlerImpl::GetInstance()->SetPublicFirstPartySets(
       WritePublicSetsFile(input));
 
-  auto expected_sets = UnorderedElementsAre(
-      Pair(SerializesTo("https://example.test"),
-           net::FirstPartySetEntry(
-               net::SchemefulSite(GURL("https://example.test")),
-               net::SiteType::kPrimary)),
-      Pair(SerializesTo("https://member1.test"),
-           net::FirstPartySetEntry(
-               net::SchemefulSite(GURL("https://example.test")),
-               net::SiteType::kAssociated)),
-      Pair(SerializesTo("https://foo.test"),
-           net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                                   net::SiteType::kPrimary)),
-      Pair(SerializesTo("https://member2.test"),
-           net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                                   net::SiteType::kAssociated)));
-
   // Persisted sets are expected to be loaded with the provided path.
   FirstPartySetsHandlerImpl::GetInstance()->Init(
       scoped_dir_.GetPath(),
       /*flag_value=*/"https://example.test,https://member1.test");
-  EXPECT_THAT(GetSetsAndWait(), expected_sets);
+  EXPECT_THAT(GetSetsAndWait(),
+              UnorderedElementsAre(
+                  Pair(SerializesTo("https://example.test"),
+                       net::FirstPartySetEntry(
+                           net::SchemefulSite(GURL("https://example.test")),
+                           net::SiteType::kPrimary, absl::nullopt)),
+                  Pair(SerializesTo("https://member1.test"),
+                       net::FirstPartySetEntry(
+                           net::SchemefulSite(GURL("https://example.test")),
+                           net::SiteType::kAssociated, 0)),
+                  Pair(SerializesTo("https://foo.test"),
+                       net::FirstPartySetEntry(
+                           net::SchemefulSite(GURL("https://foo.test")),
+                           net::SiteType::kPrimary, absl::nullopt)),
+                  Pair(SerializesTo("https://member2.test"),
+                       net::FirstPartySetEntry(
+                           net::SchemefulSite(GURL("https://foo.test")),
+                           net::SiteType::kAssociated, 0))));
 
   env().RunUntilIdle();
 
   std::string got;
   ASSERT_TRUE(base::ReadFileToString(persisted_sets_path_, &got));
   EXPECT_THAT(FirstPartySetParser::DeserializeFirstPartySets(got),
-              expected_sets);
+              UnorderedElementsAre(
+                  Pair(SerializesTo("https://example.test"),
+                       net::FirstPartySetEntry(
+                           net::SchemefulSite(GURL("https://example.test")),
+                           net::SiteType::kPrimary, absl::nullopt)),
+                  Pair(SerializesTo("https://member1.test"),
+                       net::FirstPartySetEntry(
+                           net::SchemefulSite(GURL("https://example.test")),
+                           net::SiteType::kAssociated, absl::nullopt)),
+                  Pair(SerializesTo("https://foo.test"),
+                       net::FirstPartySetEntry(
+                           net::SchemefulSite(GURL("https://foo.test")),
+                           net::SiteType::kPrimary, absl::nullopt)),
+                  Pair(SerializesTo("https://member2.test"),
+                       net::FirstPartySetEntry(
+                           net::SchemefulSite(GURL("https://foo.test")),
+                           net::SiteType::kAssociated, absl::nullopt))));
 }
 
 TEST_F(FirstPartySetsHandlerImplEnabledTest, Successful_PersistedSetsEmpty) {
@@ -726,34 +748,51 @@
   FirstPartySetsHandlerImpl::GetInstance()->SetPublicFirstPartySets(
       WritePublicSetsFile(input));
 
-  auto expected_sets = UnorderedElementsAre(
-      Pair(SerializesTo("https://example.test"),
-           net::FirstPartySetEntry(
-               net::SchemefulSite(GURL("https://example.test")),
-               net::SiteType::kPrimary)),
-      Pair(SerializesTo("https://member1.test"),
-           net::FirstPartySetEntry(
-               net::SchemefulSite(GURL("https://example.test")),
-               net::SiteType::kAssociated)),
-      Pair(SerializesTo("https://foo.test"),
-           net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                                   net::SiteType::kPrimary)),
-      Pair(SerializesTo("https://member2.test"),
-           net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                                   net::SiteType::kAssociated)));
-
   // Persisted sets are expected to be loaded with the provided path.
   FirstPartySetsHandlerImpl::GetInstance()->Init(
       scoped_dir_.GetPath(),
       /*flag_value=*/"https://example.test,https://member1.test");
-  EXPECT_THAT(GetSetsAndWait(), expected_sets);
+  EXPECT_THAT(GetSetsAndWait(),
+              UnorderedElementsAre(
+                  Pair(SerializesTo("https://example.test"),
+                       net::FirstPartySetEntry(
+                           net::SchemefulSite(GURL("https://example.test")),
+                           net::SiteType::kPrimary, absl::nullopt)),
+                  Pair(SerializesTo("https://member1.test"),
+                       net::FirstPartySetEntry(
+                           net::SchemefulSite(GURL("https://example.test")),
+                           net::SiteType::kAssociated, 0)),
+                  Pair(SerializesTo("https://foo.test"),
+                       net::FirstPartySetEntry(
+                           net::SchemefulSite(GURL("https://foo.test")),
+                           net::SiteType::kPrimary, absl::nullopt)),
+                  Pair(SerializesTo("https://member2.test"),
+                       net::FirstPartySetEntry(
+                           net::SchemefulSite(GURL("https://foo.test")),
+                           net::SiteType::kAssociated, 0))));
 
   env().RunUntilIdle();
 
   std::string got;
   ASSERT_TRUE(base::ReadFileToString(persisted_sets_path_, &got));
   EXPECT_THAT(FirstPartySetParser::DeserializeFirstPartySets(got),
-              expected_sets);
+              UnorderedElementsAre(
+                  Pair(SerializesTo("https://example.test"),
+                       net::FirstPartySetEntry(
+                           net::SchemefulSite(GURL("https://example.test")),
+                           net::SiteType::kPrimary, absl::nullopt)),
+                  Pair(SerializesTo("https://member1.test"),
+                       net::FirstPartySetEntry(
+                           net::SchemefulSite(GURL("https://example.test")),
+                           net::SiteType::kAssociated, absl::nullopt)),
+                  Pair(SerializesTo("https://foo.test"),
+                       net::FirstPartySetEntry(
+                           net::SchemefulSite(GURL("https://foo.test")),
+                           net::SiteType::kPrimary, absl::nullopt)),
+                  Pair(SerializesTo("https://member2.test"),
+                       net::FirstPartySetEntry(
+                           net::SchemefulSite(GURL("https://foo.test")),
+                           net::SiteType::kAssociated, absl::nullopt))));
 }
 
 TEST_F(FirstPartySetsHandlerImplEnabledTest,
@@ -768,33 +807,48 @@
   FirstPartySetsHandlerImpl::GetInstance()->SetPublicFirstPartySets(
       WritePublicSetsFile(input));
 
-  auto expected_sets = UnorderedElementsAre(
-      Pair(SerializesTo("https://example.test"),
-           net::FirstPartySetEntry(
-               net::SchemefulSite(GURL("https://example.test")),
-               net::SiteType::kPrimary)),
-      Pair(SerializesTo("https://member.test"),
-           net::FirstPartySetEntry(
-               net::SchemefulSite(GURL("https://example.test")),
-               net::SiteType::kAssociated)));
-
   // Persisted sets are expected to be loaded with the provided path.
   FirstPartySetsHandlerImpl::GetInstance()->Init(scoped_dir_.GetPath(),
                                                  /*flag_value=*/"");
-  EXPECT_THAT(GetSetsAndWait(), expected_sets);
+  EXPECT_THAT(GetSetsAndWait(),
+              UnorderedElementsAre(
+                  Pair(SerializesTo("https://example.test"),
+                       net::FirstPartySetEntry(
+                           net::SchemefulSite(GURL("https://example.test")),
+                           net::SiteType::kPrimary, absl::nullopt)),
+                  Pair(SerializesTo("https://member.test"),
+                       net::FirstPartySetEntry(
+                           net::SchemefulSite(GURL("https://example.test")),
+                           net::SiteType::kAssociated, 0))));
 
   env().RunUntilIdle();
 
   std::string got;
   ASSERT_TRUE(base::ReadFileToString(persisted_sets_path_, &got));
   EXPECT_THAT(FirstPartySetParser::DeserializeFirstPartySets(got),
-              expected_sets);
+              UnorderedElementsAre(
+                  Pair(SerializesTo("https://example.test"),
+                       net::FirstPartySetEntry(
+                           net::SchemefulSite(GURL("https://example.test")),
+                           net::SiteType::kPrimary, absl::nullopt)),
+                  Pair(SerializesTo("https://member.test"),
+                       net::FirstPartySetEntry(
+                           net::SchemefulSite(GURL("https://example.test")),
+                           net::SiteType::kAssociated, absl::nullopt))));
 
   EXPECT_THAT(
       FirstPartySetsHandlerImpl::GetInstance()->GetSets(
           base::BindLambdaForTesting(
               [](FirstPartySetsHandlerImpl::FlattenedSets) { FAIL(); })),
-      testing::Optional(expected_sets));
+      testing::Optional(UnorderedElementsAre(
+          Pair(SerializesTo("https://example.test"),
+               net::FirstPartySetEntry(
+                   net::SchemefulSite(GURL("https://example.test")),
+                   net::SiteType::kPrimary, absl::nullopt)),
+          Pair(SerializesTo("https://member.test"),
+               net::FirstPartySetEntry(
+                   net::SchemefulSite(GURL("https://example.test")),
+                   net::SiteType::kAssociated, 0)))));
 }
 
 TEST_F(FirstPartySetsHandlerImplEnabledTest,
@@ -824,11 +878,11 @@
                   Pair(SerializesTo("https://example.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated))));
+                           net::SiteType::kAssociated, 0))));
 
   EXPECT_THAT(
       FirstPartySetsHandlerImpl::GetInstance()->GetSets(
@@ -838,11 +892,11 @@
           Pair(SerializesTo("https://example.test"),
                net::FirstPartySetEntry(
                    net::SchemefulSite(GURL("https://example.test")),
-                   net::SiteType::kPrimary)),
+                   net::SiteType::kPrimary, absl::nullopt)),
           Pair(SerializesTo("https://member.test"),
                net::FirstPartySetEntry(
                    net::SchemefulSite(GURL("https://example.test")),
-                   net::SiteType::kAssociated)))));
+                   net::SiteType::kAssociated, 0)))));
 }
 
 class FirstPartySetsHandlerGetCustomizationForPolicyTest
@@ -945,23 +999,23 @@
                   Pair(SerializesTo("https://owner1.test"),
                        Optional(net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://owner2.test")),
-                           net::SiteType::kAssociated))),
+                           net::SiteType::kAssociated, absl::nullopt))),
                   Pair(SerializesTo("https://member1.test"),
                        Optional(net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://member1.test")),
-                           net::SiteType::kPrimary))),
+                           net::SiteType::kPrimary, absl::nullopt))),
                   Pair(SerializesTo("https://owner3.test"),
                        Optional(net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://member1.test")),
-                           net::SiteType::kAssociated))),
+                           net::SiteType::kAssociated, absl::nullopt))),
                   Pair(SerializesTo("https://member2.test"),
                        Optional(net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://owner2.test")),
-                           net::SiteType::kAssociated))),
+                           net::SiteType::kAssociated, absl::nullopt))),
                   Pair(SerializesTo("https://owner2.test"),
                        Optional(net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://owner2.test")),
-                           net::SiteType::kPrimary)))));
+                           net::SiteType::kPrimary, absl::nullopt)))));
 }
 
 TEST(FirstPartySetsProfilePolicyCustomizations, EmptyPolicySetLists) {
@@ -987,11 +1041,11 @@
                   Pair(SerializesTo("https://member2.test"),
                        Optional(net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://owner2.test")),
-                           net::SiteType::kAssociated))),
+                           net::SiteType::kAssociated, absl::nullopt))),
                   Pair(SerializesTo("https://owner2.test"),
                        Optional(net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://owner2.test")),
-                           net::SiteType::kPrimary)))));
+                           net::SiteType::kPrimary, absl::nullopt)))));
 }
 
 // The common member between the policy and existing set is removed from its
@@ -1012,11 +1066,11 @@
                   Pair(SerializesTo("https://member1b.test"),
                        Optional(net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://owner2.test")),
-                           net::SiteType::kAssociated))),
+                           net::SiteType::kAssociated, absl::nullopt))),
                   Pair(SerializesTo("https://owner2.test"),
                        Optional(net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://owner2.test")),
-                           net::SiteType::kPrimary)))));
+                           net::SiteType::kPrimary, absl::nullopt)))));
 }
 
 // The common owner between the policy and existing set is removed and its
@@ -1037,11 +1091,11 @@
                   Pair(SerializesTo("https://member2.test"),
                        Optional(net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://owner1.test")),
-                           net::SiteType::kAssociated))),
+                           net::SiteType::kAssociated, absl::nullopt))),
                   Pair(SerializesTo("https://owner1.test"),
                        Optional(net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://owner1.test")),
-                           net::SiteType::kPrimary))),
+                           net::SiteType::kPrimary, absl::nullopt))),
                   Pair(SerializesTo("https://member1a.test"), absl::nullopt),
                   Pair(SerializesTo("https://member1b.test"), absl::nullopt)));
 }
@@ -1063,11 +1117,11 @@
                   Pair(SerializesTo("https://member1.test"),
                        Optional(net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://owner3.test")),
-                           net::SiteType::kAssociated))),
+                           net::SiteType::kAssociated, absl::nullopt))),
                   Pair(SerializesTo("https://owner3.test"),
                        Optional(net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://owner3.test")),
-                           net::SiteType::kPrimary))),
+                           net::SiteType::kPrimary, absl::nullopt))),
                   Pair(SerializesTo("https://owner1.test"), absl::nullopt)));
 }
 
@@ -1088,11 +1142,11 @@
                   Pair(SerializesTo("https://member2.test"),
                        Optional(net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://owner2.test")),
-                           net::SiteType::kAssociated))),
+                           net::SiteType::kAssociated, absl::nullopt))),
                   Pair(SerializesTo("https://owner2.test"),
                        Optional(net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://owner2.test")),
-                           net::SiteType::kPrimary)))));
+                           net::SiteType::kPrimary, absl::nullopt)))));
 }
 
 // The owner of a policy set is also a member in an existing set.
@@ -1113,19 +1167,19 @@
                   Pair(SerializesTo("https://owner1.test"),
                        Optional(net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://member2.test")),
-                           net::SiteType::kAssociated))),
+                           net::SiteType::kAssociated, absl::nullopt))),
                   Pair(SerializesTo("https://member2a.test"),
                        Optional(net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://member2.test")),
-                           net::SiteType::kAssociated))),
+                           net::SiteType::kAssociated, absl::nullopt))),
                   Pair(SerializesTo("https://member2b.test"),
                        Optional(net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://member2.test")),
-                           net::SiteType::kAssociated))),
+                           net::SiteType::kAssociated, absl::nullopt))),
                   Pair(SerializesTo("https://member2.test"),
                        Optional(net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://member2.test")),
-                           net::SiteType::kPrimary)))));
+                           net::SiteType::kPrimary, absl::nullopt)))));
 }
 
 // The owner of a policy set is also an owner of an existing set.
@@ -1146,19 +1200,19 @@
                   Pair(SerializesTo("https://member2.test"),
                        Optional(net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://owner1.test")),
-                           net::SiteType::kAssociated))),
+                           net::SiteType::kAssociated, absl::nullopt))),
                   Pair(SerializesTo("https://member1.test"),
                        Optional(net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://owner1.test")),
-                           net::SiteType::kAssociated))),
+                           net::SiteType::kAssociated, absl::nullopt))),
                   Pair(SerializesTo("https://member3.test"),
                        Optional(net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://owner1.test")),
-                           net::SiteType::kAssociated))),
+                           net::SiteType::kAssociated, absl::nullopt))),
                   Pair(SerializesTo("https://owner1.test"),
                        Optional(net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://owner1.test")),
-                           net::SiteType::kPrimary)))));
+                           net::SiteType::kPrimary, absl::nullopt)))));
 }
 
 TEST(FirstPartySetsProfilePolicyCustomizations,
@@ -1183,44 +1237,53 @@
           FirstPartySetParser::ParsedPolicySetLists(
               /*replacement_list=*/{},
               {
-                  SingleSet(
-                      {{owner0, net::FirstPartySetEntry(
-                                    owner0, net::SiteType::kPrimary)},
-                       {member0, net::FirstPartySetEntry(
-                                     owner0, net::SiteType::kAssociated)}}),
-                  SingleSet(
-                      {{owner1, net::FirstPartySetEntry(
-                                    owner1, net::SiteType::kPrimary)},
-                       {member1, net::FirstPartySetEntry(
-                                     owner1, net::SiteType::kAssociated)}}),
-                  SingleSet(
-                      {{owner2, net::FirstPartySetEntry(
-                                    owner2, net::SiteType::kPrimary)},
-                       {member2, net::FirstPartySetEntry(
-                                     owner2, net::SiteType::kAssociated)}}),
-                  SingleSet(
-                      {{owner42, net::FirstPartySetEntry(
-                                     owner42, net::SiteType::kPrimary)},
-                       {member42, net::FirstPartySetEntry(
-                                      owner42, net::SiteType::kAssociated)}}),
+                  SingleSet({{owner0, net::FirstPartySetEntry(
+                                          owner0, net::SiteType::kPrimary,
+                                          absl::nullopt)},
+                             {member0, net::FirstPartySetEntry(
+                                           owner0, net::SiteType::kAssociated,
+                                           absl::nullopt)}}),
+                  SingleSet({{owner1, net::FirstPartySetEntry(
+                                          owner1, net::SiteType::kPrimary,
+                                          absl::nullopt)},
+                             {member1, net::FirstPartySetEntry(
+                                           owner1, net::SiteType::kAssociated,
+                                           absl::nullopt)}}),
+                  SingleSet({{owner2, net::FirstPartySetEntry(
+                                          owner2, net::SiteType::kPrimary,
+                                          absl::nullopt)},
+                             {member2, net::FirstPartySetEntry(
+                                           owner2, net::SiteType::kAssociated,
+                                           absl::nullopt)}}),
+                  SingleSet({{owner42, net::FirstPartySetEntry(
+                                           owner42, net::SiteType::kPrimary,
+                                           absl::nullopt)},
+                             {member42, net::FirstPartySetEntry(
+                                            owner42, net::SiteType::kAssociated,
+                                            absl::nullopt)}}),
               })),
       UnorderedElementsAre(
-          Pair(member0, absl::make_optional(net::FirstPartySetEntry(
-                            owner0, net::SiteType::kAssociated))),
-          Pair(member1, absl::make_optional(net::FirstPartySetEntry(
-                            owner1, net::SiteType::kAssociated))),
-          Pair(member2, absl::make_optional(net::FirstPartySetEntry(
-                            owner1, net::SiteType::kAssociated))),
-          Pair(member42, absl::make_optional(net::FirstPartySetEntry(
-                             owner42, net::SiteType::kAssociated))),
+          Pair(member0,
+               absl::make_optional(net::FirstPartySetEntry(
+                   owner0, net::SiteType::kAssociated, absl::nullopt))),
+          Pair(member1,
+               absl::make_optional(net::FirstPartySetEntry(
+                   owner1, net::SiteType::kAssociated, absl::nullopt))),
+          Pair(member2,
+               absl::make_optional(net::FirstPartySetEntry(
+                   owner1, net::SiteType::kAssociated, absl::nullopt))),
+          Pair(member42,
+               absl::make_optional(net::FirstPartySetEntry(
+                   owner42, net::SiteType::kAssociated, absl::nullopt))),
           Pair(owner0, absl::make_optional(net::FirstPartySetEntry(
-                           owner0, net::SiteType::kPrimary))),
+                           owner0, net::SiteType::kPrimary, absl::nullopt))),
           Pair(owner1, absl::make_optional(net::FirstPartySetEntry(
-                           owner1, net::SiteType::kPrimary))),
+                           owner1, net::SiteType::kPrimary, absl::nullopt))),
           Pair(owner2, absl::make_optional(net::FirstPartySetEntry(
-                           owner1, net::SiteType::kAssociated))),
-          Pair(owner42, absl::make_optional(net::FirstPartySetEntry(
-                            owner42, net::SiteType::kPrimary)))));
+                           owner1, net::SiteType::kAssociated, absl::nullopt))),
+          Pair(owner42,
+               absl::make_optional(net::FirstPartySetEntry(
+                   owner42, net::SiteType::kPrimary, absl::nullopt)))));
 }
 
 TEST(FirstPartySetsProfilePolicyCustomizations,
@@ -1245,44 +1308,53 @@
           FirstPartySetParser::ParsedPolicySetLists(
               /*replacement_list=*/{},
               {
-                  SingleSet(
-                      {{owner0, net::FirstPartySetEntry(
-                                    owner0, net::SiteType::kPrimary)},
-                       {member0, net::FirstPartySetEntry(
-                                     owner0, net::SiteType::kAssociated)}}),
-                  SingleSet(
-                      {{owner2, net::FirstPartySetEntry(
-                                    owner2, net::SiteType::kPrimary)},
-                       {member2, net::FirstPartySetEntry(
-                                     owner2, net::SiteType::kAssociated)}}),
-                  SingleSet(
-                      {{owner1, net::FirstPartySetEntry(
-                                    owner1, net::SiteType::kPrimary)},
-                       {member1, net::FirstPartySetEntry(
-                                     owner1, net::SiteType::kAssociated)}}),
-                  SingleSet(
-                      {{owner42, net::FirstPartySetEntry(
-                                     owner42, net::SiteType::kPrimary)},
-                       {member42, net::FirstPartySetEntry(
-                                      owner42, net::SiteType::kAssociated)}}),
+                  SingleSet({{owner0, net::FirstPartySetEntry(
+                                          owner0, net::SiteType::kPrimary,
+                                          absl::nullopt)},
+                             {member0, net::FirstPartySetEntry(
+                                           owner0, net::SiteType::kAssociated,
+                                           absl::nullopt)}}),
+                  SingleSet({{owner2, net::FirstPartySetEntry(
+                                          owner2, net::SiteType::kPrimary,
+                                          absl::nullopt)},
+                             {member2, net::FirstPartySetEntry(
+                                           owner2, net::SiteType::kAssociated,
+                                           absl::nullopt)}}),
+                  SingleSet({{owner1, net::FirstPartySetEntry(
+                                          owner1, net::SiteType::kPrimary,
+                                          absl::nullopt)},
+                             {member1, net::FirstPartySetEntry(
+                                           owner1, net::SiteType::kAssociated,
+                                           absl::nullopt)}}),
+                  SingleSet({{owner42, net::FirstPartySetEntry(
+                                           owner42, net::SiteType::kPrimary,
+                                           absl::nullopt)},
+                             {member42, net::FirstPartySetEntry(
+                                            owner42, net::SiteType::kAssociated,
+                                            absl::nullopt)}}),
               })),
       UnorderedElementsAre(
-          Pair(member0, absl::make_optional(net::FirstPartySetEntry(
-                            owner0, net::SiteType::kAssociated))),
-          Pair(member1, absl::make_optional(net::FirstPartySetEntry(
-                            owner2, net::SiteType::kAssociated))),
-          Pair(member2, absl::make_optional(net::FirstPartySetEntry(
-                            owner2, net::SiteType::kAssociated))),
-          Pair(member42, absl::make_optional(net::FirstPartySetEntry(
-                             owner42, net::SiteType::kAssociated))),
+          Pair(member0,
+               absl::make_optional(net::FirstPartySetEntry(
+                   owner0, net::SiteType::kAssociated, absl::nullopt))),
+          Pair(member1,
+               absl::make_optional(net::FirstPartySetEntry(
+                   owner2, net::SiteType::kAssociated, absl::nullopt))),
+          Pair(member2,
+               absl::make_optional(net::FirstPartySetEntry(
+                   owner2, net::SiteType::kAssociated, absl::nullopt))),
+          Pair(member42,
+               absl::make_optional(net::FirstPartySetEntry(
+                   owner42, net::SiteType::kAssociated, absl::nullopt))),
           Pair(owner0, absl::make_optional(net::FirstPartySetEntry(
-                           owner0, net::SiteType::kPrimary))),
+                           owner0, net::SiteType::kPrimary, absl::nullopt))),
           Pair(owner1, absl::make_optional(net::FirstPartySetEntry(
-                           owner2, net::SiteType::kAssociated))),
+                           owner2, net::SiteType::kAssociated, absl::nullopt))),
           Pair(owner2, absl::make_optional(net::FirstPartySetEntry(
-                           owner2, net::SiteType::kPrimary))),
-          Pair(owner42, absl::make_optional(net::FirstPartySetEntry(
-                            owner42, net::SiteType::kPrimary)))));
+                           owner2, net::SiteType::kPrimary, absl::nullopt))),
+          Pair(owner42,
+               absl::make_optional(net::FirstPartySetEntry(
+                   owner42, net::SiteType::kPrimary, absl::nullopt)))));
 }
 
 // Existing set overlaps with both replacement and addition set.
@@ -1303,22 +1375,22 @@
                   Pair(SerializesTo("https://member1.test"),
                        Optional(net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://owner0.test")),
-                           net::SiteType::kAssociated))),
+                           net::SiteType::kAssociated, absl::nullopt))),
                   Pair(SerializesTo("https://owner0.test"),
                        Optional(net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://owner0.test")),
-                           net::SiteType::kPrimary))),
+                           net::SiteType::kPrimary, absl::nullopt))),
                   Pair(SerializesTo("https://new-member1.test"),
                        Optional(net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://owner1.test")),
-                           net::SiteType::kAssociated))),
+                           net::SiteType::kAssociated, absl::nullopt))),
                   Pair(SerializesTo("https://member2.test"),
                        Optional(net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://owner1.test")),
-                           net::SiteType::kAssociated))),
+                           net::SiteType::kAssociated, absl::nullopt))),
                   Pair(SerializesTo("https://owner1.test"),
                        Optional(net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://owner1.test")),
-                           net::SiteType::kPrimary)))));
+                           net::SiteType::kPrimary, absl::nullopt)))));
 }
 }  // namespace content
diff --git a/content/browser/first_party_sets/first_party_sets_loader.cc b/content/browser/first_party_sets/first_party_sets_loader.cc
index be17bddb..825048c 100644
--- a/content/browser/first_party_sets/first_party_sets_loader.cc
+++ b/content/browser/first_party_sets/first_party_sets_loader.cc
@@ -42,14 +42,20 @@
 
   const net::SchemefulSite& owner = *maybe_owner;
   std::vector<std::pair<net::SchemefulSite, net::FirstPartySetEntry>> sites(
-      {{owner, net::FirstPartySetEntry(owner, net::SiteType::kPrimary)}});
+      {{owner, net::FirstPartySetEntry(owner, net::SiteType::kPrimary,
+                                       absl::nullopt)}});
+  base::flat_set<net::SchemefulSite> associated_sites;
   for (auto it = origins.begin() + 1; it != origins.end(); ++it) {
     const absl::optional<net::SchemefulSite> maybe_member =
         content::FirstPartySetParser::CanonicalizeRegisteredDomain(
             *it, true /* emit_errors */);
-    if (maybe_member.has_value() && maybe_member != owner)
+    if (maybe_member.has_value() && maybe_member != owner &&
+        !base::Contains(associated_sites, *maybe_member)) {
       sites.emplace_back(*maybe_member, net::FirstPartySetEntry(
-                                            owner, net::SiteType::kAssociated));
+                                            owner, net::SiteType::kAssociated,
+                                            associated_sites.size()));
+      associated_sites.insert(*maybe_member);
+    }
   }
 
   if (sites.size() < 2) {
diff --git a/content/browser/first_party_sets/first_party_sets_loader_unittest.cc b/content/browser/first_party_sets/first_party_sets_loader_unittest.cc
index 5a7b85f..ca23bbed 100644
--- a/content/browser/first_party_sets/first_party_sets_loader_unittest.cc
+++ b/content/browser/first_party_sets/first_party_sets_loader_unittest.cc
@@ -98,11 +98,11 @@
                   Pair(SerializesTo("https://example.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://aaaa.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated))));
+                           net::SiteType::kAssociated, 0))));
 }
 
 TEST_F(FirstPartySetsLoaderTest, AcceptsMultipleSets) {
@@ -121,19 +121,19 @@
                   Pair(SerializesTo("https://example.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member1.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated)),
+                           net::SiteType::kAssociated, 0)),
                   Pair(SerializesTo("https://foo.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://foo.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member2.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://foo.test")),
-                           net::SiteType::kAssociated))));
+                           net::SiteType::kAssociated, 0))));
 }
 
 TEST_F(FirstPartySetsLoaderTest, SetComponentSets_Idempotent) {
@@ -156,19 +156,19 @@
                   Pair(SerializesTo("https://example.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member1.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated)),
+                           net::SiteType::kAssociated, 0)),
                   Pair(SerializesTo("https://foo.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://foo.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member2.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://foo.test")),
-                           net::SiteType::kAssociated))));
+                           net::SiteType::kAssociated, 0))));
 }
 
 TEST_F(FirstPartySetsLoaderTest, OwnerIsOnlyMember) {
@@ -263,11 +263,11 @@
                   Pair(SerializesTo("https://example.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated))));
+                           net::SiteType::kAssociated, 0))));
 }
 
 TEST_F(FirstPartySetsLoaderTest,
@@ -282,11 +282,11 @@
                   Pair(SerializesTo("https://example.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated))));
+                           net::SiteType::kAssociated, 0))));
 }
 
 TEST_F(FirstPartySetsLoaderTest, SetsManuallySpecified_Valid_MultipleMembers) {
@@ -300,15 +300,15 @@
                   Pair(SerializesTo("https://example.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member1.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated)),
+                           net::SiteType::kAssociated, 0)),
                   Pair(SerializesTo("https://member2.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated))));
+                           net::SiteType::kAssociated, 1))));
 }
 
 TEST_F(FirstPartySetsLoaderTest,
@@ -331,11 +331,11 @@
                   Pair(SerializesTo("https://example.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member1.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated))));
+                           net::SiteType::kAssociated, 0))));
 }
 
 TEST_F(FirstPartySetsLoaderTest, SetsManuallySpecified_Valid_RepeatedMember) {
@@ -351,15 +351,15 @@
                   Pair(SerializesTo("https://example.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member1.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated)),
+                           net::SiteType::kAssociated, 0)),
                   Pair(SerializesTo("https://member2.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated))));
+                           net::SiteType::kAssociated, 1))));
 }
 
 TEST_F(FirstPartySetsLoaderTest, SetsManuallySpecified_DeduplicatesOwnerOwner) {
@@ -375,23 +375,23 @@
                   Pair(SerializesTo("https://example.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member1.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated)),
+                           net::SiteType::kAssociated, 0)),
                   Pair(SerializesTo("https://member2.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated)),
+                           net::SiteType::kAssociated, 1)),
                   Pair(SerializesTo("https://bar.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://bar.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member4.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://bar.test")),
-                           net::SiteType::kAssociated))));
+                           net::SiteType::kAssociated, 0))));
 }
 
 TEST_F(FirstPartySetsLoaderTest,
@@ -408,23 +408,23 @@
                   Pair(SerializesTo("https://example.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member1.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated)),
+                           net::SiteType::kAssociated, 0)),
                   Pair(SerializesTo("https://member3.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated)),
+                           net::SiteType::kAssociated, 1)),
                   Pair(SerializesTo("https://bar.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://bar.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member2.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://bar.test")),
-                           net::SiteType::kAssociated))));
+                           net::SiteType::kAssociated, 0))));
 }
 
 TEST_F(FirstPartySetsLoaderTest,
@@ -440,23 +440,23 @@
                   Pair(SerializesTo("https://example.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member3.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated)),
+                           net::SiteType::kAssociated, 0)),
                   Pair(SerializesTo("https://foo.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://foo.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member1.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://foo.test")),
-                           net::SiteType::kAssociated)),
+                           net::SiteType::kAssociated, 0)),
                   Pair(SerializesTo("https://member2.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://foo.test")),
-                           net::SiteType::kAssociated))));
+                           net::SiteType::kAssociated, 1))));
 }
 
 TEST_F(FirstPartySetsLoaderTest,
@@ -473,31 +473,31 @@
                   Pair(SerializesTo("https://example.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member1.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated)),
+                           net::SiteType::kAssociated, 0)),
                   Pair(SerializesTo("https://member2.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated)),
+                           net::SiteType::kAssociated, 1)),
                   Pair(SerializesTo("https://foo.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://foo.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member3.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://foo.test")),
-                           net::SiteType::kAssociated)),
+                           net::SiteType::kAssociated, 1)),
                   Pair(SerializesTo("https://bar.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://bar.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member4.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://bar.test")),
-                           net::SiteType::kAssociated))));
+                           net::SiteType::kAssociated, 0))));
 }
 
 TEST_F(FirstPartySetsLoaderTest,
@@ -516,11 +516,11 @@
                   Pair(SerializesTo("https://example.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member1.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated))));
+                           net::SiteType::kAssociated, 0))));
 }
 
 }  // namespace content
diff --git a/content/browser/first_party_sets/test/first_party_set_parser_map_fuzzer.cc b/content/browser/first_party_sets/test/first_party_set_parser_map_fuzzer.cc
index 08598d8..516e2c21 100644
--- a/content/browser/first_party_sets/test/first_party_set_parser_map_fuzzer.cc
+++ b/content/browser/first_party_sets/test/first_party_set_parser_map_fuzzer.cc
@@ -11,6 +11,7 @@
 #include "net/base/schemeful_site.h"
 #include "net/cookies/first_party_set_entry.h"
 #include "testing/libfuzzer/proto/lpm_interface.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "url/gurl.h"
 
 namespace content {
@@ -32,11 +33,16 @@
   for (const firstpartysets::proto::SitePair& item : sets.items()) {
     auto member_or_owner = GetSchemefulSite(item.member_or_owner());
     auto owner = GetSchemefulSite(item.owner());
-    map.emplace(
-        std::move(member_or_owner),
-        net::FirstPartySetEntry(owner, member_or_owner == owner
-                                           ? net::SiteType::kPrimary
-                                           : net::SiteType::kAssociated));
+    net::SiteType site_type = member_or_owner == owner
+                                  ? net::SiteType::kPrimary
+                                  : net::SiteType::kAssociated;
+    absl::optional<net::FirstPartySetEntry::SiteIndex> site_index =
+        site_type == net::SiteType::kPrimary
+            ? absl::nullopt
+            : absl::make_optional(
+                  net::FirstPartySetEntry::SiteIndex(map.size()));
+    map.emplace(member_or_owner,
+                net::FirstPartySetEntry(owner, site_type, site_index));
   }
   return map;
 }
diff --git a/content/browser/media/session/media_session_impl.cc b/content/browser/media/session/media_session_impl.cc
index 6c7be7d..2096290 100644
--- a/content/browser/media/session/media_session_impl.cc
+++ b/content/browser/media/session/media_session_impl.cc
@@ -7,12 +7,10 @@
 #include <algorithm>
 #include <memory>
 #include <utility>
-#include <vector>
 
 #include "base/bind.h"
 #include "base/containers/contains.h"
 #include "base/cxx17_backports.h"
-#include "base/numerics/safe_conversions.h"
 #include "base/ranges/algorithm.h"
 #include "base/strings/string_util.h"
 #include "base/time/time.h"
@@ -41,9 +39,7 @@
 #include "services/media_session/public/mojom/audio_focus.mojom.h"
 #include "third_party/blink/public/mojom/favicon/favicon_url.mojom.h"
 #include "third_party/blink/public/strings/grit/blink_strings.h"
-#include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/gfx/favicon_size.h"
-#include "ui/gfx/image/image_util.h"
 
 #if BUILDFLAG(IS_ANDROID)
 #include "content/browser/media/session/media_session_android.h"
@@ -869,25 +865,17 @@
     const GURL& image_url,
     const std::vector<SkBitmap>& bitmaps,
     const std::vector<gfx::Size>& sizes) {
-  DCHECK_EQ(bitmaps.size(), sizes.size());
-
-  std::vector<SkBitmap> filtered_images;
-  std::vector<gfx::Size> filtered_bitmap_sizes;
-  gfx::FilterAndResizeImagesForMaximalSize(
-      bitmaps, base::checked_cast<uint32_t>(desired_size_px), filtered_images,
-      filtered_bitmap_sizes);
-  DCHECK_EQ(filtered_images.size(), filtered_bitmap_sizes.size());
-
+  DCHECK(bitmaps.size() == sizes.size());
   SkBitmap image;
   double best_image_score = 0.0;
 
-  // Rank |filtered_bitmap_sizes| and |filtered_images| using MediaImageManager.
-  for (size_t i = 0; i < filtered_images.size(); i++) {
+  // Rank |sizes| and |bitmaps| using MediaImageManager.
+  for (size_t i = 0; i < bitmaps.size(); i++) {
     double image_score = media_session::MediaImageManager::GetImageSizeScore(
-        minimum_size_px, desired_size_px, filtered_bitmap_sizes.at(i));
+        minimum_size_px, desired_size_px, sizes.at(i));
 
     if (image_score > best_image_score)
-      image = filtered_images.at(i);
+      image = bitmaps.at(i);
   }
 
   // If the image is the wrong color type then we should convert it.
@@ -1334,7 +1322,7 @@
   const gfx::Size preferred_size(desired_size_px, desired_size_px);
   web_contents()->DownloadImage(
       image.src, false /* is_favicon */, preferred_size,
-      /*max_bitmap_size=*/0, false /* bypass_cache */,
+      desired_size_px /* max_bitmap_size */, false /* bypass_cache */,
       base::BindOnce(&MediaSessionImpl::OnImageDownloadComplete,
                      base::Unretained(this),
                      mojo::WrapCallbackWithDefaultInvokeIfNotRun(
diff --git a/content/browser/notifications/platform_notification_context_impl.cc b/content/browser/notifications/platform_notification_context_impl.cc
index 61ac1d77..fdd7485 100644
--- a/content/browser/notifications/platform_notification_context_impl.cc
+++ b/content/browser/notifications/platform_notification_context_impl.cc
@@ -24,6 +24,7 @@
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/notification_database_data.h"
 #include "content/public/browser/permission_controller.h"
+#include "content/public/browser/permission_result.h"
 #include "content/public/browser/platform_notification_service.h"
 #include "content/public/common/content_client.h"
 #include "content/public/common/content_features.h"
@@ -365,8 +366,11 @@
 
   // Erase all valid origins so we're left with invalid ones.
   base::EraseIf(origins, [controller](const GURL& origin) {
-    auto permission = controller->GetPermissionStatusForOriginWithoutContext(
-        blink::PermissionType::NOTIFICATIONS, url::Origin::Create(origin));
+    auto permission = controller
+                          ->GetPermissionResultForOriginWithoutContext(
+                              blink::PermissionType::NOTIFICATIONS,
+                              url::Origin::Create(origin))
+                          .status;
     return permission == blink::mojom::PermissionStatus::GRANTED;
   });
 
diff --git a/content/browser/notifications/platform_notification_context_unittest.cc b/content/browser/notifications/platform_notification_context_unittest.cc
index d080f61..c71967e 100644
--- a/content/browser/notifications/platform_notification_context_unittest.cc
+++ b/content/browser/notifications/platform_notification_context_unittest.cc
@@ -19,6 +19,7 @@
 #include "content/browser/service_worker/embedded_worker_test_helper.h"
 #include "content/browser/service_worker/service_worker_context_wrapper.h"
 #include "content/public/browser/notification_database_data.h"
+#include "content/public/browser/permission_result.h"
 #include "content/public/common/content_client.h"
 #include "content/public/common/content_features.h"
 #include "content/public/test/browser_task_environment.h"
@@ -210,10 +211,11 @@
 
   void SetPermissionStatus(const GURL& origin,
                            blink::mojom::PermissionStatus permission_status) {
-    ON_CALL(*permission_manager_,
-            GetPermissionStatus(blink::PermissionType::NOTIFICATIONS, origin,
-                                origin))
-        .WillByDefault(Return(permission_status));
+    ON_CALL(*permission_manager_, GetPermissionResultForOriginWithoutContext(
+                                      blink::PermissionType::NOTIFICATIONS,
+                                      url::Origin::Create(origin)))
+        .WillByDefault(Return(content::PermissionResult(
+            permission_status, PermissionStatusSource::UNSPECIFIED)));
   }
 
   // Returns the file path to the leveldb database for |context|.
diff --git a/content/browser/payments/installed_payment_apps_finder_impl.cc b/content/browser/payments/installed_payment_apps_finder_impl.cc
index 656ee65..05c2abf9 100644
--- a/content/browser/payments/installed_payment_apps_finder_impl.cc
+++ b/content/browser/payments/installed_payment_apps_finder_impl.cc
@@ -14,6 +14,7 @@
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/permission_controller.h"
+#include "content/public/browser/permission_result.h"
 #include "third_party/blink/public/common/permissions/permission_utils.h"
 #include "third_party/blink/public/mojom/permissions/permission_status.mojom.h"
 
@@ -78,10 +79,11 @@
   PaymentApps permitted_apps;
   for (auto& app : apps) {
     GURL origin = app.second->scope.DeprecatedGetOriginAsURL();
-    if (permission_controller->GetPermissionStatusForOriginWithoutContext(
-            blink::PermissionType::PAYMENT_HANDLER,
-            url::Origin::Create(origin)) ==
-        blink::mojom::PermissionStatus::GRANTED) {
+    if (permission_controller
+            ->GetPermissionResultForOriginWithoutContext(
+                blink::PermissionType::PAYMENT_HANDLER,
+                url::Origin::Create(origin))
+            .status == blink::mojom::PermissionStatus::GRANTED) {
       permitted_apps[app.first] = std::move(app.second);
     }
   }
diff --git a/content/browser/payments/payment_app_provider_impl_unittest.cc b/content/browser/payments/payment_app_provider_impl_unittest.cc
index 281b16ec..48ee071 100644
--- a/content/browser/payments/payment_app_provider_impl_unittest.cc
+++ b/content/browser/payments/payment_app_provider_impl_unittest.cc
@@ -73,10 +73,11 @@
     std::unique_ptr<MockPermissionManager> mock_permission_manager(
         new testing::NiceMock<MockPermissionManager>());
     ON_CALL(*mock_permission_manager,
-            GetPermissionStatus(blink::PermissionType::PAYMENT_HANDLER,
-                                testing::_, testing::_))
-        .WillByDefault(
-            testing::Return(blink::mojom::PermissionStatus::GRANTED));
+            GetPermissionResultForOriginWithoutContext(
+                blink::PermissionType::PAYMENT_HANDLER, testing::_))
+        .WillByDefault(testing::Return(
+            PermissionResult(blink::mojom::PermissionStatus::GRANTED,
+                             PermissionStatusSource::UNSPECIFIED)));
     static_cast<TestBrowserContext*>(browser_context())
         ->SetPermissionControllerDelegate(std::move(mock_permission_manager));
 
diff --git a/content/browser/permissions/permission_controller_impl.cc b/content/browser/permissions/permission_controller_impl.cc
index 4f292be8..d8c23c0 100644
--- a/content/browser/permissions/permission_controller_impl.cc
+++ b/content/browser/permissions/permission_controller_impl.cc
@@ -11,13 +11,12 @@
 #include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/permission_controller_delegate.h"
+#include "content/public/browser/permission_result.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
 #include "third_party/blink/public/common/permissions/permission_utils.h"
 #include "third_party/blink/public/mojom/permissions/permission_status.mojom.h"
 
-class GURL;
-
 namespace content {
 
 namespace {
@@ -167,9 +166,9 @@
         url::Origin::Create(subscription.requesting_origin));
   }
 
-  return DeprecatedGetPermissionStatus(subscription.permission,
-                                       subscription.requesting_origin,
-                                       subscription.embedding_origin);
+  return GetPermissionStatusInternal(subscription.permission,
+                                     subscription.requesting_origin,
+                                     subscription.embedding_origin);
 }
 
 PermissionControllerImpl::SubscriptionsStatusMap
@@ -335,7 +334,7 @@
 }
 
 blink::mojom::PermissionStatus
-PermissionControllerImpl::DeprecatedGetPermissionStatus(
+PermissionControllerImpl::GetPermissionStatusInternal(
     PermissionType permission,
     const GURL& requesting_origin,
     const GURL& embedding_origin) {
@@ -349,6 +348,7 @@
       browser_context_->GetPermissionControllerDelegate();
   if (!delegate)
     return blink::mojom::PermissionStatus::DENIED;
+
   return delegate->GetPermissionStatus(permission, requesting_origin,
                                        embedding_origin);
 }
@@ -389,12 +389,52 @@
                                                          render_frame_host);
 }
 
+PermissionResult
+PermissionControllerImpl::GetPermissionResultForCurrentDocument(
+    PermissionType permission,
+    RenderFrameHost* render_frame_host) {
+  absl::optional<blink::mojom::PermissionStatus> status =
+      devtools_permission_overrides_.Get(
+          render_frame_host->GetLastCommittedOrigin(), permission);
+  if (status)
+    return PermissionResult(*status, PermissionStatusSource::UNSPECIFIED);
+
+  PermissionControllerDelegate* delegate =
+      browser_context_->GetPermissionControllerDelegate();
+  if (!delegate)
+    return PermissionResult(blink::mojom::PermissionStatus::DENIED,
+                            PermissionStatusSource::UNSPECIFIED);
+
+  return delegate->GetPermissionResultForCurrentDocument(permission,
+                                                         render_frame_host);
+}
+
+PermissionResult
+PermissionControllerImpl::GetPermissionResultForOriginWithoutContext(
+    PermissionType permission,
+    const url::Origin& origin) {
+  absl::optional<blink::mojom::PermissionStatus> status =
+      devtools_permission_overrides_.Get(origin, permission);
+  if (status)
+    return PermissionResult(*status, PermissionStatusSource::UNSPECIFIED);
+
+  PermissionControllerDelegate* delegate =
+      browser_context_->GetPermissionControllerDelegate();
+  if (!delegate)
+    return PermissionResult(blink::mojom::PermissionStatus::DENIED,
+                            PermissionStatusSource::UNSPECIFIED);
+
+  return delegate->GetPermissionResultForOriginWithoutContext(permission,
+                                                              origin);
+}
+
 blink::mojom::PermissionStatus
 PermissionControllerImpl::GetPermissionStatusForOriginWithoutContext(
     PermissionType permission,
-    const url::Origin& origin) {
-  return DeprecatedGetPermissionStatus(permission, origin.GetURL(),
-                                       origin.GetURL());
+    const url::Origin& requesting_origin,
+    const url::Origin& embedding_origin) {
+  return GetPermissionStatusInternal(permission, requesting_origin.GetURL(),
+                                     embedding_origin.GetURL());
 }
 
 void PermissionControllerImpl::ResetPermission(PermissionType permission,
diff --git a/content/browser/permissions/permission_controller_impl.h b/content/browser/permissions/permission_controller_impl.h
index 613867c..b332fca 100644
--- a/content/browser/permissions/permission_controller_impl.h
+++ b/content/browser/permissions/permission_controller_impl.h
@@ -23,6 +23,7 @@
 class PermissionControllerImplTest;
 class RenderProcessHost;
 class PermissionServiceImpl;
+struct PermissionResult;
 
 using blink::PermissionType;
 
@@ -76,7 +77,7 @@
   friend class PermissionControllerImplTest;
   friend class PermissionServiceImpl;
 
-  blink::mojom::PermissionStatus DeprecatedGetPermissionStatus(
+  blink::mojom::PermissionStatus GetPermissionStatusInternal(
       PermissionType permission,
       const GURL& requesting_origin,
       const GURL& embedding_origin);
@@ -89,9 +90,16 @@
   blink::mojom::PermissionStatus GetPermissionStatusForCurrentDocument(
       PermissionType permission,
       RenderFrameHost* render_frame_host) override;
-  blink::mojom::PermissionStatus GetPermissionStatusForOriginWithoutContext(
+  PermissionResult GetPermissionResultForCurrentDocument(
+      PermissionType permission,
+      RenderFrameHost* render_frame_host) override;
+  PermissionResult GetPermissionResultForOriginWithoutContext(
       PermissionType permission,
       const url::Origin& origin) override;
+  blink::mojom::PermissionStatus GetPermissionStatusForOriginWithoutContext(
+      blink::PermissionType permission,
+      const url::Origin& requesting_origin,
+      const url::Origin& embedding_origin) override;
   void RequestPermissionFromCurrentDocument(
       PermissionType permission,
       RenderFrameHost* render_frame_host,
diff --git a/content/browser/permissions/permission_service_impl.cc b/content/browser/permissions/permission_service_impl.cc
index d2b12f7..956ce9b 100644
--- a/content/browser/permissions/permission_service_impl.cc
+++ b/content/browser/permissions/permission_service_impl.cc
@@ -15,6 +15,7 @@
 #include "content/browser/bad_message.h"
 #include "content/browser/permissions/permission_controller_impl.h"
 #include "content/public/browser/browser_context.h"
+#include "content/public/browser/permission_result.h"
 #include "content/public/browser/render_frame_host.h"
 #include "third_party/blink/public/common/permissions/permission_utils.h"
 #include "third_party/blink/public/mojom/permissions/permission.mojom-shared.h"
@@ -211,7 +212,8 @@
 
   DCHECK(context_->GetEmbeddingOrigin().is_empty());
   return browser_context->GetPermissionController()
-      ->GetPermissionStatusForOriginWithoutContext(type, origin_);
+      ->GetPermissionResultForOriginWithoutContext(type, origin_)
+      .status;
 }
 
 void PermissionServiceImpl::ResetPermissionStatus(blink::PermissionType type) {
diff --git a/content/browser/renderer_host/render_widget_host_view_aura.cc b/content/browser/renderer_host/render_widget_host_view_aura.cc
index e44c788..5e45adfa 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura.cc
+++ b/content/browser/renderer_host/render_widget_host_view_aura.cc
@@ -1386,7 +1386,7 @@
 }
 
 bool RenderWidgetHostViewAura::GetCompositionCharacterBounds(
-    uint32_t index,
+    size_t index,
     gfx::Rect* rect) const {
   DCHECK(rect);
 
diff --git a/content/browser/renderer_host/render_widget_host_view_aura.h b/content/browser/renderer_host/render_widget_host_view_aura.h
index eba15e73..f406646 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura.h
+++ b/content/browser/renderer_host/render_widget_host_view_aura.h
@@ -223,7 +223,7 @@
   bool CanComposeInline() const override;
   gfx::Rect GetCaretBounds() const override;
   gfx::Rect GetSelectionBoundingBox() const override;
-  bool GetCompositionCharacterBounds(uint32_t index,
+  bool GetCompositionCharacterBounds(size_t index,
                                      gfx::Rect* rect) const override;
   bool HasCompositionText() const override;
   ui::TextInputClient::FocusReason GetFocusReason() const override;
diff --git a/content/browser/storage_partition_impl.cc b/content/browser/storage_partition_impl.cc
index 9606e4c..d2275a3 100644
--- a/content/browser/storage_partition_impl.cc
+++ b/content/browser/storage_partition_impl.cc
@@ -101,6 +101,7 @@
 #include "content/public/browser/login_delegate.h"
 #include "content/public/browser/network_service_instance.h"
 #include "content/public/browser/permission_controller.h"
+#include "content/public/browser/permission_result.h"
 #include "content/public/browser/service_process_host.h"
 #include "content/public/browser/session_storage_usage_info.h"
 #include "content/public/browser/shared_cors_origin_access_list.h"
@@ -2037,10 +2038,10 @@
 
   std::vector<url::Origin> origins_out;
   for (auto& origin : origins) {
-    bool allowed =
-        permission_controller->GetPermissionStatusForOriginWithoutContext(
-            blink::PermissionType::BACKGROUND_SYNC, origin) ==
-        blink::mojom::PermissionStatus::GRANTED;
+    bool allowed = permission_controller
+                       ->GetPermissionResultForOriginWithoutContext(
+                           blink::PermissionType::BACKGROUND_SYNC, origin)
+                       .status == blink::mojom::PermissionStatus::GRANTED;
     if (allowed)
       origins_out.push_back(origin);
   }
@@ -2054,11 +2055,12 @@
   DCHECK(initialized_);
   PermissionController* permission_controller =
       browser_context_->GetPermissionController();
-  std::move(callback).Run(
-      permission_controller->GetPermissionStatusForOriginWithoutContext(
-          blink::PermissionType::BACKGROUND_SYNC,
-          url::Origin::Create(origin)) ==
-      blink::mojom::PermissionStatus::GRANTED);
+  std::move(callback).Run(permission_controller
+                              ->GetPermissionResultForOriginWithoutContext(
+                                  blink::PermissionType::BACKGROUND_SYNC,
+                                  url::Origin::Create(origin))
+                              .status ==
+                          blink::mojom::PermissionStatus::GRANTED);
 }
 
 void StoragePartitionImpl::OnClearSiteData(
diff --git a/content/browser/web_contents/web_contents_android.cc b/content/browser/web_contents/web_contents_android.cc
index e7ccd06..4d57232 100644
--- a/content/browser/web_contents/web_contents_android.cc
+++ b/content/browser/web_contents/web_contents_android.cc
@@ -20,8 +20,6 @@
 #include "base/lazy_instance.h"
 #include "base/logging.h"
 #include "base/metrics/user_metrics.h"
-#include "base/numerics/safe_conversions.h"
-#include "base/task/thread_pool.h"
 #include "base/threading/scoped_blocking_call.h"
 #include "content/browser/android/java/gin_java_bridge_dispatcher_host.h"
 #include "content/browser/media/media_web_contents_observer.h"
@@ -37,9 +35,7 @@
 #include "content/public/browser/render_widget_host.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_switches.h"
-#include "skia/ext/image_operations.h"
 #include "third_party/blink/public/mojom/input/input_handler.mojom-blink.h"
-#include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/accessibility/ax_assistant_structure.h"
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/accessibility/ax_tree_update.h"
@@ -49,7 +45,6 @@
 #include "ui/gfx/android/java_bitmap.h"
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/rect.h"
-#include "ui/gfx/image/image_util.h"
 #include "url/android/gurl_android.h"
 #include "url/gurl.h"
 
@@ -726,19 +721,13 @@
     jint max_bitmap_size,
     jboolean bypass_cache,
     const base::android::JavaParamRef<jobject>& jcallback) {
-  ScopedJavaGlobalRef<jobject> j_callback;
-  j_callback.Reset(env, jcallback);
-  // The max_image_size of 0 being passed to web_contents_->DownloadImage()
-  // ensures that all images are fetched by the renderer before being
-  // resized/filtered out in WebContentsAndroid::OnFinishDownloadImage()
-  // using the max_bitmap_size in a separate threadpool.
   const gfx::Size preferred_size;
   return web_contents_->DownloadImage(
       *url::GURLAndroid::ToNativeGURL(env, jurl), is_fav_icon, preferred_size,
-      /*max_image_size=*/0, bypass_cache,
+      max_bitmap_size, bypass_cache,
       base::BindOnce(&WebContentsAndroid::OnFinishDownloadImage,
-                     weak_factory_.GetWeakPtr(), j_callback,
-                     base::checked_cast<uint32_t>(max_bitmap_size)));
+                     weak_factory_.GetWeakPtr(), obj_,
+                     ScopedJavaGlobalRef<jobject>(env, jcallback)));
 }
 
 void WebContentsAndroid::SetHasPersistentVideo(JNIEnv* env, jboolean value) {
@@ -782,45 +771,13 @@
 }
 
 void WebContentsAndroid::OnFinishDownloadImage(
-    const base::android::ScopedJavaGlobalRef<jobject>& callback,
-    uint32_t max_image_size,
-    int id,
-    int http_status_code,
-    const GURL& url,
-    const std::vector<SkBitmap>& bitmaps,
-    const std::vector<gfx::Size>& sizes) {
-  std::vector<SkBitmap> filtered_images;
-  std::vector<gfx::Size> filtered_bitmap_sizes;
-
-  base::ThreadPool::PostTaskAndReply(
-      FROM_HERE, {base::TaskPriority::USER_VISIBLE},
-      base::BindOnce(&WebContentsAndroid::FilterOrResizeReceivedImages,
-                     weak_factory_.GetWeakPtr(), bitmaps, max_image_size,
-                     std::ref(filtered_images),
-                     std::ref(filtered_bitmap_sizes)),
-      base::BindOnce(&WebContentsAndroid::ConvertToJavaBitmap,
-                     weak_factory_.GetWeakPtr(), obj_, callback, id,
-                     http_status_code, url, filtered_images,
-                     filtered_bitmap_sizes));
-}
-
-void WebContentsAndroid::FilterOrResizeReceivedImages(
-    const std::vector<SkBitmap>& bitmaps,
-    uint32_t max_image_size,
-    std::vector<SkBitmap>& filtered_images,
-    std::vector<gfx::Size>& filtered_bitmap_sizes) {
-  gfx::FilterAndResizeImagesForMaximalSize(
-      bitmaps, max_image_size, filtered_images, filtered_bitmap_sizes);
-}
-
-void WebContentsAndroid::ConvertToJavaBitmap(
     const JavaRef<jobject>& obj,
     const JavaRef<jobject>& callback,
     int id,
     int http_status_code,
     const GURL& url,
-    const std::vector<SkBitmap>& filtered_images,
-    const std::vector<gfx::Size>& filtered_bitmap_sizes) {
+    const std::vector<SkBitmap>& bitmaps,
+    const std::vector<gfx::Size>& sizes) {
   JNIEnv* env = base::android::AttachCurrentThread();
   ScopedJavaLocalRef<jobject> jbitmaps =
       Java_WebContentsImpl_createBitmapList(env);
@@ -828,15 +785,14 @@
       Java_WebContentsImpl_createSizeList(env);
   ScopedJavaLocalRef<jobject> jurl = url::GURLAndroid::FromNativeGURL(env, url);
 
-  DCHECK_EQ(filtered_images.size(), filtered_bitmap_sizes.size());
-  for (const SkBitmap& bitmap : filtered_images) {
-    // WARNING: converting to java bitmaps results in duplicate memory
+  for (const SkBitmap& bitmap : bitmaps) {
+    // WARNING: convering to java bitmaps results in duplicate memory
     // allocations, which increases the chance of OOMs if DownloadImage() is
     // misused.
     ScopedJavaLocalRef<jobject> jbitmap = gfx::ConvertToJavaBitmap(bitmap);
     Java_WebContentsImpl_addToBitmapList(env, jbitmaps, jbitmap);
   }
-  for (const gfx::Size& size : filtered_bitmap_sizes) {
+  for (const gfx::Size& size : sizes) {
     Java_WebContentsImpl_createSizeAndAddToList(env, jsizes, size.width(),
                                                 size.height());
   }
diff --git a/content/browser/web_contents/web_contents_android.h b/content/browser/web_contents/web_contents_android.h
index f28c0d19..2b278693 100644
--- a/content/browser/web_contents/web_contents_android.h
+++ b/content/browser/web_contents/web_contents_android.h
@@ -215,26 +215,13 @@
   void RemoveDestructionObserver(DestructionObserver* observer);
 
  private:
-  void OnFinishDownloadImage(
-      const base::android::ScopedJavaGlobalRef<jobject>& jcallback,
-      uint32_t max_image_size,
-      int id,
-      int http_status_code,
-      const GURL& url,
-      const std::vector<SkBitmap>& bitmaps,
-      const std::vector<gfx::Size>& sizes);
-  void FilterOrResizeReceivedImages(
-      const std::vector<SkBitmap>& bitmaps,
-      uint32_t max_image_size,
-      std::vector<SkBitmap>& filtered_images,
-      std::vector<gfx::Size>& filtered_bitmap_sizes);
-  void ConvertToJavaBitmap(const base::android::JavaRef<jobject>& obj,
-                           const base::android::JavaRef<jobject>& callback,
-                           int id,
-                           int http_status_code,
-                           const GURL& url,
-                           const std::vector<SkBitmap>& filtered_images,
-                           const std::vector<gfx::Size>& filtered_bitmap_sizes);
+  void OnFinishDownloadImage(const base::android::JavaRef<jobject>& obj,
+                             const base::android::JavaRef<jobject>& callback,
+                             int id,
+                             int http_status_code,
+                             const GURL& url,
+                             const std::vector<SkBitmap>& bitmaps,
+                             const std::vector<gfx::Size>& sizes);
   void SelectAroundCaretAck(blink::mojom::SelectAroundCaretResultPtr result);
   // Walks over the AXTreeUpdate and creates a light weight snapshot.
   void AXTreeSnapshotCallback(
diff --git a/content/browser/webid/fedcm_metrics.h b/content/browser/webid/fedcm_metrics.h
index 6352cae..1390216d 100644
--- a/content/browser/webid/fedcm_metrics.h
+++ b/content/browser/webid/fedcm_metrics.h
@@ -38,7 +38,7 @@
   kIdTokenHttpNotFound,
   kIdTokenNoResponse,
   kIdTokenInvalidResponse,
-  kIdTokenInvalidRequest,
+  kIdTokenInvalidRequest,                  // obsolete
   kClientMetadataMissingPrivacyPolicyUrl,  // obsolete
   kThirdPartyCookiesBlocked,
   kDisabledInSettings,
diff --git a/content/browser/webid/federated_auth_request_impl.cc b/content/browser/webid/federated_auth_request_impl.cc
index 9978e42..f083a85 100644
--- a/content/browser/webid/federated_auth_request_impl.cc
+++ b/content/browser/webid/federated_auth_request_impl.cc
@@ -35,6 +35,8 @@
 #include "url/url_constants.h"
 
 using blink::mojom::FederatedAuthRequestResult;
+using blink::mojom::IdentityProvider;
+using blink::mojom::IdentityProviderPtr;
 using blink::mojom::LogoutRpsStatus;
 using blink::mojom::RequestTokenStatus;
 using FederatedApiPermissionStatus =
@@ -158,9 +160,6 @@
     case FederatedAuthRequestResult::kErrorFetchingIdTokenInvalidResponse: {
       return "Provider's token is invalid.";
     }
-    case FederatedAuthRequestResult::kErrorFetchingIdTokenInvalidRequest: {
-      return "The id token fetching request is invalid.";
-    }
     case FederatedAuthRequestResult::kErrorCanceled: {
       return "The request has been aborted.";
     }
@@ -210,7 +209,6 @@
     case FederatedAuthRequestResult::kErrorFetchingIdTokenHttpNotFound:
     case FederatedAuthRequestResult::kErrorFetchingIdTokenNoResponse:
     case FederatedAuthRequestResult::kErrorFetchingIdTokenInvalidResponse:
-    case FederatedAuthRequestResult::kErrorFetchingIdTokenInvalidRequest:
     case FederatedAuthRequestResult::kError: {
       return RequestTokenStatus::kError;
     }
@@ -292,11 +290,10 @@
       sharing_permission_context, std::move(receiver));
 }
 
-void FederatedAuthRequestImpl::RequestToken(const GURL& provider,
-                                            const std::string& client_id,
-                                            const std::string& nonce,
-                                            bool prefer_auto_sign_in,
-                                            RequestTokenCallback callback) {
+void FederatedAuthRequestImpl::RequestToken(
+    IdentityProviderPtr identity_provider_ptr,
+    bool prefer_auto_sign_in,
+    RequestTokenCallback callback) {
   if (HasPendingRequest()) {
     fedcm_metrics_->RecordRequestTokenStatus(TokenStatus::kTooManyRequests);
     std::move(callback).Run(RequestTokenStatus::kErrorTooManyRequests, "");
@@ -304,20 +301,20 @@
   }
 
   auth_request_callback_ = std::move(callback);
-  provider_ = provider;
   // Generate a random int for the FedCM call, to be used by the UKM events.
   std::random_device dev;
   std::mt19937 rng(dev());
   std::uniform_int_distribution<std::mt19937::result_type> uniform_dist(
       1, 1 << 30);
+  // TODO(crbug.com/1307709): Handle FedCmMetrics for multiple IDPs.
   fedcm_metrics_ = std::make_unique<FedCmMetrics>(
-      provider_, render_frame_host().GetPageUkmSourceId(), uniform_dist(rng));
-  client_id_ = client_id;
-  nonce_ = nonce;
+      identity_provider_ptr->config_url,
+      render_frame_host().GetPageUkmSourceId(), uniform_dist(rng));
   prefer_auto_sign_in_ = prefer_auto_sign_in && IsFedCmAutoSigninEnabled();
   start_time_ = base::TimeTicks::Now();
 
-  network_manager_ = CreateNetworkManager(provider);
+  // TODO(crbug.com/1307709): Handle network managers for multiple IDPs.
+  network_manager_ = CreateNetworkManager(identity_provider_ptr->config_url);
   if (!network_manager_) {
     fedcm_metrics_->RecordRequestTokenStatus(TokenStatus::kNoNetworkManager);
     // TODO(yigu): this is due to provider url being non-secure. We should
@@ -365,7 +362,7 @@
 
   request_dialog_controller_ = CreateDialogController();
 
-  FetchManifest();
+  FetchManifest(std::move(identity_provider_ptr));
 }
 
 void FederatedAuthRequestImpl::CancelTokenRequest() {
@@ -442,19 +439,25 @@
   return auth_request_callback_ || logout_callback_;
 }
 
-GURL FederatedAuthRequestImpl::ResolveManifestUrl(const std::string& endpoint) {
+GURL FederatedAuthRequestImpl::ResolveManifestUrl(
+    const IdentityProvider& identity_provider,
+    const std::string& endpoint) {
   if (endpoint.empty())
     return GURL();
-  GURL manifest_url =
-      provider_.Resolve(IdpNetworkRequestManager::kManifestFilePath);
+  GURL manifest_url = identity_provider.config_url.Resolve(
+      IdpNetworkRequestManager::kManifestFilePath);
   return manifest_url.Resolve(endpoint);
 }
 
-bool FederatedAuthRequestImpl::IsEndpointUrlValid(const GURL& endpoint_url) {
-  return url::Origin::Create(provider_).IsSameOriginWith(endpoint_url);
+bool FederatedAuthRequestImpl::IsEndpointUrlValid(
+    const IdentityProvider& identity_provider,
+    const GURL& endpoint_url) {
+  return url::Origin::Create(identity_provider.config_url)
+      .IsSameOriginWith(endpoint_url);
 }
 
-void FederatedAuthRequestImpl::FetchManifest() {
+void FederatedAuthRequestImpl::FetchManifest(
+    IdentityProviderPtr identity_provider_ptr) {
   absl::optional<int> icon_ideal_size = absl::nullopt;
   absl::optional<int> icon_minimum_size = absl::nullopt;
   if (request_dialog_controller_) {
@@ -464,10 +467,10 @@
 
   IdpNetworkRequestManager::FetchManifestCallback manifest_callback =
       base::BindOnce(&FederatedAuthRequestImpl::OnManifestFetched,
-                     weak_ptr_factory_.GetWeakPtr());
+                     weak_ptr_factory_.GetWeakPtr(), *identity_provider_ptr);
   IdpNetworkRequestManager::FetchManifestListCallback manifest_list_callback =
       base::BindOnce(&FederatedAuthRequestImpl::OnManifestListFetched,
-                     weak_ptr_factory_.GetWeakPtr());
+                     weak_ptr_factory_.GetWeakPtr(), *identity_provider_ptr);
 
   if (IsFedCmManifestValidationEnabled()) {
     network_manager_->FetchManifestList(std::move(manifest_list_callback));
@@ -485,6 +488,7 @@
 }
 
 void FederatedAuthRequestImpl::OnManifestListFetched(
+    const IdentityProvider& identity_provider,
     IdpNetworkRequestManager::FetchStatus status,
     const std::set<GURL>& urls) {
   switch (status) {
@@ -514,10 +518,6 @@
           /*should_delay_callback=*/true);
       return;
     }
-    case IdpNetworkRequestManager::FetchStatus::kInvalidRequestError: {
-      NOTREACHED();
-      return;
-    }
     case IdpNetworkRequestManager::FetchStatus::kSuccess: {
       // Intentional fall-through.
     }
@@ -545,7 +545,7 @@
   //     "https://foo.idp.example/fedcm.json"
   //   ]
   // }
-  bool provider_url_is_valid = (urls.count(provider_) != 0);
+  bool provider_url_is_valid = (urls.count(identity_provider.config_url) != 0);
 
   if (!provider_url_is_valid) {
     fedcm_metrics_->RecordRequestTokenStatus(
@@ -557,10 +557,11 @@
 
   manifest_list_checked_ = true;
   if (idp_metadata_)
-    OnManifestReady(*idp_metadata_);
+    OnManifestReady(identity_provider, *idp_metadata_);
 }
 
 void FederatedAuthRequestImpl::OnManifestFetched(
+    const IdentityProvider& identity_provider,
     IdpNetworkRequestManager::FetchStatus status,
     IdpNetworkRequestManager::Endpoints endpoints,
     IdentityProviderMetadata idp_metadata) {
@@ -589,28 +590,28 @@
           /*should_delay_callback=*/true);
       return;
     }
-    case IdpNetworkRequestManager::FetchStatus::kInvalidRequestError: {
-      NOTREACHED();
-      return;
-    }
     case IdpNetworkRequestManager::FetchStatus::kSuccess: {
       // Intentional fall-through.
     }
   }
 
-  endpoints_.token = ResolveManifestUrl(endpoints.token);
-  endpoints_.accounts = ResolveManifestUrl(endpoints.accounts);
-  endpoints_.client_metadata = ResolveManifestUrl(endpoints.client_metadata);
+  endpoints_.token = ResolveManifestUrl(identity_provider, endpoints.token);
+  endpoints_.accounts =
+      ResolveManifestUrl(identity_provider, endpoints.accounts);
+  endpoints_.client_metadata =
+      ResolveManifestUrl(identity_provider, endpoints.client_metadata);
   idp_metadata_ = idp_metadata;
 
   if (manifest_list_checked_)
-    OnManifestReady(idp_metadata);
+    OnManifestReady(identity_provider, idp_metadata);
 }
 
 void FederatedAuthRequestImpl::OnManifestReady(
+    const IdentityProvider& identity_provider,
     IdentityProviderMetadata idp_metadata) {
-  bool is_token_valid = IsEndpointUrlValid(endpoints_.token);
-  bool is_accounts_valid = IsEndpointUrlValid(endpoints_.accounts);
+  bool is_token_valid = IsEndpointUrlValid(identity_provider, endpoints_.token);
+  bool is_accounts_valid =
+      IsEndpointUrlValid(identity_provider, endpoints_.accounts);
   if (!is_token_valid || !is_accounts_valid) {
     std::string message =
         "Manifest is missing or has an invalid URL for the following "
@@ -630,22 +631,24 @@
         /*should_delay_callback=*/true);
     return;
   }
-  if (IsEndpointUrlValid(endpoints_.client_metadata)) {
+  if (IsEndpointUrlValid(identity_provider, endpoints_.client_metadata)) {
     network_manager_->FetchClientMetadata(
-        endpoints_.client_metadata, client_id_,
+        endpoints_.client_metadata, identity_provider.client_id,
         base::BindOnce(
             &FederatedAuthRequestImpl::OnClientMetadataResponseReceived,
-            weak_ptr_factory_.GetWeakPtr(), std::move(idp_metadata)));
+            weak_ptr_factory_.GetWeakPtr(), identity_provider,
+            std::move(idp_metadata)));
   } else {
     network_manager_->SendAccountsRequest(
-        endpoints_.accounts, client_id_,
+        endpoints_.accounts, identity_provider.client_id,
         base::BindOnce(&FederatedAuthRequestImpl::OnAccountsResponseReceived,
-                       weak_ptr_factory_.GetWeakPtr(),
+                       weak_ptr_factory_.GetWeakPtr(), identity_provider,
                        std::move(idp_metadata)));
   }
 }
 
 void FederatedAuthRequestImpl::OnClientMetadataResponseReceived(
+    const IdentityProvider& identity_provider,
     IdentityProviderMetadata idp_metadata,
     IdpNetworkRequestManager::FetchStatus status,
     IdpNetworkRequestManager::ClientMetadata data) {
@@ -653,12 +656,14 @@
   // console logs.
   client_metadata_ = data;
   network_manager_->SendAccountsRequest(
-      endpoints_.accounts, client_id_,
+      endpoints_.accounts, identity_provider.client_id,
       base::BindOnce(&FederatedAuthRequestImpl::OnAccountsResponseReceived,
-                     weak_ptr_factory_.GetWeakPtr(), std::move(idp_metadata)));
+                     weak_ptr_factory_.GetWeakPtr(), identity_provider,
+                     std::move(idp_metadata)));
 }
 
 void FederatedAuthRequestImpl::OnAccountsResponseReceived(
+    const IdentityProvider& identity_provider,
     IdentityProviderMetadata idp_metadata,
     IdpNetworkRequestManager::FetchStatus status,
     IdpNetworkRequestManager::AccountList accounts) {
@@ -705,7 +710,7 @@
       WebContents* rp_web_contents =
           WebContents::FromRenderFrameHost(&render_frame_host());
 
-      ComputeLoginStateAndReorderAccounts(accounts);
+      ComputeLoginStateAndReorderAccounts(identity_provider, accounts);
 
       bool screen_reader_is_on =
           rp_web_contents->GetAccessibilityMode().has_mode(
@@ -726,21 +731,19 @@
                                                    start_time_);
 
       request_dialog_controller_->ShowAccountsDialog(
-          rp_web_contents, provider_, accounts, idp_metadata, data,
-          is_auto_sign_in ? SignInMode::kAuto : SignInMode::kExplicit,
+          rp_web_contents, identity_provider.config_url, accounts, idp_metadata,
+          data, is_auto_sign_in ? SignInMode::kAuto : SignInMode::kExplicit,
           base::BindOnce(&FederatedAuthRequestImpl::OnAccountSelected,
-                         weak_ptr_factory_.GetWeakPtr()),
+                         weak_ptr_factory_.GetWeakPtr(), identity_provider),
           base::BindOnce(&FederatedAuthRequestImpl::OnDialogDismissed,
                          weak_ptr_factory_.GetWeakPtr()));
       return;
     }
-    case IdpNetworkRequestManager::FetchStatus::kInvalidRequestError: {
-      NOTREACHED();
-    }
   }
 }
 
 void FederatedAuthRequestImpl::ComputeLoginStateAndReorderAccounts(
+    const IdentityProvider& identity_provider,
     IdpNetworkRequestManager::AccountList& accounts) {
   // Populate the accounts login state.
   for (auto& account : accounts) {
@@ -748,7 +751,8 @@
     bool idp_claimed_sign_in = account.login_state == LoginState::kSignIn;
     bool browser_observed_sign_in =
         sharing_permission_delegate_->HasSharingPermission(
-            origin(), url::Origin::Create(provider_), account.id);
+            origin(), url::Origin::Create(identity_provider.config_url),
+            account.id);
 
     if (idp_claimed_sign_in == browser_observed_sign_in) {
       fedcm_metrics_->RecordSignInStateMatchStatus(
@@ -784,8 +788,10 @@
   });
 }
 
-void FederatedAuthRequestImpl::OnAccountSelected(const std::string& account_id,
-                                                 bool is_sign_in) {
+void FederatedAuthRequestImpl::OnAccountSelected(
+    const IdentityProvider& identity_provider,
+    const std::string& account_id,
+    bool is_sign_in) {
   DCHECK(!account_id.empty());
 
   // Check if the user has disabled the FedCM API after the FedCM UI is
@@ -813,10 +819,11 @@
 
   network_manager_->SendTokenRequest(
       endpoints_.token, account_id_,
-      ComputeUrlEncodedTokenPostData(client_id_, nonce_, account_id,
+      ComputeUrlEncodedTokenPostData(identity_provider.client_id,
+                                     identity_provider.nonce, account_id,
                                      is_sign_in),
       base::BindOnce(&FederatedAuthRequestImpl::OnTokenResponseReceived,
-                     weak_ptr_factory_.GetWeakPtr()));
+                     weak_ptr_factory_.GetWeakPtr(), identity_provider));
 }
 
 void FederatedAuthRequestImpl::OnDialogDismissed(
@@ -854,6 +861,7 @@
 }
 
 void FederatedAuthRequestImpl::OnTokenResponseReceived(
+    const IdentityProvider& identity_provider,
     IdpNetworkRequestManager::FetchStatus status,
     const std::string& id_token) {
   if (!auth_request_callback_)
@@ -867,18 +875,20 @@
   base::TimeDelta fetch_time = token_response_time_ - select_account_time_;
   if (ShouldCompleteRequestImmediately() ||
       fetch_time >= token_request_delay_) {
-    CompleteTokenRequest(status, id_token);
+    CompleteTokenRequest(identity_provider, status, id_token);
     return;
   }
 
   base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
       FROM_HERE,
       base::BindOnce(&FederatedAuthRequestImpl::CompleteTokenRequest,
-                     weak_ptr_factory_.GetWeakPtr(), status, id_token),
+                     weak_ptr_factory_.GetWeakPtr(), identity_provider, status,
+                     id_token),
       token_request_delay_ - fetch_time);
 }
 
 void FederatedAuthRequestImpl::CompleteTokenRequest(
+    const IdentityProvider& identity_provider,
     IdpNetworkRequestManager::FetchStatus status,
     const std::string& token) {
   DCHECK(!start_time_.is_null());
@@ -898,14 +908,6 @@
           /*should_delay_callback=*/true);
       return;
     }
-    case IdpNetworkRequestManager::FetchStatus::kInvalidRequestError: {
-      fedcm_metrics_->RecordRequestTokenStatus(
-          TokenStatus::kIdTokenInvalidRequest);
-      CompleteRequest(
-          FederatedAuthRequestResult::kErrorFetchingIdTokenInvalidRequest, "",
-          /*should_delay_callback=*/true);
-      return;
-    }
     case IdpNetworkRequestManager::FetchStatus::kInvalidResponseError: {
       fedcm_metrics_->RecordRequestTokenStatus(
           TokenStatus::kIdTokenInvalidResponse);
@@ -929,10 +931,12 @@
       // https://crbug.com/1199088
       CHECK(!account_id_.empty());
       sharing_permission_delegate_->GrantSharingPermission(
-          origin(), url::Origin::Create(provider_), account_id_);
+          origin(), url::Origin::Create(identity_provider.config_url),
+          account_id_);
 
       active_session_permission_delegate_->GrantActiveSession(
-          origin(), url::Origin::Create(provider_), account_id_);
+          origin(), url::Origin::Create(identity_provider.config_url),
+          account_id_);
 
       fedcm_metrics_->RecordTokenResponseAndTurnaroundTime(
           token_response_time_ - select_account_time_,
diff --git a/content/browser/webid/federated_auth_request_impl.h b/content/browser/webid/federated_auth_request_impl.h
index 97b2f9c1..ea80961 100644
--- a/content/browser/webid/federated_auth_request_impl.h
+++ b/content/browser/webid/federated_auth_request_impl.h
@@ -56,9 +56,7 @@
   ~FederatedAuthRequestImpl() override;
 
   // blink::mojom::FederatedAuthRequest:
-  void RequestToken(const GURL& provider,
-                    const std::string& client_id,
-                    const std::string& nonce,
+  void RequestToken(blink::mojom::IdentityProviderPtr identity_provider_ptr,
                     bool prefer_auto_sign_in,
                     RequestTokenCallback) override;
   void CancelTokenRequest() override;
@@ -85,34 +83,52 @@
       mojo::PendingReceiver<blink::mojom::FederatedAuthRequest>);
 
   bool HasPendingRequest() const;
-  GURL ResolveManifestUrl(const std::string& url);
+  GURL ResolveManifestUrl(
+      const blink::mojom::IdentityProvider& identity_provider,
+      const std::string& url);
 
   // Checks validity of the passed-in endpoint URL origin.
-  bool IsEndpointUrlValid(const GURL& endpoint_url);
+  bool IsEndpointUrlValid(
+      const blink::mojom::IdentityProvider& identity_provider,
+      const GURL& endpoint_url);
 
-  void FetchManifest();
-  void OnManifestListFetched(IdpNetworkRequestManager::FetchStatus status,
-                             const std::set<GURL>& urls);
-  void OnManifestFetched(IdpNetworkRequestManager::FetchStatus status,
-                         IdpNetworkRequestManager::Endpoints,
-                         IdentityProviderMetadata idp_metadata);
-  void OnManifestReady(IdentityProviderMetadata idp_metadata);
+  void FetchManifest(blink::mojom::IdentityProviderPtr identity_provider_ptr);
+  void OnManifestListFetched(
+      const blink::mojom::IdentityProvider& identity_provider,
+      IdpNetworkRequestManager::FetchStatus status,
+      const std::set<GURL>& urls);
+  void OnManifestFetched(
+      const blink::mojom::IdentityProvider& identity_provider,
+      IdpNetworkRequestManager::FetchStatus status,
+      IdpNetworkRequestManager::Endpoints,
+      IdentityProviderMetadata idp_metadata);
+  void OnManifestReady(const blink::mojom::IdentityProvider& identity_provider,
+                       IdentityProviderMetadata idp_metadata);
   void OnClientMetadataResponseReceived(
+      const blink::mojom::IdentityProvider& identity_provider,
       IdentityProviderMetadata idp_metadata,
       IdpNetworkRequestManager::FetchStatus status,
       IdpNetworkRequestManager::ClientMetadata data);
 
   void OnAccountsResponseReceived(
+      const blink::mojom::IdentityProvider& identity_provider,
       IdentityProviderMetadata idp_metadata,
       IdpNetworkRequestManager::FetchStatus status,
       IdpNetworkRequestManager::AccountList accounts);
-  void OnAccountSelected(const std::string& account_id, bool is_sign_in);
+  void OnAccountSelected(
+      const blink::mojom::IdentityProvider& identity_provider,
+      const std::string& account_id,
+      bool is_sign_in);
   void OnDialogDismissed(
       IdentityRequestDialogController::DismissReason dismiss_reason);
-  void CompleteTokenRequest(IdpNetworkRequestManager::FetchStatus status,
-                            const std::string& token);
-  void OnTokenResponseReceived(IdpNetworkRequestManager::FetchStatus status,
-                               const std::string& token);
+  void CompleteTokenRequest(
+      const blink::mojom::IdentityProvider& identity_provider,
+      IdpNetworkRequestManager::FetchStatus status,
+      const std::string& token);
+  void OnTokenResponseReceived(
+      const blink::mojom::IdentityProvider& identity_provider,
+      IdpNetworkRequestManager::FetchStatus status,
+      const std::string& token);
   void DispatchOneLogout();
   void OnLogoutCompleted();
   void CompleteRequest(blink::mojom::FederatedAuthRequestResult,
@@ -146,6 +162,7 @@
   // reorders accounts so that those that are considered returning users are
   // before users that are not returning.
   void ComputeLoginStateAndReorderAccounts(
+      const blink::mojom::IdentityProvider& identity_provider,
       IdpNetworkRequestManager::AccountList& accounts);
 
   std::unique_ptr<IdpNetworkRequestManager> network_manager_;
@@ -159,19 +176,6 @@
   // RequestToken() method, so all metrics must be recorded after that.
   std::unique_ptr<FedCmMetrics> fedcm_metrics_;
 
-  // Parameters of auth request.
-  GURL provider_;
-
-  // The federated auth request parameters provided by RP. Note that these
-  // parameters will uniquely identify the users so they should only be passed
-  // to IDP after user permission has been granted.
-  //
-  // TODO(majidvp): Implement a mechanism (e.g., a getter) that checks the
-  // request permission is granted before providing access to this parameter
-  // this way we avoid accidentally sharing these values.
-  std::string client_id_;
-  std::string nonce_;
-
   bool prefer_auto_sign_in_;
 
   // Fetched from the IDP FedCM manifest configuration.
diff --git a/content/browser/webid/federated_auth_request_impl_unittest.cc b/content/browser/webid/federated_auth_request_impl_unittest.cc
index f965526..f1d892d 100644
--- a/content/browser/webid/federated_auth_request_impl_unittest.cc
+++ b/content/browser/webid/federated_auth_request_impl_unittest.cc
@@ -686,7 +686,10 @@
                      const std::string& nonce,
                      bool prefer_auto_sign_in,
                      bool wait_for_callback) {
-    request_remote_->RequestToken(provider, client_id, nonce,
+    blink::mojom::IdentityProviderPtr identity_provider =
+        blink::mojom::IdentityProvider::New(provider, client_id, nonce);
+
+    request_remote_->RequestToken(std::move(identity_provider),
                                   prefer_auto_sign_in, auth_helper_.callback());
 
     if (wait_for_callback)
@@ -868,8 +871,10 @@
   }
 
   void ComputeLoginStateAndReorderAccounts(
+      const blink::mojom::IdentityProvider& identity_provider,
       IdpNetworkRequestManager::AccountList& accounts) {
-    federated_auth_request_impl_->ComputeLoginStateAndReorderAccounts(accounts);
+    federated_auth_request_impl_->ComputeLoginStateAndReorderAccounts(
+        identity_provider, accounts);
   }
 
  protected:
@@ -2018,7 +2023,10 @@
               kConfigurationValid);
 
   AccountList multiple_accounts = kMultipleAccounts;
-  ComputeLoginStateAndReorderAccounts(multiple_accounts);
+  blink::mojom::IdentityProviderPtr identity_provider =
+      blink::mojom::IdentityProvider::New(GURL(kProviderUrlFull), kClientId,
+                                          kNonce);
+  ComputeLoginStateAndReorderAccounts(*identity_provider, multiple_accounts);
 
   // Check the account order using the account ids.
   ASSERT_EQ(multiple_accounts.size(), 3u);
diff --git a/content/browser/webid/idp_network_request_manager.cc b/content/browser/webid/idp_network_request_manager.cc
index 18c82a41..0841ba4 100644
--- a/content/browser/webid/idp_network_request_manager.cc
+++ b/content/browser/webid/idp_network_request_manager.cc
@@ -631,11 +631,6 @@
     const std::string& account,
     const std::string& url_encoded_post_data,
     TokenRequestCallback callback) {
-  if (url_encoded_post_data.empty()) {
-    std::move(callback).Run(FetchStatus::kInvalidRequestError, std::string());
-    return;
-  }
-
   std::unique_ptr<network::SimpleURLLoader> url_loader =
       CreateCredentialedUrlLoader(token_url,
                                   /* send_referrer= */ true,
diff --git a/content/browser/webid/idp_network_request_manager.h b/content/browser/webid/idp_network_request_manager.h
index 3b61f29..ab50ba8 100644
--- a/content/browser/webid/idp_network_request_manager.h
+++ b/content/browser/webid/idp_network_request_manager.h
@@ -67,7 +67,6 @@
     kHttpNotFoundError,
     kNoResponseError,
     kInvalidResponseError,
-    kInvalidRequestError,
   };
 
   enum class LogoutResponse {
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc
index af20f26..ea54b3c 100644
--- a/content/child/runtime_features.cc
+++ b/content/child/runtime_features.cc
@@ -219,6 +219,8 @@
     {wf::EnableDocumentPolicy, features::kDocumentPolicy},
     {wf::EnableDocumentPolicyNegotiation, features::kDocumentPolicyNegotiation},
     {wf::EnableFedCm, features::kFedCm, kSetOnlyIfOverridden},
+    {wf::EnableFedCmMultipleIdentityProviders,
+     features::kFedCmMultipleIdentityProviders, kDefault},
     {wf::EnableFencedFrames, features::kPrivacySandboxAdsAPIsOverride,
      kSetOnlyIfOverridden},
     {wf::EnableSharedStorageAPI, features::kPrivacySandboxAdsAPIsOverride,
diff --git a/content/public/android/BUILD.gn b/content/public/android/BUILD.gn
index 40b7166..b2f7ce1 100644
--- a/content/public/android/BUILD.gn
+++ b/content/public/android/BUILD.gn
@@ -147,6 +147,7 @@
     "//build/android:build_java",
     "//components/download/public/common:public_java",
     "//components/payments/mojom:mojom_java",
+    "//content/public/common:common_java",
     "//content/public/common:trust_tokens_mojo_bindings_java",
     "//device/bluetooth:java",
     "//device/gamepad:java",
diff --git a/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncherHelperImpl.java b/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncherHelperImpl.java
index cb531379..1748f358 100644
--- a/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncherHelperImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/ChildProcessLauncherHelperImpl.java
@@ -44,6 +44,7 @@
 import org.chromium.content_public.browser.ChildProcessImportance;
 import org.chromium.content_public.browser.ContentFeatureList;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
+import org.chromium.content_public.common.ContentFeatures;
 import org.chromium.content_public.common.ContentSwitches;
 
 import java.io.IOException;
@@ -78,6 +79,12 @@
     // To be conservative, only delay removing binding in the initial second of the process.
     private static final int TIMEOUT_FOR_DELAY_BINDING_REMOVE_MS = 1000;
 
+    // Delay after app is background before reducing process priority.
+    private static final int REDUCE_PRIORITY_ON_BACKGROUND_DELAY_MS = 9 * 1000;
+
+    private static final Runnable sDelayedBackgroundTask =
+            ChildProcessLauncherHelperImpl::onSentToBackgroundOnLauncherThreadAfterDelay;
+
     // Flag to check if ServiceGroupImportance should be enabled after native is initialized.
     private static boolean sCheckedServiceGroupImportance;
 
@@ -102,12 +109,16 @@
     private static ChildConnectionAllocator.ConnectionFactory sSandboxedServiceFactoryForTesting;
     private static int sSandboxedServicesCountForTesting = -1;
     private static String sSandboxedServicesNameForTesting;
+    private static boolean sSkipDelayForReducePriorityOnBackgroundForTesting;
 
     private static BindingManager sBindingManager;
 
     // Whether the main application is currently brought to the foreground.
     private static boolean sApplicationInForegroundOnUiThread;
 
+    // Set on UI thread only, but null-checked on launcher thread as well.
+    private static ApplicationStatus.ApplicationStateListener sAppStateListener;
+
     // TODO(boliu): These are only set for sandboxed renderer processes. Generalize them for
     // all types of processes.
     private final ChildProcessRanking mRanking;
@@ -116,6 +127,9 @@
     // Whether the created process should be sandboxed.
     private final boolean mSandboxed;
 
+    // Remove strong binding when app is in background.
+    private final boolean mReducePriorityOnBackground;
+
     // The type of process as determined by the command line.
     private final String mProcessType;
 
@@ -188,6 +202,10 @@
                         if (mSandboxed) {
                             ChildProcessConnectionMetrics.getInstance().addConnection(connection);
                         }
+                        if (mReducePriorityOnBackground
+                                && !ApplicationStatus.hasVisibleActivities()) {
+                            reducePriorityOnBackgroundOnLauncherThread();
+                        }
                     }
 
                     // Tell native launch result (whether getPid is 0).
@@ -342,6 +360,8 @@
     private @ChildProcessImportance int mEffectiveImportance = ChildProcessImportance.MODERATE;
     private boolean mVisible;
 
+    private boolean mDroppedStrongBingingDueToBackgrounding;
+
     // Protects fields below that are accessed on client thread as well.
     private final Object mLock = new Object();
     private int mReverseRankWhenConnectionLost;
@@ -378,9 +398,12 @@
             commandLine[commandLine.length - 1] = TRACE_EARLY_JAVA_IN_CHILD_SWITCH;
         }
         boolean sandboxed = true;
+        boolean reducePriorityOnBackground = false;
         if (!ContentSwitches.SWITCH_RENDERER_PROCESS.equals(processType)) {
             if (ContentSwitches.SWITCH_GPU_PROCESS.equals(processType)) {
                 sandboxed = false;
+                reducePriorityOnBackground = ContentFeatureList.isEnabled(
+                        ContentFeatures.REDUCE_GPU_PRIORITY_ON_BACKGROUND);
             } else {
                 // We only support sandboxed utility processes now.
                 assert ContentSwitches.SWITCH_UTILITY_PROCESS.equals(processType);
@@ -397,7 +420,8 @@
                 : null;
 
         ChildProcessLauncherHelperImpl helper = new ChildProcessLauncherHelperImpl(nativePointer,
-                commandLine, filesToBeMapped, sandboxed, canUseWarmUpConnection, binderCallback);
+                commandLine, filesToBeMapped, sandboxed, reducePriorityOnBackground,
+                canUseWarmUpConnection, binderCallback);
         helper.start();
 
         if (sandboxed && !sCheckedServiceGroupImportance) {
@@ -459,39 +483,57 @@
                 ChildProcessConnectionMetrics.getInstance().setBindingManager(sBindingManager);
             }
         });
-
-        sApplicationInForegroundOnUiThread = ApplicationStatus.getStateForApplication()
-                        == ApplicationState.HAS_RUNNING_ACTIVITIES
-                || ApplicationStatus.getStateForApplication()
-                        == ApplicationState.HAS_PAUSED_ACTIVITIES;
-
-        ApplicationStatus.registerApplicationStateListener(newState -> {
-            switch (newState) {
-                case ApplicationState.UNKNOWN:
-                    break;
-                case ApplicationState.HAS_RUNNING_ACTIVITIES:
-                case ApplicationState.HAS_PAUSED_ACTIVITIES:
-                    if (!sApplicationInForegroundOnUiThread) onBroughtToForeground();
-                    break;
-                default:
-                    if (sApplicationInForegroundOnUiThread) onSentToBackground();
-                    break;
-            }
-        });
     }
 
     private static void onSentToBackground() {
         assert ThreadUtils.runningOnUiThread();
         sApplicationInForegroundOnUiThread = false;
+        int delay = sSkipDelayForReducePriorityOnBackgroundForTesting
+                ? 0
+                : REDUCE_PRIORITY_ON_BACKGROUND_DELAY_MS;
+        LauncherThread.postDelayed(sDelayedBackgroundTask, delay);
         LauncherThread.post(() -> {
             if (sBindingManager != null) sBindingManager.onSentToBackground();
         });
     }
 
+    private static void onSentToBackgroundOnLauncherThreadAfterDelay() {
+        assert LauncherThread.runningOnLauncherThread();
+        for (ChildProcessLauncherHelperImpl helper : sLauncherByPid.values()) {
+            if (!helper.mReducePriorityOnBackground) continue;
+            helper.reducePriorityOnBackgroundOnLauncherThread();
+        }
+    }
+
+    private void reducePriorityOnBackgroundOnLauncherThread() {
+        assert LauncherThread.runningOnLauncherThread();
+        if (mDroppedStrongBingingDueToBackgrounding) return;
+        ChildProcessConnection connection = mLauncher.getConnection();
+        if (!connection.isConnected()) return;
+        if (connection.isStrongBindingBound()) {
+            connection.removeStrongBinding();
+            mDroppedStrongBingingDueToBackgrounding = true;
+        }
+    }
+
+    private void raisePriorityOnForegroundOnLauncherThread() {
+        assert LauncherThread.runningOnLauncherThread();
+        if (!mDroppedStrongBingingDueToBackgrounding) return;
+        ChildProcessConnection connection = mLauncher.getConnection();
+        if (!connection.isConnected()) return;
+        connection.addStrongBinding();
+        mDroppedStrongBingingDueToBackgrounding = false;
+    }
+
     private static void onBroughtToForeground() {
         assert ThreadUtils.runningOnUiThread();
         sApplicationInForegroundOnUiThread = true;
+        LauncherThread.removeCallbacks(sDelayedBackgroundTask);
         LauncherThread.post(() -> {
+            for (ChildProcessLauncherHelperImpl helper : sLauncherByPid.values()) {
+                if (!helper.mReducePriorityOnBackground) continue;
+                helper.raisePriorityOnForegroundOnLauncherThread();
+            }
             if (sBindingManager != null) sBindingManager.onBroughtToForeground();
         });
     }
@@ -506,6 +548,11 @@
     }
 
     @VisibleForTesting
+    public static void setSkipDelayForReducePriorityOnBackgroundForTesting() {
+        sSkipDelayForReducePriorityOnBackgroundForTesting = true;
+    }
+
+    @VisibleForTesting
     static ChildConnectionAllocator getConnectionAllocator(Context context, boolean sandboxed) {
         assert LauncherThread.runningOnLauncherThread();
         boolean bindToCaller = ChildProcessCreationParamsImpl.getBindToCallerCheck();
@@ -577,12 +624,14 @@
     }
 
     private ChildProcessLauncherHelperImpl(long nativePointer, String[] commandLine,
-            FileDescriptorInfo[] filesToBeMapped, boolean sandboxed, boolean canUseWarmUpConnection,
+            FileDescriptorInfo[] filesToBeMapped, boolean sandboxed,
+            boolean reducePriorityOnBackground, boolean canUseWarmUpConnection,
             IBinder binderCallback) {
         assert LauncherThread.runningOnLauncherThread();
 
         mNativeChildProcessLauncherHelper = nativePointer;
         mSandboxed = sandboxed;
+        mReducePriorityOnBackground = reducePriorityOnBackground;
         mCanUseWarmUpConnection = canUseWarmUpConnection;
         ChildConnectionAllocator connectionAllocator =
                 getConnectionAllocator(ContextUtils.getApplicationContext(), sandboxed);
@@ -602,6 +651,27 @@
             // -2 means not applicable.
             mReverseRankWhenConnectionLost = -2;
         }
+
+        if (!ApplicationStatus.isInitialized()) return;
+        if (sAppStateListener != null) return;
+        PostTask.postTask(UiThreadTaskTraits.BEST_EFFORT, () -> {
+            if (sAppStateListener != null) return;
+            sApplicationInForegroundOnUiThread = ApplicationStatus.hasVisibleActivities();
+            sAppStateListener = newState -> {
+                switch (newState) {
+                    case ApplicationState.UNKNOWN:
+                        break;
+                    case ApplicationState.HAS_RUNNING_ACTIVITIES:
+                    case ApplicationState.HAS_PAUSED_ACTIVITIES:
+                        if (!sApplicationInForegroundOnUiThread) onBroughtToForeground();
+                        break;
+                    default:
+                        if (sApplicationInForegroundOnUiThread) onSentToBackground();
+                        break;
+                }
+            };
+            ApplicationStatus.registerApplicationStateListener(sAppStateListener);
+        });
     }
 
     private void start() {
@@ -822,10 +892,12 @@
 
     @VisibleForTesting
     public static ChildProcessLauncherHelperImpl createAndStartForTesting(String[] commandLine,
-            FileDescriptorInfo[] filesToBeMapped, boolean sandboxed, boolean canUseWarmUpConnection,
+            FileDescriptorInfo[] filesToBeMapped, boolean sandboxed,
+            boolean reducePriorityOnBackground, boolean canUseWarmUpConnection,
             IBinder binderCallback, boolean doSetupConnection) {
-        ChildProcessLauncherHelperImpl launcherHelper = new ChildProcessLauncherHelperImpl(0L,
-                commandLine, filesToBeMapped, sandboxed, canUseWarmUpConnection, binderCallback);
+        ChildProcessLauncherHelperImpl launcherHelper =
+                new ChildProcessLauncherHelperImpl(0L, commandLine, filesToBeMapped, sandboxed,
+                        reducePriorityOnBackground, canUseWarmUpConnection, binderCallback);
         launcherHelper.mLauncher.start(doSetupConnection, true /* queueIfNoFreeConnection */);
         return launcherHelper;
     }
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/ChildProcessLauncherHelperTest.java b/content/public/android/javatests/src/org/chromium/content/browser/ChildProcessLauncherHelperTest.java
index df482a63..cc94122 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/ChildProcessLauncherHelperTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/ChildProcessLauncherHelperTest.java
@@ -21,9 +21,12 @@
 import org.hamcrest.Matchers;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.chromium.base.ActivityState;
+import org.chromium.base.ApplicationStatus;
 import org.chromium.base.BaseSwitches;
 import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.base.library_loader.LibraryProcessType;
@@ -39,6 +42,8 @@
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.content_shell_apk.ChildProcessLauncherTestHelperService;
 import org.chromium.content_shell_apk.ChildProcessLauncherTestUtils;
+import org.chromium.content_shell_apk.ContentShellActivity;
+import org.chromium.content_shell_apk.ContentShellActivityTestRule;
 
 import java.util.concurrent.Callable;
 
@@ -59,6 +64,9 @@
     private static final int BLOCK_UNTIL_CONNECTED = 1;
     private static final int BLOCK_UNTIL_SETUP = 2;
 
+    @Rule
+    public ContentShellActivityTestRule mActivityTestRule = new ContentShellActivityTestRule();
+
     @Before
     public void setUp() {
         LibraryLoader.getInstance().ensureInitialized();
@@ -321,9 +329,9 @@
         Assert.assertEquals(1, getConnectedServicesCount());
 
         // And subsequent process launches should work.
-        ChildProcessLauncherHelperImpl launcher =
-                startChildProcess(BLOCK_UNTIL_SETUP, true /* doSetupConnection */,
-                        false /* sandboxed */, true /* canUseWarmUpConnection */);
+        ChildProcessLauncherHelperImpl launcher = startChildProcess(BLOCK_UNTIL_SETUP,
+                true /* doSetupConnection */, false /* sandboxed */,
+                false /* reducePriorityOnBackground */, true /* canUseWarmUpConnection */);
         Assert.assertEquals(1, getConnectedServicesCount());
         Assert.assertEquals(0, getConnectedSandboxedServicesCount());
         Assert.assertNotNull(ChildProcessLauncherTestUtils.getConnection(launcher));
@@ -352,14 +360,65 @@
         waitForConnectedSandboxedServicesCount(0);
     }
 
+    @Test
+    @MediumTest
+    @Feature({"ProcessManagement"})
+    public void testReducePriorityOnBackground() {
+        ChildProcessLauncherHelperImpl.setSkipDelayForReducePriorityOnBackgroundForTesting();
+
+        final ContentShellActivity activity =
+                mActivityTestRule.launchContentShellWithUrl("about:blank");
+        mActivityTestRule.waitForActiveShellToBeDoneLoading();
+        Assert.assertTrue(ApplicationStatus.hasVisibleActivities());
+
+        ChildProcessLauncherHelperImpl launcher = startChildProcess(BLOCK_UNTIL_SETUP,
+                true /* doSetupConnection */, false /* sandboxed */,
+                true /* reducePriorityOnBackground */, true /* canUseWarmUpConnection */);
+        final ChildProcessConnection connection =
+                ChildProcessLauncherTestUtils.getConnection(launcher);
+
+        Assert.assertTrue(ChildProcessLauncherTestUtils.runOnLauncherAndGetResult(
+                () -> connection.isStrongBindingBound()));
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> ApplicationStatus.onStateChangeForTesting(activity, ActivityState.STOPPED));
+        Assert.assertFalse(ApplicationStatus.hasVisibleActivities());
+        Assert.assertFalse(ChildProcessLauncherTestUtils.runOnLauncherAndGetResult(
+                () -> connection.isStrongBindingBound()));
+    }
+
+    @Test
+    @MediumTest
+    @Feature({"ProcessManagement"})
+    public void testLaunchWithReducedPriorityOnBackground() {
+        ChildProcessLauncherHelperImpl.setSkipDelayForReducePriorityOnBackgroundForTesting();
+
+        final ContentShellActivity activity =
+                mActivityTestRule.launchContentShellWithUrl("about:blank");
+        mActivityTestRule.waitForActiveShellToBeDoneLoading();
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> ApplicationStatus.onStateChangeForTesting(activity, ActivityState.STOPPED));
+        Assert.assertFalse(ApplicationStatus.hasVisibleActivities());
+
+        ChildProcessLauncherHelperImpl launcher = startChildProcess(BLOCK_UNTIL_SETUP,
+                true /* doSetupConnection */, false /* sandboxed */,
+                true /* reducePriorityOnBackground */, true /* canUseWarmUpConnection */);
+        final ChildProcessConnection connection =
+                ChildProcessLauncherTestUtils.getConnection(launcher);
+
+        Assert.assertFalse(ChildProcessLauncherTestUtils.runOnLauncherAndGetResult(
+                () -> connection.isStrongBindingBound()));
+    }
+
     private static ChildProcessLauncherHelperImpl startSandboxedChildProcess(
             int blockingPolicy, final boolean doSetupConnection) {
         return startChildProcess(blockingPolicy, doSetupConnection, true /* sandboxed */,
-                true /* canUseWarmUpConnection */);
+                false /* reducePriorityOnBackground */, true /* canUseWarmUpConnection */);
     }
 
     private static ChildProcessLauncherHelperImpl startChildProcess(int blockingPolicy,
-            final boolean doSetupConnection, boolean sandboxed, boolean canUseWarmUpConnection) {
+            final boolean doSetupConnection, boolean sandboxed, boolean reducePriorityOnBackground,
+            boolean canUseWarmUpConnection) {
         assert doSetupConnection || blockingPolicy != BLOCK_UNTIL_SETUP;
         ChildProcessLauncherHelperImpl launcher =
                 ChildProcessLauncherTestUtils.runOnLauncherAndGetResult(
@@ -368,8 +427,8 @@
                             public ChildProcessLauncherHelperImpl call() {
                                 return ChildProcessLauncherHelperImpl.createAndStartForTesting(
                                         sProcessWaitArguments, new FileDescriptorInfo[0], sandboxed,
-                                        canUseWarmUpConnection, null /* binderCallback */,
-                                        doSetupConnection);
+                                        reducePriorityOnBackground, canUseWarmUpConnection,
+                                        null /* binderCallback */, doSetupConnection);
                             }
                         });
         if (blockingPolicy != DONT_BLOCK) {
diff --git a/content/public/browser/BUILD.gn b/content/public/browser/BUILD.gn
index ea154d8f..a16cd52 100644
--- a/content/public/browser/BUILD.gn
+++ b/content/public/browser/BUILD.gn
@@ -283,6 +283,8 @@
     "permission_controller.h",
     "permission_controller_delegate.cc",
     "permission_controller_delegate.h",
+    "permission_result.cc",
+    "permission_result.h",
     "picture_in_picture_window_controller.h",
     "platform_notification_context.h",
     "platform_notification_service.h",
@@ -424,6 +426,8 @@
     "web_ui_controller.cc",
     "web_ui_controller.h",
     "web_ui_controller_factory.h",
+    "web_ui_controller_interface_binder.cc",
+    "web_ui_controller_interface_binder.h",
     "web_ui_data_source.h",
     "web_ui_message_handler.h",
     "web_ui_url_loader_factory.h",
diff --git a/content/public/browser/permission_controller.h b/content/public/browser/permission_controller.h
index 87b8785..f147bc4 100644
--- a/content/public/browser/permission_controller.h
+++ b/content/public/browser/permission_controller.h
@@ -8,6 +8,7 @@
 #include "base/supports_user_data.h"
 #include "base/types/id_type.h"
 #include "content/common/content_export.h"
+#include "content/public/browser/permission_result.h"
 #include "third_party/blink/public/mojom/permissions/permission_status.mojom.h"
 
 class GURL;
@@ -53,11 +54,26 @@
       blink::PermissionType permission,
       RenderFrameHost* render_frame_host) = 0;
 
+  // The method does the same as `GetPermissionStatusForCurrentDocument` but
+  // additionally returns a source or reason for the permission status.
+  virtual PermissionResult GetPermissionResultForCurrentDocument(
+      blink::PermissionType permission,
+      RenderFrameHost* render_frame_host) = 0;
+
   // Returns the permission status for a given origin. Use this API only if
   // there is no document and it is not a ServiceWorker.
+  virtual PermissionResult GetPermissionResultForOriginWithoutContext(
+      blink::PermissionType permission,
+      const url::Origin& origin) = 0;
+
+  // The method does the same as `GetPermissionResultForOriginWithoutContext`
+  // but it can be used for `PermissionType` that are keyed on a combination of
+  // requesting and embedding origins, e.g., Notifications.
   virtual blink::mojom::PermissionStatus
-  GetPermissionStatusForOriginWithoutContext(blink::PermissionType permission,
-                                             const url::Origin& origin) = 0;
+  GetPermissionStatusForOriginWithoutContext(
+      blink::PermissionType permission,
+      const url::Origin& requesting_origin,
+      const url::Origin& embedding_origin) = 0;
 
   // Requests the permission from the current document in the given
   // RenderFrameHost. This API takes into account the lifecycle state of a given
diff --git a/content/public/browser/permission_controller_delegate.cc b/content/public/browser/permission_controller_delegate.cc
index 0cd97b19..2a0c82e 100644
--- a/content/public/browser/permission_controller_delegate.cc
+++ b/content/public/browser/permission_controller_delegate.cc
@@ -3,6 +3,8 @@
 // found in the LICENSE file.
 
 #include "content/public/browser/permission_controller_delegate.h"
+#include "content/public/browser/permission_result.h"
+#include "content/public/browser/render_frame_host.h"
 
 namespace content {
 
@@ -12,4 +14,12 @@
   return true;
 }
 
+PermissionResult
+PermissionControllerDelegate::GetPermissionResultForCurrentDocument(
+    blink::PermissionType permission,
+    RenderFrameHost* render_frame_host) {
+  return PermissionResult(blink::mojom::PermissionStatus::DENIED,
+                          PermissionStatusSource::UNSPECIFIED);
+}
+
 }  // namespace content
diff --git a/content/public/browser/permission_controller_delegate.h b/content/public/browser/permission_controller_delegate.h
index e91b4557..cf92ec9 100644
--- a/content/public/browser/permission_controller_delegate.h
+++ b/content/public/browser/permission_controller_delegate.h
@@ -8,6 +8,7 @@
 #include "base/types/id_type.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/devtools_permission_overrides.h"
+#include "content/public/browser/permission_result.h"
 #include "third_party/blink/public/mojom/permissions/permission_status.mojom.h"
 
 class GURL;
@@ -75,6 +76,10 @@
       const GURL& requesting_origin,
       const GURL& embedding_origin) = 0;
 
+  virtual PermissionResult GetPermissionResultForOriginWithoutContext(
+      blink::PermissionType permission,
+      const url::Origin& origin) = 0;
+
   // Returns the permission status for the current document in the given
   // RenderFrameHost. Use this over `GetPermissionStatus` whenever possible as
   // this API takes into account the lifecycle state of a given document (i.e.
@@ -84,6 +89,12 @@
       blink::PermissionType permission,
       RenderFrameHost* render_frame_host) = 0;
 
+  // The method does the same as `GetPermissionStatusForCurrentDocument` but
+  // additionally returns a source or reason for the permission status.
+  virtual PermissionResult GetPermissionResultForCurrentDocument(
+      blink::PermissionType permission,
+      RenderFrameHost* render_frame_host);
+
   // Returns the status of the given `permission` for a worker on
   // `worker_origin` running in `render_process_host`, also performing
   // additional checks such as Permission Policy.  Use this over
diff --git a/content/public/browser/permission_result.cc b/content/public/browser/permission_result.cc
new file mode 100644
index 0000000..6ff6d807
--- /dev/null
+++ b/content/public/browser/permission_result.cc
@@ -0,0 +1,16 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/public/browser/permission_result.h"
+
+namespace content {
+
+PermissionResult::PermissionResult(
+    blink::mojom::PermissionStatus permission_status,
+    PermissionStatusSource permission_status_source)
+    : status(permission_status), source(permission_status_source) {}
+
+PermissionResult::~PermissionResult() = default;
+
+}  // namespace content
diff --git a/content/public/browser/permission_result.h b/content/public/browser/permission_result.h
new file mode 100644
index 0000000..b26428f
--- /dev/null
+++ b/content/public/browser/permission_result.h
@@ -0,0 +1,62 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_PUBLIC_BROWSER_PERMISSION_RESULT_H_
+#define CONTENT_PUBLIC_BROWSER_PERMISSION_RESULT_H_
+
+#include "content/common/content_export.h"
+#include "third_party/blink/public/mojom/permissions/permission_status.mojom.h"
+
+namespace content {
+
+// Identifies the source or reason for a permission status being returned.
+enum class PermissionStatusSource {
+  // The reason for the status is not specified.
+  UNSPECIFIED,
+
+  // The status is the result of being blocked by the permissions kill switch.
+  KILL_SWITCH,
+
+  // The status is the result of being blocked due to the user dismissing a
+  // permission prompt multiple times.
+  MULTIPLE_DISMISSALS,
+
+  // The status is the result of being blocked due to the user ignoring a
+  // permission prompt multiple times.
+  MULTIPLE_IGNORES,
+
+  // This origin is insecure, thus its access to some permissions has been
+  // restricted, such as camera, microphone, etc.
+  INSECURE_ORIGIN,
+
+  // The feature has been blocked in the requesting frame by permissions policy.
+  FEATURE_POLICY,
+
+  // The virtual URL and the loaded URL are for different origins. The loaded
+  // URL is the one actually in the renderer, but the virtual URL is the one
+  // seen by the user. This may be very confusing for a user to see in a
+  // permissions request.
+  VIRTUAL_URL_DIFFERENT_ORIGIN,
+
+  // The status is the result of a permission being requested inside a portal.
+  // Permissions are currently always denied inside a portal.
+  PORTAL,
+
+  // The status is the result of a permission being requested inside a fenced
+  // frame. Permissions are currently always denied inside a fenced frame.
+  FENCED_FRAME,
+};
+
+struct CONTENT_EXPORT PermissionResult {
+  PermissionResult(blink::mojom::PermissionStatus status,
+                   PermissionStatusSource source);
+  ~PermissionResult();
+
+  blink::mojom::PermissionStatus status;
+  PermissionStatusSource source;
+};
+
+}  // namespace content
+
+#endif  // CONTENT_PUBLIC_BROWSER_PERMISSION_RESULT_H_
diff --git a/content/public/browser/web_contents.h b/content/public/browser/web_contents.h
index 2ee2f2a..c12e9514 100644
--- a/content/public/browser/web_contents.h
+++ b/content/public/browser/web_contents.h
@@ -1096,8 +1096,8 @@
   // be called with the bitmaps received from the renderer.
   // If |is_favicon| is true, the cookies are not sent and not accepted during
   // download.
-  // Bitmaps with pixel sizes larger than |max_bitmap_size| are filtered out
-  // from the bitmap results.
+  // If there are no bitmap results <= |max_bitmap_size|, the smallest bitmap
+  // is resized to |max_bitmap_size| and is the only result.
   // A |max_bitmap_size| of 0 means unlimited.
   // For vector images, |preferred_size| will serve as a viewport into which
   // the image will be rendered. This would usually be the dimensions of the
diff --git a/content/public/browser/web_ui_controller_interface_binder.cc b/content/public/browser/web_ui_controller_interface_binder.cc
new file mode 100644
index 0000000..b46089f
--- /dev/null
+++ b/content/public/browser/web_ui_controller_interface_binder.cc
@@ -0,0 +1,17 @@
+// Copyright 2022 The Chromium Authors.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/public/browser/web_ui_controller_interface_binder.h"
+
+#include "content/browser/bad_message.h"
+
+namespace content::internal {
+
+void ReceivedInvalidWebUIControllerMessage(RenderFrameHost* rfh) {
+  ReceivedBadMessage(
+      rfh->GetProcess(),
+      bad_message::BadMessageReason::RFH_INVALID_WEB_UI_CONTROLLER);
+}
+
+}  // namespace content::internal
diff --git a/content/public/browser/web_ui_controller_interface_binder.h b/content/public/browser/web_ui_controller_interface_binder.h
new file mode 100644
index 0000000..e517f9f
--- /dev/null
+++ b/content/public/browser/web_ui_controller_interface_binder.h
@@ -0,0 +1,96 @@
+// Copyright 2022 The Chromium Authors.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_PUBLIC_BROWSER_WEB_UI_CONTROLLER_INTERFACE_BINDER_H_
+#define CONTENT_PUBLIC_BROWSER_WEB_UI_CONTROLLER_INTERFACE_BINDER_H_
+
+#include "content/common/content_export.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/web_ui.h"
+#include "content/public/browser/web_ui_controller.h"
+#include "mojo/public/cpp/bindings/binder_map.h"
+
+namespace content {
+namespace internal {
+
+template <typename Interface, int N, typename... Subclasses>
+struct BinderHelper;
+
+template <typename Interface, typename WebUIControllerSubclass>
+bool SafeDownCastAndBindInterface(WebUI* web_ui,
+                                  mojo::PendingReceiver<Interface>& receiver) {
+  // Performs a safe downcast to the concrete WebUIController subclass.
+  WebUIControllerSubclass* concrete_controller =
+      web_ui ? web_ui->GetController()->GetAs<WebUIControllerSubclass>()
+             : nullptr;
+
+  if (!concrete_controller)
+    return false;
+
+  // Fails to compile if |Subclass| does not implement the appropriate overload
+  // for |Interface|.
+  concrete_controller->BindInterface(std::move(receiver));
+  return true;
+}
+
+template <typename Interface, int N, typename Subclass, typename... Subclasses>
+struct BinderHelper<Interface, N, std::tuple<Subclass, Subclasses...>> {
+  static bool BindInterface(WebUI* web_ui,
+                            mojo::PendingReceiver<Interface> receiver) {
+    // Try a different subclass if the current one is not the right
+    // WebUIController for the current WebUI page, and only fail if none of the
+    // passed subclasses match.
+    if (!SafeDownCastAndBindInterface<Interface, Subclass>(web_ui, receiver)) {
+      return BinderHelper<Interface, N - 1, std::tuple<Subclasses...>>::
+          BindInterface(web_ui, std::move(receiver));
+    }
+    return true;
+  }
+};
+
+template <typename Interface, typename Subclass, typename... Subclasses>
+struct BinderHelper<Interface, 0, std::tuple<Subclass, Subclasses...>> {
+  static bool BindInterface(WebUI* web_ui,
+                            mojo::PendingReceiver<Interface> receiver) {
+    return SafeDownCastAndBindInterface<Interface, Subclass>(web_ui, receiver);
+  }
+};
+
+void CONTENT_EXPORT ReceivedInvalidWebUIControllerMessage(RenderFrameHost* rfh);
+
+}  // namespace internal
+
+// Registers a binder in |map| that binds |Interface| iff the RenderFrameHost
+// has a WebUIController among type |WebUIControllerSubclasses|.
+template <typename Interface, typename... WebUIControllerSubclasses>
+void RegisterWebUIControllerInterfaceBinder(
+    mojo::BinderMapWithContext<RenderFrameHost*>* map) {
+  DCHECK(!map->Contains<Interface>())
+      << "A binder for " << Interface::Name_ << " has already been registered.";
+  map->Add<Interface>(base::BindRepeating(
+      [](RenderFrameHost* host, mojo::PendingReceiver<Interface> receiver) {
+        // This is expected to be called only for outermost main frames.
+        if (host->GetParentOrOuterDocument()) {
+          internal::ReceivedInvalidWebUIControllerMessage(host);
+          return;
+        }
+
+        const int size = sizeof...(WebUIControllerSubclasses);
+        bool is_bound =
+            internal::BinderHelper<Interface, size - 1,
+                                   std::tuple<WebUIControllerSubclasses...>>::
+                BindInterface(host->GetWebUI(), std::move(receiver));
+
+        // This is expected to be called only for the right WebUI pages matching
+        // the same WebUI associated to the RenderFrameHost.
+        if (!is_bound) {
+          internal::ReceivedInvalidWebUIControllerMessage(host);
+          return;
+        }
+      }));
+}
+
+}  // namespace content
+
+#endif  // CONTENT_PUBLIC_BROWSER_WEB_UI_CONTROLLER_INTERFACE_BINDER_H_
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index 80661db1..fd51e0c6 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -352,6 +352,11 @@
 const base::Feature kFedCmManifestValidation{"FedCmManifestValidation",
                                              base::FEATURE_ENABLED_BY_DEFAULT};
 
+// Enables usage of the FedCM API with multiple identity providers at the same
+// time.
+const base::Feature kFedCmMultipleIdentityProviders{
+    "FedCmMultipleIdentityProviders", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enables usage of First Party Sets to determine cookie availability.
 constexpr base::Feature kFirstPartySets{"FirstPartySets",
                                         base::FEATURE_DISABLED_BY_DEFAULT};
@@ -1199,6 +1204,11 @@
     "BackgroundMediaRendererHasModerateBinding",
     base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Reduce the priority of GPU process when in background so it is more likely
+// to be killed first if the OS needs more memory.
+const base::Feature kReduceGpuPriorityOnBackground{
+    "ReduceGpuPriorityOnBackground", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Allows the use of an experimental feature to drop any AccessibilityEvents
 // that are not relevant to currently enabled accessibility services.
 const base::Feature kOnDemandAccessibilityEvents{
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index 080db50..a436c7c 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -92,6 +92,7 @@
 CONTENT_EXPORT extern const char kFedCmIdpSignoutFieldTrialParamName[];
 CONTENT_EXPORT extern const char kFedCmIframeSupportFieldTrialParamName[];
 CONTENT_EXPORT extern const base::Feature kFedCmManifestValidation;
+CONTENT_EXPORT extern const base::Feature kFedCmMultipleIdentityProviders;
 CONTENT_EXPORT extern const base::Feature kFirstPartySets;
 CONTENT_EXPORT extern const base::FeatureParam<bool> kFirstPartySetsIsDogfooder;
 CONTENT_EXPORT extern const base::Feature kFontManagerEarlyInit;
@@ -312,6 +313,7 @@
 CONTENT_EXPORT extern const base::Feature kAccessibilityPageZoom;
 CONTENT_EXPORT extern const base::Feature
     kBackgroundMediaRendererHasModerateBinding;
+CONTENT_EXPORT extern const base::Feature kReduceGpuPriorityOnBackground;
 CONTENT_EXPORT extern const base::Feature kOnDemandAccessibilityEvents;
 CONTENT_EXPORT extern const base::Feature kRequestDesktopSiteAdditions;
 CONTENT_EXPORT extern const base::Feature kRequestDesktopSiteExceptions;
diff --git a/content/public/test/DEPS b/content/public/test/DEPS
index 166beac..4d049c846 100644
--- a/content/public/test/DEPS
+++ b/content/public/test/DEPS
@@ -31,6 +31,7 @@
   "+components/viz/test",
   "+device/vr/public/mojom",
   "+services/audio",
+  "+services/cert_verifier",
   "+services/metrics/public/cpp",
   "+services/network",
   "+services/service_manager",
diff --git a/content/public/test/OWNERS b/content/public/test/OWNERS
index 9f2054f..d7c2aa8 100644
--- a/content/public/test/OWNERS
+++ b/content/public/test/OWNERS
@@ -22,6 +22,9 @@
 # Network Service
 per-file network_service_test_helper.*=file://services/network/OWNERS
 
+# Cert Verifier Service
+per-file test_cert_verifier_service_factory.*=file://services/cert_verifier/OWNERS
+
 # DevTools-specific changes
 per-file *devtools*=file://content/browser/devtools/OWNERS
 
diff --git a/content/public/test/mock_permission_controller.h b/content/public/test/mock_permission_controller.h
index 0a8b14a..b940ff58 100644
--- a/content/public/test/mock_permission_controller.h
+++ b/content/public/test/mock_permission_controller.h
@@ -6,6 +6,7 @@
 #define CONTENT_PUBLIC_TEST_MOCK_PERMISSION_CONTROLLER_H_
 
 #include "content/public/browser/permission_controller.h"
+#include "content/public/browser/permission_result.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
 namespace blink {
@@ -38,10 +39,17 @@
       GetPermissionStatusForCurrentDocument,
       blink::mojom::PermissionStatus(blink::PermissionType permission,
                                      RenderFrameHost* render_frame_host));
-  MOCK_METHOD2(
+  MOCK_METHOD2(GetPermissionResultForCurrentDocument,
+               content::PermissionResult(blink::PermissionType permission,
+                                         RenderFrameHost* render_frame_host));
+  MOCK_METHOD2(GetPermissionResultForOriginWithoutContext,
+               content::PermissionResult(blink::PermissionType permission,
+                                         const url::Origin& requesting_origin));
+  MOCK_METHOD3(
       GetPermissionStatusForOriginWithoutContext,
       blink::mojom::PermissionStatus(blink::PermissionType permission,
-                                     const url::Origin& requesting_origin));
+                                     const url::Origin& requesting_origin,
+                                     const url::Origin& embedding_origin));
   void RequestPermissionFromCurrentDocument(
       blink::PermissionType permission,
       RenderFrameHost* render_frame_host,
diff --git a/content/public/test/mock_permission_manager.h b/content/public/test/mock_permission_manager.h
index f36cb43..2d178c4 100644
--- a/content/public/test/mock_permission_manager.h
+++ b/content/public/test/mock_permission_manager.h
@@ -6,8 +6,10 @@
 #define CONTENT_PUBLIC_TEST_MOCK_PERMISSION_MANAGER_H_
 
 #include "content/public/browser/permission_controller_delegate.h"
+#include "content/public/browser/permission_result.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "url/gurl.h"
+#include "url/origin.h"
 
 namespace blink {
 enum class PermissionType;
@@ -30,6 +32,9 @@
                blink::mojom::PermissionStatus(blink::PermissionType permission,
                                               const GURL& requesting_origin,
                                               const GURL& embedding_origin));
+  MOCK_METHOD2(GetPermissionResultForOriginWithoutContext,
+               content::PermissionResult(blink::PermissionType permission,
+                                         const url::Origin& origin));
   MOCK_METHOD2(GetPermissionStatusForCurrentDocument,
                blink::mojom::PermissionStatus(
                    blink::PermissionType permission,
diff --git a/services/cert_verifier/test_cert_verifier_service_factory.cc b/content/public/test/test_cert_verifier_service_factory.cc
similarity index 64%
rename from services/cert_verifier/test_cert_verifier_service_factory.cc
rename to content/public/test/test_cert_verifier_service_factory.cc
index 139d98f..3cece9b 100644
--- a/services/cert_verifier/test_cert_verifier_service_factory.cc
+++ b/content/public/test/test_cert_verifier_service_factory.cc
@@ -2,12 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "services/cert_verifier/test_cert_verifier_service_factory.h"
+#include "content/public/test/test_cert_verifier_service_factory.h"
 
 #include <memory>
 #include <type_traits>
 
 #include "base/memory/scoped_refptr.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
 #include "net/net_buildflags.h"
 #include "services/cert_verifier/cert_verifier_service.h"
 #include "services/cert_verifier/cert_verifier_service_factory.h"
@@ -72,8 +74,37 @@
 }
 
 void TestCertVerifierServiceFactoryImpl::InitDelegate() {
-  delegate_ = std::make_unique<CertVerifierServiceFactoryImpl>(
-      delegate_remote_.BindNewPipeAndPassReceiver());
+  delegate_ = base::MakeRefCounted<DelegateOwner>(
+#if BUILDFLAG(IS_CHROMEOS)
+      // In-process CertVerifierService in Ash and Lacros should run on the IO
+      // thread because it interacts with IO-bound NSS and ChromeOS user slots.
+      // See for example InitializeNSSForChromeOSUser() or
+      // CertDbInitializerIOImpl.
+      content::GetIOThreadTaskRunner({})
+#else
+      base::SequencedTaskRunnerHandle::Get()
+#endif
+  );
+  delegate_->Init(delegate_remote_.BindNewPipeAndPassReceiver());
+}
+
+TestCertVerifierServiceFactoryImpl::DelegateOwner::DelegateOwner(
+    scoped_refptr<base::SequencedTaskRunner> task_runner)
+    : base::RefCountedDeleteOnSequence<DelegateOwner>(std::move(task_runner)) {}
+
+TestCertVerifierServiceFactoryImpl::DelegateOwner::~DelegateOwner() = default;
+
+void TestCertVerifierServiceFactoryImpl::DelegateOwner::Init(
+    mojo::PendingReceiver<cert_verifier::mojom::CertVerifierServiceFactory>
+        receiver) {
+  if (!owning_task_runner()->RunsTasksInCurrentSequence()) {
+    owning_task_runner()->PostTask(
+        FROM_HERE,
+        base::BindOnce(&DelegateOwner::Init, this, std::move(receiver)));
+    return;
+  }
+  delegate_ =
+      std::make_unique<CertVerifierServiceFactoryImpl>(std::move(receiver));
 }
 
 }  // namespace cert_verifier
diff --git a/services/cert_verifier/test_cert_verifier_service_factory.h b/content/public/test/test_cert_verifier_service_factory.h
similarity index 75%
rename from services/cert_verifier/test_cert_verifier_service_factory.h
rename to content/public/test/test_cert_verifier_service_factory.h
index 6ff8a30..bed5b36e 100644
--- a/services/cert_verifier/test_cert_verifier_service_factory.h
+++ b/content/public/test/test_cert_verifier_service_factory.h
@@ -2,12 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef SERVICES_CERT_VERIFIER_TEST_CERT_VERIFIER_SERVICE_FACTORY_H_
-#define SERVICES_CERT_VERIFIER_TEST_CERT_VERIFIER_SERVICE_FACTORY_H_
+#ifndef CONTENT_PUBLIC_TEST_TEST_CERT_VERIFIER_SERVICE_FACTORY_H_
+#define CONTENT_PUBLIC_TEST_TEST_CERT_VERIFIER_SERVICE_FACTORY_H_
 
 #include <memory>
 
 #include "base/containers/circular_deque.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/ref_counted_delete_on_sequence.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
@@ -63,14 +65,32 @@
   }
 
  private:
+  class DelegateOwner : public base::RefCountedDeleteOnSequence<DelegateOwner> {
+   public:
+    explicit DelegateOwner(
+        scoped_refptr<base::SequencedTaskRunner> owning_task_runner);
+
+    void Init(
+        mojo::PendingReceiver<cert_verifier::mojom::CertVerifierServiceFactory>
+            receiver);
+
+   private:
+    friend class base::RefCountedDeleteOnSequence<DelegateOwner>;
+    friend class base::DeleteHelper<DelegateOwner>;
+
+    ~DelegateOwner();
+
+    std::unique_ptr<CertVerifierServiceFactoryImpl> delegate_;
+  };
+
   void InitDelegate();
 
   mojo::Remote<mojom::CertVerifierServiceFactory> delegate_remote_;
-  std::unique_ptr<CertVerifierServiceFactoryImpl> delegate_;
+  scoped_refptr<DelegateOwner> delegate_;
 
   base::circular_deque<GetNewCertVerifierParams> captured_params_;
 };
 
 }  // namespace cert_verifier
 
-#endif  // SERVICES_CERT_VERIFIER_TEST_CERT_VERIFIER_SERVICE_FACTORY_H_
+#endif  // CONTENT_PUBLIC_TEST_TEST_CERT_VERIFIER_SERVICE_FACTORY_H_
diff --git a/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ChildProcessLauncherTestUtils.java b/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ChildProcessLauncherTestUtils.java
index 3481221..0ee095fa 100644
--- a/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ChildProcessLauncherTestUtils.java
+++ b/content/shell/android/shell_apk/src/org/chromium/content_shell_apk/ChildProcessLauncherTestUtils.java
@@ -59,8 +59,9 @@
             @Override
             public ChildProcessLauncherHelperImpl call() {
                 return ChildProcessLauncherHelperImpl.createAndStartForTesting(commandLine,
-                        filesToBeMapped, sandboxed, true /* canUseWarmUpConnection */,
-                        null /* binderCallback */, doSetupConnection);
+                        filesToBeMapped, sandboxed, false /* reducePriorityOnBackground */,
+                        true /* canUseWarmUpConnection */, null /* binderCallback */,
+                        doSetupConnection);
             }
         });
     }
diff --git a/content/shell/browser/shell_permission_manager.cc b/content/shell/browser/shell_permission_manager.cc
index 0b62b1b2..4d5ba47 100644
--- a/content/shell/browser/shell_permission_manager.cc
+++ b/content/shell/browser/shell_permission_manager.cc
@@ -154,6 +154,16 @@
              : blink::mojom::PermissionStatus::DENIED;
 }
 
+PermissionResult
+ShellPermissionManager::GetPermissionResultForOriginWithoutContext(
+    blink::PermissionType permission,
+    const url::Origin& origin) {
+  blink::mojom::PermissionStatus status =
+      GetPermissionStatus(permission, origin.GetURL(), origin.GetURL());
+
+  return PermissionResult(status, content::PermissionStatusSource::UNSPECIFIED);
+}
+
 blink::mojom::PermissionStatus
 ShellPermissionManager::GetPermissionStatusForCurrentDocument(
     PermissionType permission,
diff --git a/content/shell/browser/shell_permission_manager.h b/content/shell/browser/shell_permission_manager.h
index cf6d8ab..70210564 100644
--- a/content/shell/browser/shell_permission_manager.h
+++ b/content/shell/browser/shell_permission_manager.h
@@ -7,6 +7,7 @@
 
 #include "base/callback_forward.h"
 #include "content/public/browser/permission_controller_delegate.h"
+#include "content/public/browser/permission_result.h"
 
 namespace blink {
 enum class PermissionType;
@@ -53,6 +54,9 @@
       blink::PermissionType permission,
       const GURL& requesting_origin,
       const GURL& embedding_origin) override;
+  PermissionResult GetPermissionResultForOriginWithoutContext(
+      blink::PermissionType permission,
+      const url::Origin& origin) override;
   blink::mojom::PermissionStatus GetPermissionStatusForCurrentDocument(
       blink::PermissionType permission,
       content::RenderFrameHost* render_frame_host) override;
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 240f5ad8..b12c3e12 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -290,6 +290,8 @@
     "../public/test/test_aggregation_service.h",
     "../public/test/test_browser_context.cc",
     "../public/test/test_browser_context.h",
+    "../public/test/test_cert_verifier_service_factory.cc",
+    "../public/test/test_cert_verifier_service_factory.h",
     "../public/test/test_content_client_initializer.cc",
     "../public/test/test_content_client_initializer.h",
     "../public/test/test_devtools_protocol_client.cc",
@@ -539,6 +541,7 @@
     "//net:test_support",
     "//services/audio",
     "//services/audio/public/mojom",
+    "//services/cert_verifier:lib",
     "//services/data_decoder/public/cpp:test_support",
     "//services/data_decoder/public/mojom",
     "//services/device/public/mojom",
diff --git a/content/web_test/browser/web_test_permission_manager.cc b/content/web_test/browser/web_test_permission_manager.cc
index 348bcd4..da00838 100644
--- a/content/web_test/browser/web_test_permission_manager.cc
+++ b/content/web_test/browser/web_test_permission_manager.cc
@@ -168,6 +168,16 @@
   return it->second;
 }
 
+PermissionResult
+WebTestPermissionManager::GetPermissionResultForOriginWithoutContext(
+    blink::PermissionType permission,
+    const url::Origin& origin) {
+  blink::mojom::PermissionStatus status =
+      GetPermissionStatus(permission, origin.GetURL(), origin.GetURL());
+
+  return PermissionResult(status, content::PermissionStatusSource::UNSPECIFIED);
+}
+
 blink::mojom::PermissionStatus
 WebTestPermissionManager::GetPermissionStatusForCurrentDocument(
     blink::PermissionType permission,
diff --git a/content/web_test/browser/web_test_permission_manager.h b/content/web_test/browser/web_test_permission_manager.h
index 998ab38b1..c9f1cbd 100644
--- a/content/web_test/browser/web_test_permission_manager.h
+++ b/content/web_test/browser/web_test_permission_manager.h
@@ -11,6 +11,7 @@
 #include "base/containers/id_map.h"
 #include "base/synchronization/lock.h"
 #include "content/public/browser/permission_controller_delegate.h"
+#include "content/public/browser/permission_result.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
 #include "third_party/blink/public/mojom/permissions/permission.mojom.h"
 #include "third_party/blink/public/mojom/permissions/permission_automation.mojom.h"
@@ -63,6 +64,9 @@
       blink::PermissionType permission,
       const GURL& requesting_origin,
       const GURL& embedding_origin) override;
+  PermissionResult GetPermissionResultForOriginWithoutContext(
+      blink::PermissionType permission,
+      const url::Origin& origin) override;
   blink::mojom::PermissionStatus GetPermissionStatusForCurrentDocument(
       blink::PermissionType permission,
       content::RenderFrameHost* render_frame_host) override;
diff --git a/content/web_test/renderer/text_input_controller.cc b/content/web_test/renderer/text_input_controller.cc
index 51a7b49..cc0e6769 100644
--- a/content/web_test/renderer/text_input_controller.cc
+++ b/content/web_test/renderer/text_input_controller.cc
@@ -57,8 +57,8 @@
   bool HasMarkedText();
   std::vector<int> MarkedRange();
   std::vector<int> SelectedRange();
-  std::vector<int> FirstRectForCharacterRange(unsigned location,
-                                              unsigned length);
+  std::vector<int> FirstRectForCharacterRange(uint32_t location,
+                                              uint32_t length);
   void SetComposition(const std::string& text);
   void SetCompositionWithReplacementRange(const std::string& text,
                                           int replacement_start,
@@ -186,8 +186,8 @@
 }
 
 std::vector<int> TextInputControllerBindings::FirstRectForCharacterRange(
-    unsigned location,
-    unsigned length) {
+    uint32_t location,
+    uint32_t length) {
   return controller_ ? controller_->FirstRectForCharacterRange(location, length)
                      : std::vector<int>();
 }
@@ -363,8 +363,8 @@
 }
 
 std::vector<int> TextInputController::FirstRectForCharacterRange(
-    unsigned location,
-    unsigned length) {
+    uint32_t location,
+    uint32_t length) {
   gfx::Rect rect;
   if (!view()->FocusedFrame() ||
       !view()->FocusedFrame()->FirstRectForCharacterRange(location, length,
diff --git a/content/web_test/renderer/text_input_controller.h b/content/web_test/renderer/text_input_controller.h
index b6ab1dd..9fbb5f37 100644
--- a/content/web_test/renderer/text_input_controller.h
+++ b/content/web_test/renderer/text_input_controller.h
@@ -48,8 +48,8 @@
   bool HasMarkedText();
   std::vector<int> MarkedRange();
   std::vector<int> SelectedRange();
-  std::vector<int> FirstRectForCharacterRange(unsigned location,
-                                              unsigned length);
+  std::vector<int> FirstRectForCharacterRange(uint32_t location,
+                                              uint32_t length);
   void SetComposition(const std::string& text,
                       int replacement_range_start,
                       int replacement_range_end);
diff --git a/device/bluetooth/floss/floss_socket_manager.cc b/device/bluetooth/floss/floss_socket_manager.cc
index dcf46f1..2e98597 100644
--- a/device/bluetooth/floss/floss_socket_manager.cc
+++ b/device/bluetooth/floss/floss_socket_manager.cc
@@ -127,6 +127,26 @@
     absl::optional<FlossSocketManager::FlossListeningSocket>* socket);
 
 template <>
+void FlossDBusClient::WriteDBusParam(
+    dbus::MessageWriter* writer,
+    const FlossSocketManager::FlossListeningSocket& socket) {
+  dbus::MessageWriter array(nullptr);
+  dbus::MessageWriter dict(nullptr);
+
+  writer->OpenArray("{sv}", &array);
+
+  WriteDictEntry(&array, kListeningPropId, socket.id);
+  WriteDictEntry(&array, kListeningPropSockType, socket.type);
+  WriteDictEntry(&array, kListeningPropFlags, socket.flags);
+  WriteDictEntry(&array, kListeningPropPsm, socket.psm);
+  WriteDictEntry(&array, kListeningPropChannel, socket.channel);
+  WriteDictEntry(&array, kListeningPropName, socket.name);
+  WriteDictEntry(&array, kListeningPropUuid, socket.uuid);
+
+  writer->CloseContainer(&array);
+}
+
+template <>
 bool FlossDBusClient::ReadDBusParam(dbus::MessageReader* reader,
                                     FlossSocketManager::FlossSocket* socket) {
   dbus::MessageReader array(nullptr);
diff --git a/device/bluetooth/floss/floss_socket_manager.h b/device/bluetooth/floss/floss_socket_manager.h
index 1c7f762..f680633 100644
--- a/device/bluetooth/floss/floss_socket_manager.h
+++ b/device/bluetooth/floss/floss_socket_manager.h
@@ -58,7 +58,7 @@
 
   // Represents a listening socket.
   struct FlossListeningSocket {
-    SocketId id;
+    SocketId id = FlossSocketManager::kInvalidSocketId;
     SocketType type;
     int flags;
     absl::optional<int> psm;
@@ -69,11 +69,13 @@
     FlossListeningSocket();
     FlossListeningSocket(const FlossListeningSocket&);
     ~FlossListeningSocket();
+
+    bool is_valid() const { return id != FlossSocketManager::kInvalidSocketId; }
   };
 
   // Represents a connecting socket (either incoming or outgoing).
   struct FlossSocket {
-    SocketId id;
+    SocketId id = FlossSocketManager::kInvalidSocketId;
     FlossDeviceId remote_device;
     SocketType type;
     int flags;
diff --git a/device/bluetooth/floss/floss_socket_manager_unittest.cc b/device/bluetooth/floss/floss_socket_manager_unittest.cc
index b0ec631..9806aaa 100644
--- a/device/bluetooth/floss/floss_socket_manager_unittest.cc
+++ b/device/bluetooth/floss/floss_socket_manager_unittest.cc
@@ -83,6 +83,30 @@
     sockmgr_->Init(bus_.get(), kSocketManagerInterface, adapter_path_.value());
   }
 
+  void SetupListeningSocket() {
+    // First listen on something. This will push the socket callbacks into a
+    // map.
+    EXPECT_CALL(
+        *sockmgr_proxy_.get(),
+        DoCallMethodWithErrorResponse(
+            HasMemberOf(socket_manager::kListenUsingRfcommWithServiceRecord), _,
+            _))
+        .WillOnce(
+            Invoke(this, &FlossSocketManagerTest::HandleReturnSocketResult));
+
+    sockmgr_->ListenUsingRfcomm(
+        "Foo", device::BluetoothUUID("F00D"), Security::kSecure,
+        base::BindOnce(&FlossSocketManagerTest::SockStatusCb,
+                       weak_ptr_factory_.GetWeakPtr()),
+        base::BindRepeating(&FlossSocketManagerTest::SockConnectionStateChanged,
+                            weak_ptr_factory_.GetWeakPtr()),
+        base::BindRepeating(&FlossSocketManagerTest::SockConnectionAccepted,
+                            weak_ptr_factory_.GetWeakPtr()));
+
+    // We should call accept here but that state is tracked on the daemon side.
+    // Opting not to simply because we have it mocked away...
+  }
+
   void HandleRegisterCallback(
       ::dbus::MethodCall* method_call,
       int timeout_ms,
@@ -114,10 +138,22 @@
     std::move(*cb).Run(response.get(), nullptr);
   }
 
+  void HandleReturnSuccess(::dbus::MethodCall* method_call,
+                           int timeout_ms,
+                           ::dbus::ObjectProxy::ResponseOrErrorCallback* cb) {
+    auto response = ::dbus::Response::CreateEmpty();
+    ::dbus::MessageWriter msg(response.get());
+
+    BtifStatus status = BtifStatus::kSuccess;
+    FlossDBusClient::WriteAllDBusParams(&msg, status);
+
+    std::move(*cb).Run(response.get(), nullptr);
+  }
+
   void SendOutgoingConnectionResult(
       FlossSocketManager::SocketId id,
       BtifStatus status,
-      absl::optional<FlossSocketManager::FlossSocket> socket,
+      const absl::optional<FlossSocketManager::FlossSocket>& socket,
       dbus::ExportedObject::ResponseSender response) {
     dbus::MethodCall method_call(socket_manager::kCallbackInterface,
                                  socket_manager::kOnOutgoingConnectionResult);
@@ -128,6 +164,43 @@
     sockmgr_->OnOutgoingConnectionResult(&method_call, std::move(response));
   }
 
+  void SendIncomingSocketReady(
+      const FlossSocketManager::FlossListeningSocket& server_socket,
+      BtifStatus status,
+      dbus::ExportedObject::ResponseSender response) {
+    dbus::MethodCall method_call(socket_manager::kCallbackInterface,
+                                 socket_manager::kOnIncomingSocketReady);
+    method_call.SetSerial(serial_++);
+    dbus::MessageWriter writer(&method_call);
+    FlossDBusClient::WriteAllDBusParams(&writer, server_socket, status);
+
+    sockmgr_->OnIncomingSocketReady(&method_call, std::move(response));
+  }
+
+  void SendIncomingSocketClosed(FlossSocketManager::SocketId id,
+                                BtifStatus status,
+                                dbus::ExportedObject::ResponseSender response) {
+    dbus::MethodCall method_call(socket_manager::kCallbackInterface,
+                                 socket_manager::kOnIncomingSocketReady);
+    method_call.SetSerial(serial_++);
+    dbus::MessageWriter writer(&method_call);
+    FlossDBusClient::WriteAllDBusParams(&writer, id, status);
+
+    sockmgr_->OnIncomingSocketClosed(&method_call, std::move(response));
+  }
+
+  void SendIncomingConnection(FlossSocketManager::SocketId id,
+                              const FlossSocketManager::FlossSocket& socket,
+                              dbus::ExportedObject::ResponseSender response) {
+    dbus::MethodCall method_call(socket_manager::kCallbackInterface,
+                                 socket_manager::kOnIncomingSocketReady);
+    method_call.SetSerial(serial_++);
+    dbus::MessageWriter writer(&method_call);
+    FlossDBusClient::WriteAllDBusParams(&writer, id, socket);
+
+    sockmgr_->OnHandleIncomingConnection(&method_call, std::move(response));
+  }
+
   void SockStatusCb(DBusResult<BtifStatus> result) {
     if (!result.has_value()) {
       last_status_ = BtifStatus::kFail;
@@ -140,11 +213,13 @@
       FlossSocketManager::ServerSocketState state,
       FlossSocketManager::FlossListeningSocket socket,
       BtifStatus status) {
-    // No-op
+    last_state_ = state;
+    last_server_socket_ = socket;
+    last_status_ = status;
   }
 
   void SockConnectionAccepted(FlossSocketManager::FlossSocket&& socket) {
-    // No -op
+    last_incoming_socket_ = std::move(socket);
   }
 
   void ExpectNormalResponse(std::unique_ptr<dbus::Response> response) {
@@ -155,7 +230,11 @@
   int adapter_index_ = 2;
   int serial_ = 1;
   dbus::ObjectPath adapter_path_;
+
+  FlossSocketManager::ServerSocketState last_state_;
+  FlossSocketManager::FlossListeningSocket last_server_socket_;
   BtifStatus last_status_;
+  FlossSocketManager::FlossSocket last_incoming_socket_;
 
   uint32_t callback_id_ctr_ = 1;
   uint64_t socket_id_ctr_ = 1;
@@ -338,4 +417,129 @@
   }
 }
 
+// Really basic calls to accept and close
+TEST_F(FlossSocketManagerTest, AcceptAndCloseConnection) {
+  Init();
+
+  EXPECT_CALL(
+      *sockmgr_proxy_.get(),
+      DoCallMethodWithErrorResponse(HasMemberOf(socket_manager::kAccept), _, _))
+      .WillOnce(Invoke(this, &FlossSocketManagerTest::HandleReturnSuccess));
+
+  last_status_ = BtifStatus::kNotReady;
+  sockmgr_->Accept(socket_id_ctr_, 42,
+                   base::BindOnce(&FlossSocketManagerTest::SockStatusCb,
+                                  weak_ptr_factory_.GetWeakPtr()));
+  EXPECT_EQ(BtifStatus::kSuccess, last_status_);
+
+  EXPECT_CALL(
+      *sockmgr_proxy_.get(),
+      DoCallMethodWithErrorResponse(HasMemberOf(socket_manager::kClose), _, _))
+      .WillOnce(Invoke(this, &FlossSocketManagerTest::HandleReturnSuccess));
+
+  last_status_ = BtifStatus::kNotReady;
+  sockmgr_->Close(socket_id_ctr_,
+                  base::BindOnce(&FlossSocketManagerTest::SockStatusCb,
+                                 weak_ptr_factory_.GetWeakPtr()));
+  EXPECT_EQ(BtifStatus::kSuccess, last_status_);
+}
+
+// Handle state changes from calling accept and close.
+TEST_F(FlossSocketManagerTest, IncomingStateChanges) {
+  Init();
+  SetupListeningSocket();
+
+  // With a bad id, callbacks will never be dispatched.
+  FlossSocketManager::FlossListeningSocket bad_socket;
+  bad_socket.id = 123456789;
+
+  // Good id is the last socket ctr we used.
+  FlossSocketManager::FlossListeningSocket good_socket;
+  good_socket.id = socket_id_ctr_ - 1;
+  good_socket.name = "Foo";
+  good_socket.uuid = device::BluetoothUUID("F00D");
+
+  // Empty out the last seen status and socket.
+  last_status_ = BtifStatus::kNotReady;
+  last_server_socket_ = FlossSocketManager::FlossListeningSocket();
+  last_state_ = FlossSocketManager::ServerSocketState::kClosed;
+
+  EXPECT_FALSE(last_server_socket_.is_valid());
+  // Send an invalid update. Should result in no callbacks being called.
+  SendIncomingSocketReady(
+      bad_socket, BtifStatus::kSuccess,
+      base::BindOnce(&FlossSocketManagerTest::ExpectNormalResponse,
+                     weak_ptr_factory_.GetWeakPtr()));
+  EXPECT_EQ(BtifStatus::kNotReady, last_status_);
+  EXPECT_FALSE(last_server_socket_.is_valid());
+
+  // Send a successful ready to a valid socket.
+  SendIncomingSocketReady(
+      good_socket, BtifStatus::kSuccess,
+      base::BindOnce(&FlossSocketManagerTest::ExpectNormalResponse,
+                     weak_ptr_factory_.GetWeakPtr()));
+  EXPECT_EQ(BtifStatus::kSuccess, last_status_);
+  EXPECT_EQ(last_state_, FlossSocketManager::ServerSocketState::kReady);
+  EXPECT_TRUE(last_server_socket_.is_valid());
+
+  // Empty out the last seen status and socket.
+  last_status_ = BtifStatus::kNotReady;
+  last_server_socket_ = FlossSocketManager::FlossListeningSocket();
+
+  // Send an invalid update. Should result in no callbacks being called.
+  SendIncomingSocketClosed(
+      bad_socket.id, BtifStatus::kSuccess,
+      base::BindOnce(&FlossSocketManagerTest::ExpectNormalResponse,
+                     weak_ptr_factory_.GetWeakPtr()));
+  EXPECT_EQ(BtifStatus::kNotReady, last_status_);
+  EXPECT_FALSE(last_server_socket_.is_valid());
+
+  // Send a successful close to a valid socket.
+  SendIncomingSocketClosed(
+      good_socket.id, BtifStatus::kSuccess,
+      base::BindOnce(&FlossSocketManagerTest::ExpectNormalResponse,
+                     weak_ptr_factory_.GetWeakPtr()));
+  EXPECT_EQ(BtifStatus::kSuccess, last_status_);
+  EXPECT_EQ(last_server_socket_.id, good_socket.id);
+  EXPECT_EQ(last_state_, FlossSocketManager::ServerSocketState::kClosed);
+
+  // Try sending a ready to the same socket and nothing should happen.
+  last_status_ = BtifStatus::kNotReady;
+  SendIncomingSocketReady(
+      good_socket, BtifStatus::kSuccess,
+      base::BindOnce(&FlossSocketManagerTest::ExpectNormalResponse,
+                     weak_ptr_factory_.GetWeakPtr()));
+  EXPECT_EQ(BtifStatus::kNotReady, last_status_);
+}
+
+// Handle incoming socket connections.
+TEST_F(FlossSocketManagerTest, IncomingConnections) {
+  Init();
+  SetupListeningSocket();
+
+  // With a bad id, callbacks will never be dispatched.
+  FlossSocketManager::FlossSocket bad_socket;
+  bad_socket.id = 123456789;
+
+  // Good id is the last socket ctr we used.
+  FlossSocketManager::FlossSocket good_socket;
+  good_socket.id = socket_id_ctr_ - 1;
+
+  last_incoming_socket_ = FlossSocketManager::FlossSocket();
+  EXPECT_FALSE(last_incoming_socket_.is_valid());
+
+  SendIncomingConnection(
+      bad_socket.id, bad_socket,
+      base::BindOnce(&FlossSocketManagerTest::ExpectNormalResponse,
+                     weak_ptr_factory_.GetWeakPtr()));
+  EXPECT_FALSE(last_incoming_socket_.is_valid());
+
+  SendIncomingConnection(
+      good_socket.id, good_socket,
+      base::BindOnce(&FlossSocketManagerTest::ExpectNormalResponse,
+                     weak_ptr_factory_.GetWeakPtr()));
+  EXPECT_TRUE(last_incoming_socket_.is_valid());
+  EXPECT_EQ(last_incoming_socket_.id, good_socket.id);
+}
+
 }  // namespace floss
diff --git a/extensions/browser/api/execute_code_function.cc b/extensions/browser/api/execute_code_function.cc
index 765028b..27e7386 100644
--- a/extensions/browser/api/execute_code_function.cc
+++ b/extensions/browser/api/execute_code_function.cc
@@ -134,10 +134,13 @@
     sources.push_back(mojom::JSSource::New(code_string, script_url_));
     // tabs.executeScript does not support waiting for promises (only
     // scripting.executeScript does).
-    constexpr bool kWaitForPromises = false;
     injection = mojom::CodeInjection::NewJs(mojom::JSInjection::New(
-        std::move(sources), mojom::ExecutionWorld::kIsolated, wants_result,
-        user_gesture(), kWaitForPromises));
+        std::move(sources), mojom::ExecutionWorld::kIsolated,
+        wants_result ? blink::mojom::WantResultOption::kWantResult
+                     : blink::mojom::WantResultOption::kNoResult,
+        user_gesture() ? blink::mojom::UserActivationOption::kActivate
+                       : blink::mojom::UserActivationOption::kDoNotActivate,
+        blink::mojom::PromiseResultOption::kDoNotWait));
   }
 
   executor->ExecuteScript(
diff --git a/extensions/browser/extension_prefs.cc b/extensions/browser/extension_prefs.cc
index 147e9fa..cfa38f1 100644
--- a/extensions/browser/extension_prefs.cc
+++ b/extensions/browser/extension_prefs.cc
@@ -725,14 +725,18 @@
   return true;
 }
 
+const base::Value* ExtensionPrefs::GetPrefAsValue(
+    const std::string& extension_id,
+    base::StringPiece pref_key) const {
+  const base::DictionaryValue* ext = GetExtensionPref(extension_id);
+  return ext ? ext->FindDictPath(pref_key) : nullptr;
+}
+
 bool ExtensionPrefs::ReadPrefAsDictionary(
     const std::string& extension_id,
     base::StringPiece pref_key,
     const base::DictionaryValue** out_value) const {
-  const base::DictionaryValue* ext = GetExtensionPref(extension_id);
-  if (!ext)
-    return false;
-  const base::Value* out = ext->FindDictPath(pref_key);
+  const base::Value* out = GetPrefAsValue(extension_id, pref_key);
   if (!out)
     return false;
   if (out_value)
@@ -741,6 +745,13 @@
   return true;
 }
 
+const base::Value::Dict* ExtensionPrefs::ReadPrefAsDict(
+    const std::string& extension_id,
+    base::StringPiece pref_key) const {
+  const base::Value* out = GetPrefAsValue(extension_id, pref_key);
+  return out ? &out->GetDict() : nullptr;
+}
+
 bool ExtensionPrefs::HasPrefForExtension(
     const std::string& extension_id) const {
   return GetExtensionPref(extension_id) != NULL;
diff --git a/extensions/browser/extension_prefs.h b/extensions/browser/extension_prefs.h
index 1fd512f..e98d4e5 100644
--- a/extensions/browser/extension_prefs.h
+++ b/extensions/browser/extension_prefs.h
@@ -350,10 +350,14 @@
                       base::StringPiece pref_key,
                       const base::ListValue** out_value) const;
 
+  // DEPRECATED: prefer ReadPrefAsDict() instead.
   bool ReadPrefAsDictionary(const std::string& extension_id,
                             base::StringPiece pref_key,
                             const base::DictionaryValue** out_value) const;
 
+  const base::Value::Dict* ReadPrefAsDict(const std::string& extension_id,
+                                          base::StringPiece pref_key) const;
+
   // Interprets the list pref, |pref_key| in |extension_id|'s preferences, as a
   // URLPatternSet. The |valid_schemes| specify how to parse the URLPatterns.
   bool ReadPrefAsURLPatternSet(const std::string& extension_id,
@@ -848,6 +852,11 @@
   // doesn't exist.
   const base::DictionaryValue* GetExtensionPref(const std::string& id) const;
 
+  // Returns an immutable base::Value for extension |id|'s prefs, or nullptr if
+  // it doesn't exist.
+  const base::Value* GetPrefAsValue(const std::string& extension_id,
+                                    base::StringPiece pref_key) const;
+
   // Modifies the extensions disable reasons to add a new reason, remove an
   // existing reason, or clear all reasons. Notifies observers if the set of
   // DisableReasons has changed.
diff --git a/extensions/common/mojom/code_injection.mojom b/extensions/common/mojom/code_injection.mojom
index b2c0d3ba1..375c14e 100644
--- a/extensions/common/mojom/code_injection.mojom
+++ b/extensions/common/mojom/code_injection.mojom
@@ -6,6 +6,7 @@
 
 import "extensions/common/mojom/css_origin.mojom";
 import "extensions/common/mojom/execution_world.mojom";
+import "third_party/blink/public/mojom/script/script_evaluation_params.mojom";
 import "url/mojom/url.mojom";
 
 // A single JS source to execute in the renderer.
@@ -37,15 +38,15 @@
 
   // Whether the caller is interested in the result value. Manifest-declared
   // content scripts and executeScript() calls without a response callback
-  // are examples of when this will be false.
-  bool wants_result;
+  // are examples of when this will be `kNoResult`.
+  blink.mojom.WantResultOption wants_result;
 
-  // Whether the code to be executed should be associated with a user gesture.
-  bool user_gesture;
+  // Whether the code to be executed with synthesized user activation.
+  blink.mojom.UserActivationOption user_gesture;
 
-  // If true and a script evaluates to a promise, causes the injection to
-  // wait for the promise to resolve and pass back the result.
-  bool wait_for_promise;
+  // For scripts that evaluate to promises, whether to wait for the resolution
+  // of the resulting promises.
+  blink.mojom.PromiseResultOption wait_for_promise;
 };
 
 // A struct representing a collection of CSS code to insert in the renderer.
diff --git a/extensions/renderer/api/automation/automation_internal_custom_bindings.cc b/extensions/renderer/api/automation/automation_internal_custom_bindings.cc
index 06771c3..67a717ed 100644
--- a/extensions/renderer/api/automation/automation_internal_custom_bindings.cc
+++ b/extensions/renderer/api/automation/automation_internal_custom_bindings.cc
@@ -2455,7 +2455,8 @@
   ui::AXTreeID tree_id = event_bundle.tree_id;
   AutomationAXTreeWrapper* tree_wrapper;
   auto iter = tree_id_to_tree_wrapper_map_.find(tree_id);
-  if (iter == tree_id_to_tree_wrapper_map_.end()) {
+  bool is_new_tree = iter == tree_id_to_tree_wrapper_map_.end();
+  if (is_new_tree) {
     tree_wrapper = new AutomationAXTreeWrapper(tree_id, this);
     tree_id_to_tree_wrapper_map_.insert(
         std::make_pair(tree_id, base::WrapUnique(tree_wrapper)));
@@ -2473,6 +2474,16 @@
     return;
   }
 
+  // Send an initial event to ensure the js-side objects get created for new
+  // trees.
+  if (is_new_tree) {
+    ui::AXEvent initial_event;
+    initial_event.id = -1;
+    initial_event.event_from = ax::mojom::EventFrom::kNone;
+    initial_event.event_type = ax::mojom::Event::kNone;
+    SendAutomationEvent(tree_id, gfx::Point(), initial_event);
+  }
+
   // After handling events in js, if the client did not add any event listeners,
   // shut things down.
   TreeEventListenersChanged(tree_wrapper);
@@ -2615,11 +2626,12 @@
       api::automation::ToString(automation_event_type);
 
   // These events get used internally to trigger other behaviors in js.
-  ax::mojom::Event ax_event = event.event_type;
-  bool fire_event = ax_event == ax::mojom::Event::kNone ||
-                    ax_event == ax::mojom::Event::kHitTestResult ||
-                    ax_event == ax::mojom::Event::kMediaStartedPlaying ||
-                    ax_event == ax::mojom::Event::kMediaStoppedPlaying;
+  bool fire_event =
+      automation_event_type == api::automation::EVENT_TYPE_NONE ||
+      automation_event_type == api::automation::EVENT_TYPE_HITTESTRESULT ||
+      automation_event_type ==
+          api::automation::EVENT_TYPE_MEDIASTARTEDPLAYING ||
+      automation_event_type == api::automation::EVENT_TYPE_MEDIASTOPPEDPLAYING;
 
   // If we don't explicitly recognize the event type, require a valid, unignored
   // node target.
@@ -2629,8 +2641,10 @@
     return;
 
   while (node && tree_wrapper && !fire_event) {
-    if (tree_wrapper->HasEventListener(automation_event_type, node))
+    if (tree_wrapper->HasEventListener(automation_event_type, node)) {
       fire_event = true;
+      break;
+    }
     node = GetParent(node, &tree_wrapper);
   }
 
@@ -2669,6 +2683,9 @@
   args.Append(std::move(event_params));
   bindings_system_->DispatchEventInContext(
       "automationInternal.onAccessibilityEvent", args, nullptr, context());
+
+  if (!notify_event_for_testing_.is_null())
+    notify_event_for_testing_.Run(automation_event_type);
 }
 
 void AutomationInternalCustomBindings::MaybeSendFocusAndBlur(
diff --git a/extensions/renderer/api/automation/automation_internal_custom_bindings.h b/extensions/renderer/api/automation/automation_internal_custom_bindings.h
index 826dbd9..86e5795 100644
--- a/extensions/renderer/api/automation/automation_internal_custom_bindings.h
+++ b/extensions/renderer/api/automation/automation_internal_custom_bindings.h
@@ -308,6 +308,9 @@
   // Keeps track of all trees with event listeners.
   std::set<ui::AXTreeID> trees_with_event_listeners_;
 
+  base::RepeatingCallback<void(api::automation::EventType)>
+      notify_event_for_testing_;
+
   base::WeakPtrFactory<AutomationInternalCustomBindings> weak_ptr_factory_{
       this};
 };
diff --git a/extensions/renderer/api/automation/automation_internal_custom_bindings_unittests.cc b/extensions/renderer/api/automation/automation_internal_custom_bindings_unittests.cc
index 422138a..12b5ce5 100644
--- a/extensions/renderer/api/automation/automation_internal_custom_bindings_unittests.cc
+++ b/extensions/renderer/api/automation/automation_internal_custom_bindings_unittests.cc
@@ -4,6 +4,7 @@
 
 #include "extensions/renderer/api/automation/automation_internal_custom_bindings.h"
 
+#include "base/test/bind.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/extension_builder.h"
 #include "extensions/common/extension_messages.h"
@@ -82,6 +83,12 @@
     return automation_internal_bindings_->GetRootsOfChildTree(node);
   }
 
+  void AddAutomationEventCallback(
+      base::RepeatingCallback<void(api::automation::EventType)> callback) {
+    automation_internal_bindings_->notify_event_for_testing_ =
+        std::move(callback);
+  }
+
  private:
   AutomationInternalCustomBindings* automation_internal_bindings_ = nullptr;
 };
@@ -639,4 +646,103 @@
   EXPECT_EQ(3, child_roots[1]->id());
 }
 
+TEST_F(AutomationInternalCustomBindingsTest, FireEventsWithListeners) {
+  // A simple tree.
+  ExtensionMsg_AccessibilityEventBundleParams bundle;
+  bundle.updates.emplace_back();
+  auto& tree_update = bundle.updates.back();
+  tree_update.has_tree_data = true;
+  tree_update.root_id = 1;
+  auto& tree_data = tree_update.tree_data;
+  tree_data.tree_id = ui::AXTreeID::CreateNewAXTreeID();
+  bundle.tree_id = tree_data.tree_id;
+  tree_update.nodes.emplace_back();
+  auto& node_data1 = tree_update.nodes.back();
+  node_data1.id = 1;
+  node_data1.role = ax::mojom::Role::kRootWebArea;
+  node_data1.child_ids.push_back(2);
+  node_data1.relative_bounds.bounds = gfx::RectF(100, 100, 100, 100);
+  tree_update.nodes.emplace_back();
+  auto& node_data2 = tree_update.nodes.back();
+  node_data2.id = 2;
+  node_data2.role = ax::mojom::Role::kButton;
+  node_data2.relative_bounds.bounds = gfx::RectF(0, 0, 200, 200);
+
+  // Add a hook for events from automation.
+  std::vector<api::automation::EventType> events;
+  AddAutomationEventCallback(base::BindLambdaForTesting(
+      [&](api::automation::EventType event) { events.push_back(event); }));
+
+  SendOnAccessibilityEvents(bundle, true /* active profile */);
+
+  // We aren't listening for any events yet, but we should still get one that
+  // gets fired on initial tree creation.
+  ASSERT_EQ(1U, events.size());
+  EXPECT_EQ(api::automation::EVENT_TYPE_NONE, events[0]);
+  events.clear();
+
+  // Remove the root node data and don't update tree data.
+  tree_update.nodes.erase(tree_update.nodes.begin());
+  tree_update.has_tree_data = false;
+
+  // Trigger a role change.
+  tree_update.nodes[0].role = ax::mojom::Role::kSwitch;
+  SendOnAccessibilityEvents(bundle, true /* active profile */);
+
+  // There should be no events since there are no listeners and this isn't the
+  // initial tree.
+  ASSERT_TRUE(events.empty());
+
+  // Add a role change listener and do trigger the role change again.
+  auto* wrapper = GetTreeIDToTreeMap()[tree_data.tree_id].get();
+  auto* tree = wrapper->tree();
+  // The button is id 2.
+  wrapper->EventListenerAdded(api::automation::EVENT_TYPE_ROLECHANGED,
+                              tree->GetFromId(2));
+  tree_update.nodes[0].role = ax::mojom::Role::kButton;
+  SendOnAccessibilityEvents(bundle, true /* active profile */);
+
+  // We should now have exactly one event.
+  ASSERT_EQ(1U, events.size());
+  EXPECT_EQ(api::automation::EVENT_TYPE_ROLECHANGED, events[0]);
+  events.clear();
+
+  // Now, remove the listener and do the same as above.
+  wrapper->EventListenerRemoved(api::automation::EVENT_TYPE_ROLECHANGED,
+                                tree->GetFromId(2));
+  // We have to add another listener to ensure we don't shut down (no event
+  // listeners means this renderer closes).
+  wrapper->EventListenerAdded(api::automation::EVENT_TYPE_LOADCOMPLETE,
+                              tree->GetFromId(1));
+  tree_update.nodes[0].role = ax::mojom::Role::kSwitch;
+  SendOnAccessibilityEvents(bundle, true /* active profile */);
+
+  // We should have no events.
+  ASSERT_TRUE(events.empty());
+
+  // Finally, let's fire a non-generated event on the button, but add the
+  // listener on the root. This will test both non-generated events and
+  // respecting event listeners on ancestors of the target.
+
+  // First, fire the event without the click listener.
+  tree_update.nodes.clear();
+  bundle.events.emplace_back();
+  auto& event = bundle.events.back();
+  event.event_type = ax::mojom::Event::kClicked;
+  event.id = 2;
+  SendOnAccessibilityEvents(bundle, true /* active profile */);
+
+  // No event.
+  ASSERT_TRUE(events.empty());
+
+  // Now, add the click listener to the root, and fire the click event on the
+  // button.
+  wrapper->EventListenerAdded(api::automation::EVENT_TYPE_CLICKED,
+                              tree->GetFromId(1));
+  SendOnAccessibilityEvents(bundle, true /* active profile */);
+
+  ASSERT_EQ(1U, events.size());
+  EXPECT_EQ(api::automation::EVENT_TYPE_CLICKED, events[0]);
+}
+
 }  // namespace extensions
diff --git a/extensions/renderer/programmatic_script_injector.cc b/extensions/renderer/programmatic_script_injector.cc
index fa5d86a..37c6b12 100644
--- a/extensions/renderer/programmatic_script_injector.cc
+++ b/extensions/renderer/programmatic_script_injector.cc
@@ -39,7 +39,8 @@
   return mojom::InjectionType::kProgrammaticScript;
 }
 
-bool ProgrammaticScriptInjector::IsUserGesture() const {
+blink::mojom::UserActivationOption ProgrammaticScriptInjector::IsUserGesture()
+    const {
   DCHECK(params_->injection->is_js());
   return params_->injection->get_js()->user_gesture;
 }
@@ -60,12 +61,14 @@
   return params_->injection->get_css()->operation;
 }
 
-bool ProgrammaticScriptInjector::ExpectsResults() const {
+blink::mojom::WantResultOption ProgrammaticScriptInjector::ExpectsResults()
+    const {
   DCHECK(params_->injection->is_js());
   return params_->injection->get_js()->wants_result;
 }
 
-bool ProgrammaticScriptInjector::ShouldWaitForPromise() const {
+blink::mojom::PromiseResultOption
+ProgrammaticScriptInjector::ShouldWaitForPromise() const {
   DCHECK(params_->injection->is_js());
   return params_->injection->get_js()->wait_for_promise;
 }
diff --git a/extensions/renderer/programmatic_script_injector.h b/extensions/renderer/programmatic_script_injector.h
index 761c8b62..cda5691 100644
--- a/extensions/renderer/programmatic_script_injector.h
+++ b/extensions/renderer/programmatic_script_injector.h
@@ -34,12 +34,12 @@
  private:
   // ScriptInjector implementation.
   mojom::InjectionType script_type() const override;
-  bool IsUserGesture() const override;
+  blink::mojom::UserActivationOption IsUserGesture() const override;
   mojom::ExecutionWorld GetExecutionWorld() const override;
   mojom::CSSOrigin GetCssOrigin() const override;
   mojom::CSSInjection::Operation GetCSSInjectionOperation() const override;
-  bool ExpectsResults() const override;
-  bool ShouldWaitForPromise() const override;
+  blink::mojom::WantResultOption ExpectsResults() const override;
+  blink::mojom::PromiseResultOption ShouldWaitForPromise() const override;
   bool ShouldInjectJs(
       mojom::RunLocation run_location,
       const std::set<std::string>& executing_scripts) const override;
diff --git a/extensions/renderer/script_injection.cc b/extensions/renderer/script_injection.cc
index 621e25e..19c8ff67 100644
--- a/extensions/renderer/script_injection.cc
+++ b/extensions/renderer/script_injection.cc
@@ -327,7 +327,6 @@
   std::vector<blink::WebScriptSource> sources = injector_->GetJsSources(
       run_location_, executing_scripts, num_injected_js_scripts);
   DCHECK(!sources.empty());
-  bool is_user_gesture = injector_->IsUserGesture();
 
   std::unique_ptr<blink::WebScriptExecutionCallback> callback(
       new TimedScriptInjectionCallback(weak_ptr_factory_.GetWeakPtr()));
@@ -345,29 +344,29 @@
       injector_->script_type() == mojom::InjectionType::kContentScript &&
       (run_location_ == mojom::RunLocation::kDocumentEnd ||
        run_location_ == mojom::RunLocation::kDocumentIdle);
-  blink::WebLocalFrame::ScriptExecutionType execution_option =
+  blink::mojom::EvaluationTiming execution_option =
       should_execute_asynchronously
-          ? blink::WebLocalFrame::kAsynchronousBlockingOnload
-          : blink::WebLocalFrame::kSynchronous;
+          ? blink::mojom::EvaluationTiming::kAsynchronous
+          : blink::mojom::EvaluationTiming::kSynchronous;
 
-  mojom::ExecutionWorld execution_world = injector_->GetExecutionWorld();
   int32_t world_id = blink::kMainDOMWorldId;
-  if (execution_world == mojom::ExecutionWorld::kIsolated) {
-    world_id = GetIsolatedWorldIdForInstance(injection_host_.get());
-    if (injection_host_->id().type == mojom::HostID::HostType::kExtensions &&
-        log_activity_) {
-      DOMActivityLogger::AttachToWorld(world_id, injection_host_->id().id);
-    }
-  } else {
-    DCHECK_EQ(mojom::ExecutionWorld::kMain, execution_world);
+  switch (injector_->GetExecutionWorld()) {
+    case mojom::ExecutionWorld::kIsolated:
+      world_id = GetIsolatedWorldIdForInstance(injection_host_.get());
+      if (injection_host_->id().type == mojom::HostID::HostType::kExtensions &&
+          log_activity_) {
+        DOMActivityLogger::AttachToWorld(world_id, injection_host_->id().id);
+      }
+      break;
+    case mojom::ExecutionWorld::kMain:
+      world_id = blink::kMainDOMWorldId;
+      break;
   }
-  auto promise_behavior =
-      injector_->ShouldWaitForPromise()
-          ? blink::WebLocalFrame::PromiseBehavior::kAwait
-          : blink::WebLocalFrame::PromiseBehavior::kDontWait;
   render_frame_->GetWebFrame()->RequestExecuteScript(
-      world_id, sources, is_user_gesture, execution_option, callback.release(),
-      blink::BackForwardCacheAware::kPossiblyDisallow, promise_behavior);
+      world_id, sources, injector_->IsUserGesture(), execution_option,
+      blink::mojom::LoadEventBlockingOption::kBlock, callback.release(),
+      blink::BackForwardCacheAware::kPossiblyDisallow,
+      injector_->ShouldWaitForPromise());
 }
 
 void ScriptInjection::OnJsInjectionCompleted(
@@ -396,8 +395,8 @@
     }
   }
 
-  bool expects_results = injector_->ExpectsResults();
-  if (expects_results) {
+  if (injector_->ExpectsResults() ==
+      blink::mojom::WantResultOption::kWantResult) {
     if (!results.empty() && !results.back().IsEmpty()) {
       // Right now, we only support returning single results (per frame).
       // It's safe to always use the main world context when converting
diff --git a/extensions/renderer/script_injector.h b/extensions/renderer/script_injector.h
index e7c6d85..8abefe47 100644
--- a/extensions/renderer/script_injector.h
+++ b/extensions/renderer/script_injector.h
@@ -50,8 +50,8 @@
   // Returns the script type of this particular injection.
   virtual mojom::InjectionType script_type() const = 0;
 
-  // Returns true if the script is running inside a user gesture.
-  virtual bool IsUserGesture() const = 0;
+  // Returns the associated `UserActivationOption` for script evaluation.
+  virtual blink::mojom::UserActivationOption IsUserGesture() const = 0;
 
   // Returns the world in which to execute the javascript code.
   virtual mojom::ExecutionWorld GetExecutionWorld() const = 0;
@@ -63,11 +63,11 @@
   // performed.
   virtual mojom::CSSInjection::Operation GetCSSInjectionOperation() const = 0;
 
-  // Returns true if the script expects results.
-  virtual bool ExpectsResults() const = 0;
+  // Returns the associated `WantResultOption` for script evaluation.
+  virtual blink::mojom::WantResultOption ExpectsResults() const = 0;
 
-  // Whether to wait for a promise result to resolve.
-  virtual bool ShouldWaitForPromise() const = 0;
+  // Returns the associated `PromiseResultOption` for script evaluation.
+  virtual blink::mojom::PromiseResultOption ShouldWaitForPromise() const = 0;
 
   // Returns true if the script should inject JS source at the given
   // |run_location|.
diff --git a/extensions/renderer/user_script_injector.cc b/extensions/renderer/user_script_injector.cc
index 4d0648d..1ef39cc 100644
--- a/extensions/renderer/user_script_injector.cc
+++ b/extensions/renderer/user_script_injector.cc
@@ -138,20 +138,21 @@
   return mojom::InjectionType::kContentScript;
 }
 
-bool UserScriptInjector::IsUserGesture() const {
-  return false;
+blink::mojom::UserActivationOption UserScriptInjector::IsUserGesture() const {
+  return blink::mojom::UserActivationOption::kDoNotActivate;
 }
 
 mojom::ExecutionWorld UserScriptInjector::GetExecutionWorld() const {
   return script_->execution_world();
 }
 
-bool UserScriptInjector::ExpectsResults() const {
-  return false;
+blink::mojom::WantResultOption UserScriptInjector::ExpectsResults() const {
+  return blink::mojom::WantResultOption::kNoResult;
 }
 
-bool UserScriptInjector::ShouldWaitForPromise() const {
-  return false;
+blink::mojom::PromiseResultOption UserScriptInjector::ShouldWaitForPromise()
+    const {
+  return blink::mojom::PromiseResultOption::kDoNotWait;
 }
 
 mojom::CSSOrigin UserScriptInjector::GetCssOrigin() const {
diff --git a/extensions/renderer/user_script_injector.h b/extensions/renderer/user_script_injector.h
index 5509e5b7..dd7fdebc6 100644
--- a/extensions/renderer/user_script_injector.h
+++ b/extensions/renderer/user_script_injector.h
@@ -45,12 +45,12 @@
 
   // ScriptInjector implementation.
   mojom::InjectionType script_type() const override;
-  bool IsUserGesture() const override;
+  blink::mojom::UserActivationOption IsUserGesture() const override;
   mojom::ExecutionWorld GetExecutionWorld() const override;
   mojom::CSSOrigin GetCssOrigin() const override;
   mojom::CSSInjection::Operation GetCSSInjectionOperation() const override;
-  bool ExpectsResults() const override;
-  bool ShouldWaitForPromise() const override;
+  blink::mojom::WantResultOption ExpectsResults() const override;
+  blink::mojom::PromiseResultOption ShouldWaitForPromise() const override;
   bool ShouldInjectJs(
       mojom::RunLocation run_location,
       const std::set<std::string>& executing_scripts) const override;
diff --git a/fuchsia_web/webengine/browser/web_engine_permission_delegate.cc b/fuchsia_web/webengine/browser/web_engine_permission_delegate.cc
index 2c9bb37..6bc15db3 100644
--- a/fuchsia_web/webengine/browser/web_engine_permission_delegate.cc
+++ b/fuchsia_web/webengine/browser/web_engine_permission_delegate.cc
@@ -70,7 +70,7 @@
   for (const auto& permission : permissions) {
     permission_strings.push_back(
         permissions::PermissionUtil::GetPermissionString(
-            permissions::PermissionUtil::PermissionTypeToContentSetting(
+            permissions::PermissionUtil::PermissionTypeToContentSettingType(
                 permission)));
   }
 
@@ -99,6 +99,17 @@
   return blink::mojom::PermissionStatus::DENIED;
 }
 
+content::PermissionResult
+WebEnginePermissionDelegate::GetPermissionResultForOriginWithoutContext(
+    blink::PermissionType permission,
+    const url::Origin& origin) {
+  blink::mojom::PermissionStatus status =
+      GetPermissionStatus(permission, origin.GetURL(), origin.GetURL());
+
+  return content::PermissionResult(
+      status, content::PermissionStatusSource::UNSPECIFIED);
+}
+
 blink::mojom::PermissionStatus
 WebEnginePermissionDelegate::GetPermissionStatusForCurrentDocument(
     blink::PermissionType permission,
diff --git a/fuchsia_web/webengine/browser/web_engine_permission_delegate.h b/fuchsia_web/webengine/browser/web_engine_permission_delegate.h
index 7c034b3..639f11d0 100644
--- a/fuchsia_web/webengine/browser/web_engine_permission_delegate.h
+++ b/fuchsia_web/webengine/browser/web_engine_permission_delegate.h
@@ -6,6 +6,7 @@
 #define FUCHSIA_WEB_WEBENGINE_BROWSER_WEB_ENGINE_PERMISSION_DELEGATE_H_
 
 #include "content/public/browser/permission_controller_delegate.h"
+#include "content/public/browser/permission_result.h"
 
 namespace blink {
 enum class PermissionType;
@@ -53,6 +54,9 @@
       blink::PermissionType permission,
       const GURL& requesting_origin,
       const GURL& embedding_origin) override;
+  content::PermissionResult GetPermissionResultForOriginWithoutContext(
+      blink::PermissionType permission,
+      const url::Origin& origin) override;
   blink::mojom::PermissionStatus GetPermissionStatusForCurrentDocument(
       blink::PermissionType permission,
       content::RenderFrameHost* render_frame_host) override;
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
index ff99584..4724174 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
@@ -2846,8 +2846,7 @@
   TRACE_EVENT_NESTABLE_ASYNC_END0(
       "cc", "GLES2DecoderPassthroughImpl::DescheduleUntilFinished",
       TRACE_ID_LOCAL(this));
-  deschedule_until_finished_fences_.erase(
-      deschedule_until_finished_fences_.begin());
+  deschedule_until_finished_fences_.pop_front();
   client()->OnRescheduleAfterFinished();
 }
 
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.h b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.h
index 6d48a0d..668711f 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.h
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.h
@@ -9,11 +9,11 @@
 
 #include <algorithm>
 #include <memory>
-#include <set>
-#include <unordered_map>
 #include <vector>
 
 #include "base/containers/circular_deque.h"
+#include "base/containers/flat_map.h"
+#include "base/containers/flat_set.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/ref_counted.h"
 #include "gpu/command_buffer/common/debug_marker_manager.h"
@@ -141,7 +141,7 @@
 
   // Mapping of client buffer IDs that are mapped to the shared memory used to
   // back the mapping so that it can be flushed when the buffer is unmapped
-  std::unordered_map<GLuint, MappedBuffer> mapped_buffer_map;
+  base::flat_map<GLuint, MappedBuffer> mapped_buffer_map;
 };
 
 class GPU_GLES2_EXPORT GLES2DecoderPassthroughImpl
@@ -655,7 +655,7 @@
   std::vector<TexturePendingBinding> textures_pending_binding_;
 
   // State tracking of currently bound buffers
-  std::unordered_map<GLenum, GLuint> bound_buffers_;
+  base::flat_map<GLenum, GLuint> bound_buffers_;
   // Lazy tracking of the bound element array buffer when changing VAOs.
   bool bound_element_array_buffer_dirty_;
 
@@ -663,7 +663,7 @@
   struct QueryInfo {
     GLenum type = GL_NONE;
   };
-  std::unordered_map<GLuint, QueryInfo> query_info_map_;
+  base::flat_map<GLuint, QueryInfo> query_info_map_;
 
   // All queries that are waiting for their results to be ready
   struct PendingQuery {
@@ -711,7 +711,7 @@
     base::TimeTicks command_processing_start_time;
     base::TimeDelta active_time;
   };
-  std::unordered_map<GLenum, ActiveQuery> active_queries_;
+  base::flat_map<GLenum, ActiveQuery> active_queries_;
 
   // Pending async ReadPixels calls
   struct PendingReadPixels {
@@ -757,7 +757,7 @@
   BufferShadowUpdateMap buffer_shadow_updates_;
 
   // Error state
-  std::set<GLenum> errors_;
+  base::flat_set<GLenum> errors_;
 
   // Checks if an error has been generated since the last call to
   // CheckErrorCallbackState
@@ -879,7 +879,8 @@
   // After a second fence is inserted, both the GpuChannelMessageQueue and
   // CommandExecutor are descheduled. Once the first fence has completed, both
   // get rescheduled.
-  std::vector<std::unique_ptr<gl::GLFence>> deschedule_until_finished_fences_;
+  base::circular_deque<std::unique_ptr<gl::GLFence>>
+      deschedule_until_finished_fences_;
 
   GLuint linking_program_service_id_ = 0u;
 
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc
index d962cbb..3f404a8c8 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough_doers.cc
@@ -4647,8 +4647,7 @@
 
   DCHECK_EQ(2u, deschedule_until_finished_fences_.size());
   if (deschedule_until_finished_fences_[0]->HasCompleted()) {
-    deschedule_until_finished_fences_.erase(
-        deschedule_until_finished_fences_.begin());
+    deschedule_until_finished_fences_.pop_front();
     return error::kNoError;
   }
 
diff --git a/gpu/command_buffer/service/shared_image/angle_vulkan_image_backing_factory.cc b/gpu/command_buffer/service/shared_image/angle_vulkan_image_backing_factory.cc
index 2487a39..84a6495 100644
--- a/gpu/command_buffer/service/shared_image/angle_vulkan_image_backing_factory.cc
+++ b/gpu/command_buffer/service/shared_image/angle_vulkan_image_backing_factory.cc
@@ -41,9 +41,6 @@
   return estimated_size;
 }
 
-using ScopedResetAndRestoreUnpackState =
-    GLTextureImageBackingHelper::ScopedResetAndRestoreUnpackState;
-
 using ScopedRestoreTexture = GLTextureImageBackingHelper::ScopedRestoreTexture;
 
 class AngleVulkanImageBacking : public ClearTrackingSharedImageBacking,
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 b14dde0..83555476 100644
--- a/gpu/command_buffer/service/shared_image/egl_image_backing.cc
+++ b/gpu/command_buffer/service/shared_image/egl_image_backing.cc
@@ -186,7 +186,6 @@
     size_t estimated_size,
     const GLCommonImageBackingFactory::FormatInfo format_info,
     const GpuDriverBugWorkarounds& workarounds,
-    const GLTextureImageBackingHelper::UnpackStateAttribs& attribs,
     bool use_passthrough,
     base::span<const uint8_t> pixel_data)
     : ClearTrackingSharedImageBacking(mailbox,
@@ -199,7 +198,6 @@
                                       estimated_size,
                                       true /*is_thread_safe*/),
       format_info_(format_info),
-      gl_unpack_attribs_(attribs),
       use_passthrough_(use_passthrough) {
   created_on_context_ = gl::g_current_gl_context;
   // On some GPUs (NVidia) keeping reference to egl image itself is not enough,
@@ -405,21 +403,20 @@
 
         if (!pixel_data.empty()) {
           GLTextureImageBackingHelper::ScopedResetAndRestoreUnpackState
-              scoped_unpack_state(api, gl_unpack_attribs_,
-                                  true /* uploading_data */);
+              scoped_unpack_state(/*uploading_data=*/true);
           api->glTexSubImage2DFn(target, 0, 0, 0, size().width(),
                                  size().height(), format_info_.adjusted_format,
                                  format_info_.gl_type, pixel_data.data());
         }
       } else if (format_info_.is_compressed) {
         GLTextureImageBackingHelper::ScopedResetAndRestoreUnpackState
-            scoped_unpack_state(api, gl_unpack_attribs_, !pixel_data.empty());
+            scoped_unpack_state(!pixel_data.empty());
         api->glCompressedTexImage2DFn(
             target, 0, format_info_.image_internal_format, size().width(),
             size().height(), 0, pixel_data.size(), pixel_data.data());
       } else {
         GLTextureImageBackingHelper::ScopedResetAndRestoreUnpackState
-            scoped_unpack_state(api, gl_unpack_attribs_, !pixel_data.empty());
+            scoped_unpack_state(!pixel_data.empty());
 
         api->glTexImage2DFn(target, 0, format_info_.image_internal_format,
                             size().width(), size().height(), 0,
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 e2d2902..b4b82ba 100644
--- a/gpu/command_buffer/service/shared_image/egl_image_backing.h
+++ b/gpu/command_buffer/service/shared_image/egl_image_backing.h
@@ -36,20 +36,18 @@
 // group. This is achieved by using locks and fences for proper synchronization.
 class EGLImageBacking : public ClearTrackingSharedImageBacking {
  public:
-  EGLImageBacking(
-      const Mailbox& mailbox,
-      viz::ResourceFormat format,
-      const gfx::Size& size,
-      const gfx::ColorSpace& color_space,
-      GrSurfaceOrigin surface_origin,
-      SkAlphaType alpha_type,
-      uint32_t usage,
-      size_t estimated_size,
-      const GLCommonImageBackingFactory::FormatInfo format_into,
-      const GpuDriverBugWorkarounds& workarounds,
-      const GLTextureImageBackingHelper::UnpackStateAttribs& attribs,
-      bool use_passthrough,
-      base::span<const uint8_t> pixel_data);
+  EGLImageBacking(const Mailbox& mailbox,
+                  viz::ResourceFormat format,
+                  const gfx::Size& size,
+                  const gfx::ColorSpace& color_space,
+                  GrSurfaceOrigin surface_origin,
+                  SkAlphaType alpha_type,
+                  uint32_t usage,
+                  size_t estimated_size,
+                  const GLCommonImageBackingFactory::FormatInfo format_into,
+                  const GpuDriverBugWorkarounds& workarounds,
+                  bool use_passthrough,
+                  base::span<const uint8_t> pixel_data);
 
   EGLImageBacking(const EGLImageBacking&) = delete;
   EGLImageBacking& operator=(const EGLImageBacking&) = delete;
@@ -122,7 +120,6 @@
   base::flat_set<const GLRepresentationShared*> active_readers_
       GUARDED_BY(lock_);
 
-  const GLTextureImageBackingHelper::UnpackStateAttribs gl_unpack_attribs_;
   const bool use_passthrough_;
 };
 
diff --git a/gpu/command_buffer/service/shared_image/egl_image_backing_factory.cc b/gpu/command_buffer/service/shared_image/egl_image_backing_factory.cc
index c269777..d2c0020e 100644
--- a/gpu/command_buffer/service/shared_image/egl_image_backing_factory.cc
+++ b/gpu/command_buffer/service/shared_image/egl_image_backing_factory.cc
@@ -137,8 +137,7 @@
 
   return std::make_unique<EGLImageBacking>(
       mailbox, format, size, color_space, surface_origin, alpha_type, usage,
-      estimated_size, format_info, workarounds_, attribs_, use_passthrough_,
-      pixel_data);
+      estimated_size, format_info, workarounds_, use_passthrough_, pixel_data);
 }
 
 }  // namespace gpu
diff --git a/gpu/command_buffer/service/shared_image/gl_common_image_backing_factory.cc b/gpu/command_buffer/service/shared_image/gl_common_image_backing_factory.cc
index 606ff3f..56fe64e1 100644
--- a/gpu/command_buffer/service/shared_image/gl_common_image_backing_factory.cc
+++ b/gpu/command_buffer/service/shared_image/gl_common_image_backing_factory.cc
@@ -38,13 +38,6 @@
   max_texture_size_ = std::min(max_texture_size_, INT_MAX - 1);
 
   texture_usage_angle_ = feature_info->feature_flags().angle_texture_usage;
-  attribs_.es3_capable = feature_info->IsES3Capable();
-  attribs_.desktop_gl = !feature_info->gl_version_info().is_es;
-  // Can't use the value from feature_info, as we unconditionally enable this
-  // extension, and assume it can't be used if PBOs are not used (which isn't
-  // true for Skia used directly against GL).
-  attribs_.supports_unpack_subimage =
-      gl::g_current_gl_driver->ext.b_GL_EXT_unpack_subimage;
   bool enable_texture_storage =
       feature_info->feature_flags().ext_texture_storage;
   const gles2::Validators* validators = feature_info->validators();
diff --git a/gpu/command_buffer/service/shared_image/gl_common_image_backing_factory.h b/gpu/command_buffer/service/shared_image/gl_common_image_backing_factory.h
index 2273649..039ae48c 100644
--- a/gpu/command_buffer/service/shared_image/gl_common_image_backing_factory.h
+++ b/gpu/command_buffer/service/shared_image/gl_common_image_backing_factory.h
@@ -79,7 +79,6 @@
   FormatInfo format_info_[viz::RESOURCE_FORMAT_MAX + 1];
   int32_t max_texture_size_ = 0;
   bool texture_usage_angle_ = false;
-  GLTextureImageBackingHelper::UnpackStateAttribs attribs_;
   GpuDriverBugWorkarounds workarounds_;
   WebGPUAdapterName use_webgpu_adapter_ = WebGPUAdapterName::kDefault;
 
diff --git a/gpu/command_buffer/service/shared_image/gl_image_backing.cc b/gpu/command_buffer/service/shared_image/gl_image_backing.cc
index fa4ae16..2b8605b 100644
--- a/gpu/command_buffer/service/shared_image/gl_image_backing.cc
+++ b/gpu/command_buffer/service/shared_image/gl_image_backing.cc
@@ -37,8 +37,6 @@
   return estimated_size;
 }
 
-using UnpackStateAttribs = GLTextureImageBackingHelper::UnpackStateAttribs;
-
 using ScopedResetAndRestoreUnpackState =
     GLTextureImageBackingHelper::ScopedResetAndRestoreUnpackState;
 
@@ -317,11 +315,10 @@
   // one param.
   InitializeGLTextureParams params;
   params.target = texture_target;
-  UnpackStateAttribs attribs;
 
   auto shared_image = std::make_unique<GLImageBacking>(
       std::move(image), mailbox, format, size, color_space, surface_origin,
-      alpha_type, usage, params, attribs, true);
+      alpha_type, usage, params, true);
 
   shared_image->passthrough_texture_ = std::move(wrapped_gl_texture);
   shared_image->gl_texture_retained_for_legacy_mailbox_ = true;
@@ -340,7 +337,6 @@
                                SkAlphaType alpha_type,
                                uint32_t usage,
                                const InitializeGLTextureParams& params,
-                               const UnpackStateAttribs& attribs,
                                bool is_passthrough)
     : SharedImageBacking(mailbox,
                          format,
@@ -353,7 +349,6 @@
                          false /* is_thread_safe */),
       image_(image),
       gl_params_(params),
-      gl_unpack_attribs_(attribs),
       is_passthrough_(is_passthrough),
       cleared_rect_(params.is_cleared ? gfx::Rect(size) : gfx::Rect()),
       weak_factory_(this) {
@@ -789,9 +784,8 @@
     }
     new_state = gles2::Texture::BOUND;
   } else {
-    ScopedResetAndRestoreUnpackState scoped_unpack_state(api,
-                                                         gl_unpack_attribs_,
-                                                         /*upload=*/true);
+    ScopedResetAndRestoreUnpackState scoped_unpack_state(
+        /*uploading_data=*/true);
     if (!image_->CopyTexImage(target)) {
       LOG(ERROR) << "Failed to copy GLImage to target";
       return false;
@@ -825,7 +819,7 @@
   ScopedRestoreTexture scoped_restore(api, target);
   api->glBindTextureFn(target, GetGLServiceId());
   ScopedResetAndRestoreUnpackState scoped_unpack_state(
-      api, gl_unpack_attribs_, true /* uploading_data */);
+      /*uploading_data=*/true);
   api->glTexSubImage2DFn(target, 0, 0, 0, size().width(), size().height(),
                          format, type, data);
   ReleaseGLTexture(true /* have_context */);
diff --git a/gpu/command_buffer/service/shared_image/gl_image_backing.h b/gpu/command_buffer/service/shared_image/gl_image_backing.h
index 9e01970..d179099 100644
--- a/gpu/command_buffer/service/shared_image/gl_image_backing.h
+++ b/gpu/command_buffer/service/shared_image/gl_image_backing.h
@@ -187,7 +187,6 @@
       SkAlphaType alpha_type,
       uint32_t usage,
       const GLTextureImageBackingHelper::InitializeGLTextureParams& params,
-      const GLTextureImageBackingHelper::UnpackStateAttribs& attribs,
       bool is_passthrough);
   GLImageBacking(const GLImageBacking& other) = delete;
   GLImageBacking& operator=(const GLImageBacking& other) = delete;
@@ -257,7 +256,6 @@
   bool gl_texture_retained_for_legacy_mailbox_ = false;
 
   const GLTextureImageBackingHelper::InitializeGLTextureParams gl_params_;
-  const GLTextureImageBackingHelper::UnpackStateAttribs gl_unpack_attribs_;
   const bool is_passthrough_;
 
   // This is the cleared rect used by ClearedRect and SetClearedRect when
diff --git a/gpu/command_buffer/service/shared_image/gl_image_backing_factory.cc b/gpu/command_buffer/service/shared_image/gl_image_backing_factory.cc
index 544fceb9..ce485b2 100644
--- a/gpu/command_buffer/service/shared_image/gl_image_backing_factory.cc
+++ b/gpu/command_buffer/service/shared_image/gl_image_backing_factory.cc
@@ -210,7 +210,7 @@
       for_framebuffer_attachment && texture_usage_angle_;
   return std::make_unique<GLImageBacking>(
       image, mailbox, plane_format, plane_size, color_space, surface_origin,
-      alpha_type, usage, params, attribs_, use_passthrough_);
+      alpha_type, usage, params, use_passthrough_);
 }
 
 scoped_refptr<gl::GLImage> GLImageBackingFactory::MakeGLImage(
@@ -224,8 +224,6 @@
   if (handle.type == gfx::SHARED_MEMORY_BUFFER) {
     if (plane != gfx::BufferPlane::DEFAULT)
       return nullptr;
-    if (!base::IsValueInRangeForNumericType<size_t>(handle.stride))
-      return nullptr;
     auto image = base::MakeRefCounted<gl::GLImageSharedMemory>(size);
     if (color_space.IsValid())
       image->SetColorSpace(color_space);
@@ -371,7 +369,7 @@
   DCHECK(!format_info.swizzle);
   auto result = std::make_unique<GLImageBacking>(
       image, mailbox, format, size, color_space, surface_origin, alpha_type,
-      usage, params, attribs_, use_passthrough_);
+      usage, params, use_passthrough_);
   if (!pixel_data.empty()) {
     gl::ScopedProgressReporter scoped_progress_reporter(progress_reporter_);
     result->InitializePixels(format_info.adjusted_format, format_info.gl_type,
diff --git a/gpu/command_buffer/service/shared_image/gl_image_backing_factory_unittest.cc b/gpu/command_buffer/service/shared_image/gl_image_backing_factory_unittest.cc
index 5b0d4292..cce492b 100644
--- a/gpu/command_buffer/service/shared_image/gl_image_backing_factory_unittest.cc
+++ b/gpu/command_buffer/service/shared_image/gl_image_backing_factory_unittest.cc
@@ -767,7 +767,7 @@
   handle.region = base::UnsafeSharedMemoryRegion::Create(shm_size);
   ASSERT_TRUE(handle.region.IsValid());
   handle.offset = 0;
-  handle.stride = static_cast<int32_t>(
+  handle.stride = static_cast<uint32_t>(
       gfx::RowSizeForBufferFormat(size.width(), format, 0));
 
   auto backing = backing_factory_shmem_->CreateSharedImage(
diff --git a/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory.cc b/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory.cc
index 623232d..a809737 100644
--- a/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory.cc
+++ b/gpu/command_buffer/service/shared_image/gl_texture_image_backing_factory.cc
@@ -217,22 +217,20 @@
 
     if (!pixel_data.empty()) {
       ScopedResetAndRestoreUnpackState scoped_unpack_state(
-          api, attribs_, true /* uploading_data */);
+          /*uploading_data=*/true);
       gl::ScopedProgressReporter scoped_progress_reporter(progress_reporter_);
       api->glTexSubImage2DFn(target, 0, 0, 0, size.width(), size.height(),
                              format_info.adjusted_format, format_info.gl_type,
                              pixel_data.data());
     }
   } else if (format_info.is_compressed) {
-    ScopedResetAndRestoreUnpackState scoped_unpack_state(api, attribs_,
-                                                         !pixel_data.empty());
+    ScopedResetAndRestoreUnpackState scoped_unpack_state(!pixel_data.empty());
     gl::ScopedProgressReporter scoped_progress_reporter(progress_reporter_);
     api->glCompressedTexImage2DFn(target, 0, format_info.image_internal_format,
                                   size.width(), size.height(), 0,
                                   pixel_data.size(), pixel_data.data());
   } else {
-    ScopedResetAndRestoreUnpackState scoped_unpack_state(api, attribs_,
-                                                         !pixel_data.empty());
+    ScopedResetAndRestoreUnpackState scoped_unpack_state(!pixel_data.empty());
     gl::ScopedProgressReporter scoped_progress_reporter(progress_reporter_);
     api->glTexImage2DFn(target, 0, format_info.image_internal_format,
                         size.width(), size.height(), 0,
diff --git a/gpu/command_buffer/service/shared_image/gl_texture_image_backing_helper.cc b/gpu/command_buffer/service/shared_image/gl_texture_image_backing_helper.cc
index e3746c5..0844fce 100644
--- a/gpu/command_buffer/service/shared_image/gl_texture_image_backing_helper.cc
+++ b/gpu/command_buffer/service/shared_image/gl_texture_image_backing_helper.cc
@@ -10,16 +10,18 @@
 #include "gpu/command_buffer/service/gl_utils.h"
 #include "gpu/command_buffer/service/shared_context_state.h"
 #include "gpu/command_buffer/service/shared_image/shared_image_factory.h"
+#include "ui/gl/gl_context.h"
 #include "ui/gl/gl_gl_api_implementation.h"
+#include "ui/gl/gl_version_info.h"
 
 namespace gpu {
 
 GLTextureImageBackingHelper::ScopedResetAndRestoreUnpackState::
-    ScopedResetAndRestoreUnpackState(gl::GLApi* api,
-                                     const UnpackStateAttribs& attribs,
-                                     bool uploading_data)
-    : api_(api) {
-  if (attribs.es3_capable) {
+    ScopedResetAndRestoreUnpackState(bool uploading_data)
+    : api_(gl::g_current_gl_context) {
+  const auto* version_info = gl::g_current_gl_version;
+
+  if (version_info->is_es3_capable) {
     // Need to unbind any GL_PIXEL_UNPACK_BUFFER for the nullptr in
     // glTexImage2D to mean "no pixels" (as opposed to offset 0 in the
     // buffer).
@@ -32,7 +34,8 @@
     if (unpack_alignment_ != 4)
       api_->glPixelStoreiFn(GL_UNPACK_ALIGNMENT, 4);
 
-    if (attribs.es3_capable || attribs.supports_unpack_subimage) {
+    if (version_info->is_es3_capable ||
+        gl::g_current_gl_driver->ext.b_GL_EXT_unpack_subimage) {
       api_->glGetIntegervFn(GL_UNPACK_ROW_LENGTH, &unpack_row_length_);
       if (unpack_row_length_)
         api_->glPixelStoreiFn(GL_UNPACK_ROW_LENGTH, 0);
@@ -44,7 +47,7 @@
         api_->glPixelStoreiFn(GL_UNPACK_SKIP_PIXELS, 0);
     }
 
-    if (attribs.es3_capable) {
+    if (version_info->is_es3_capable) {
       api_->glGetIntegervFn(GL_UNPACK_SKIP_IMAGES, &unpack_skip_images_);
       if (unpack_skip_images_)
         api_->glPixelStoreiFn(GL_UNPACK_SKIP_IMAGES, 0);
@@ -53,13 +56,13 @@
         api_->glPixelStoreiFn(GL_UNPACK_IMAGE_HEIGHT, 0);
     }
 
-    if (attribs.desktop_gl) {
-      api->glGetBooleanvFn(GL_UNPACK_SWAP_BYTES, &unpack_swap_bytes_);
+    if (!version_info->is_es) {
+      api_->glGetBooleanvFn(GL_UNPACK_SWAP_BYTES, &unpack_swap_bytes_);
       if (unpack_swap_bytes_)
-        api->glPixelStoreiFn(GL_UNPACK_SWAP_BYTES, GL_FALSE);
-      api->glGetBooleanvFn(GL_UNPACK_LSB_FIRST, &unpack_lsb_first_);
+        api_->glPixelStoreiFn(GL_UNPACK_SWAP_BYTES, GL_FALSE);
+      api_->glGetBooleanvFn(GL_UNPACK_LSB_FIRST, &unpack_lsb_first_);
       if (unpack_lsb_first_)
-        api->glPixelStoreiFn(GL_UNPACK_LSB_FIRST, GL_FALSE);
+        api_->glPixelStoreiFn(GL_UNPACK_LSB_FIRST, GL_FALSE);
     }
   }
 }
diff --git a/gpu/command_buffer/service/shared_image/gl_texture_image_backing_helper.h b/gpu/command_buffer/service/shared_image/gl_texture_image_backing_helper.h
index 4498aec..36e08ae 100644
--- a/gpu/command_buffer/service/shared_image/gl_texture_image_backing_helper.h
+++ b/gpu/command_buffer/service/shared_image/gl_texture_image_backing_helper.h
@@ -27,19 +27,10 @@
     bool has_immutable_storage = false;
   };
 
-  // Attributes needed to know what state to restore for GL upload and copy.
-  struct UnpackStateAttribs {
-    bool es3_capable = false;
-    bool desktop_gl = false;
-    bool supports_unpack_subimage = false;
-  };
-
   // Object used to restore state around GL upload and copy.
   class ScopedResetAndRestoreUnpackState {
    public:
-    ScopedResetAndRestoreUnpackState(gl::GLApi* api,
-                                     const UnpackStateAttribs& attribs,
-                                     bool uploading_data);
+    explicit ScopedResetAndRestoreUnpackState(bool uploading_data);
 
     ScopedResetAndRestoreUnpackState(const ScopedResetAndRestoreUnpackState&) =
         delete;
diff --git a/gpu/command_buffer/service/shared_memory_region_wrapper.cc b/gpu/command_buffer/service/shared_memory_region_wrapper.cc
index 85644fbf..20e3c64 100644
--- a/gpu/command_buffer/service/shared_memory_region_wrapper.cc
+++ b/gpu/command_buffer/service/shared_memory_region_wrapper.cc
@@ -17,11 +17,11 @@
 // Validate that |stride| will work for pixels with |size| and |format|.
 bool ValidateStride(const gfx::Size size,
                     viz::ResourceFormat format,
-                    int32_t stride) {
+                    uint32_t stride) {
   if (!base::IsValueInRangeForNumericType<size_t>(stride))
     return false;
 
-  int32_t min_width_in_bytes = 0;
+  uint32_t min_width_in_bytes = 0;
   if (!viz::ResourceSizes::MaybeWidthInBytes(size.width(), format,
                                              &min_width_in_bytes)) {
     return false;
diff --git a/gpu/ipc/common/gpu_memory_buffer_impl_shared_memory.cc b/gpu/ipc/common/gpu_memory_buffer_impl_shared_memory.cc
index 7b48187..e0c025d9 100644
--- a/gpu/ipc/common/gpu_memory_buffer_impl_shared_memory.cc
+++ b/gpu/ipc/common/gpu_memory_buffer_impl_shared_memory.cc
@@ -30,7 +30,7 @@
     base::UnsafeSharedMemoryRegion shared_memory_region,
     base::WritableSharedMemoryMapping shared_memory_mapping,
     size_t offset,
-    int stride)
+    uint32_t stride)
     : GpuMemoryBufferImpl(id, size, format, std::move(callback)),
       shared_memory_region_(std::move(shared_memory_region)),
       shared_memory_mapping_(std::move(shared_memory_mapping)),
@@ -89,7 +89,7 @@
   handle.type = gfx::SHARED_MEMORY_BUFFER;
   handle.id = id;
   handle.offset = 0;
-  handle.stride = static_cast<int32_t>(
+  handle.stride = static_cast<uint32_t>(
       gfx::RowSizeForBufferFormat(size.width(), format, 0));
   handle.region = std::move(shared_memory_region);
   return handle;
@@ -114,19 +114,18 @@
   size_t min_buffer_size = 0;
 
   if (gfx::NumberOfPlanesForLinearBufferFormat(format) == 1) {
-    if (static_cast<size_t>(handle.stride) < minimum_stride)
+    if (handle.stride < minimum_stride)
       return nullptr;
 
-    base::CheckedNumeric<size_t> checked_min_buffer_size =
-        base::MakeCheckedNum(handle.stride) *
-            (base::MakeCheckedNum(size.height()) - 1) +
-        minimum_stride;
+    base::CheckedNumeric<size_t> checked_min_buffer_size = handle.stride;
+    checked_min_buffer_size *= size.height() - 1;
+    checked_min_buffer_size += minimum_stride;
     if (!checked_min_buffer_size.AssignIfValid(&min_buffer_size))
       return nullptr;
   } else {
     // Custom layout (i.e. non-standard stride) is not allowed for multi-plane
     // formats.
-    if (static_cast<size_t>(handle.stride) != minimum_stride)
+    if (handle.stride != minimum_stride)
       return nullptr;
 
     if (!gfx::BufferSizeForBufferFormatChecked(size, format,
diff --git a/gpu/ipc/common/gpu_memory_buffer_impl_shared_memory.h b/gpu/ipc/common/gpu_memory_buffer_impl_shared_memory.h
index 31a7d46..add325e 100644
--- a/gpu/ipc/common/gpu_memory_buffer_impl_shared_memory.h
+++ b/gpu/ipc/common/gpu_memory_buffer_impl_shared_memory.h
@@ -85,12 +85,12 @@
       base::UnsafeSharedMemoryRegion shared_memory_region,
       base::WritableSharedMemoryMapping shared_memory_mapping,
       size_t offset,
-      int stride);
+      uint32_t stride);
 
   base::UnsafeSharedMemoryRegion shared_memory_region_;
   base::WritableSharedMemoryMapping shared_memory_mapping_;
   size_t offset_;
-  int stride_;
+  uint32_t stride_;
 };
 
 }  // namespace gpu
diff --git a/gpu/ipc/service/dcomp_texture_win.cc b/gpu/ipc/service/dcomp_texture_win.cc
index cbe350c..515916a 100644
--- a/gpu/ipc/service/dcomp_texture_win.cc
+++ b/gpu/ipc/service/dcomp_texture_win.cc
@@ -45,7 +45,6 @@
 
 using InitializeGLTextureParams =
     GLTextureImageBackingHelper::InitializeGLTextureParams;
-using UnpackStateAttribs = GLTextureImageBackingHelper::UnpackStateAttribs;
 
 }  // namespace
 
@@ -170,11 +169,10 @@
   params.is_cleared = true;
   params.is_rgb_emulation = false;
   params.framebuffer_attachment_angle = false;
-  UnpackStateAttribs attribs;
   auto shared_image = std::make_unique<GLImageBacking>(
       this, mailbox, viz::BGRA_8888, GetSize(), gfx::ColorSpace::CreateSRGB(),
       kTopLeft_GrSurfaceOrigin, kPremul_SkAlphaType,
-      /*usage=*/SHARED_IMAGE_USAGE_DISPLAY, params, attribs,
+      /*usage=*/SHARED_IMAGE_USAGE_DISPLAY, params,
       /*use_passthrough=*/true);
 
   channel_->shared_image_stub()->factory()->RegisterBacking(
diff --git a/gpu/ipc/service/gpu_channel.cc b/gpu/ipc/service/gpu_channel.cc
index 6030d8e..166b8ac 100644
--- a/gpu/ipc/service/gpu_channel.cc
+++ b/gpu/ipc/service/gpu_channel.cc
@@ -1066,8 +1066,6 @@
     case gfx::SHARED_MEMORY_BUFFER: {
       if (plane != gfx::BufferPlane::DEFAULT)
         return nullptr;
-      if (!base::IsValueInRangeForNumericType<size_t>(handle.stride))
-        return nullptr;
       auto image = base::MakeRefCounted<gl::GLImageSharedMemory>(size);
       if (!image->Initialize(handle.region, handle.id, format, handle.offset,
                              handle.stride)) {
diff --git a/headless/lib/browser/headless_permission_manager.cc b/headless/lib/browser/headless_permission_manager.cc
index e5c0ecd..c4616db 100644
--- a/headless/lib/browser/headless_permission_manager.cc
+++ b/headless/lib/browser/headless_permission_manager.cc
@@ -7,7 +7,9 @@
 #include "base/callback.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/permission_controller.h"
+#include "content/public/browser/permission_result.h"
 #include "third_party/blink/public/common/permissions/permission_utils.h"
+#include "url/gurl.h"
 
 namespace headless {
 
@@ -74,6 +76,17 @@
   return blink::mojom::PermissionStatus::ASK;
 }
 
+content::PermissionResult
+HeadlessPermissionManager::GetPermissionResultForOriginWithoutContext(
+    blink::PermissionType permission,
+    const url::Origin& origin) {
+  blink::mojom::PermissionStatus status =
+      GetPermissionStatus(permission, origin.GetURL(), origin.GetURL());
+
+  return content::PermissionResult(
+      status, content::PermissionStatusSource::UNSPECIFIED);
+}
+
 blink::mojom::PermissionStatus
 HeadlessPermissionManager::GetPermissionStatusForCurrentDocument(
     blink::PermissionType permission,
diff --git a/headless/lib/browser/headless_permission_manager.h b/headless/lib/browser/headless_permission_manager.h
index ed91c294..f5f1045 100644
--- a/headless/lib/browser/headless_permission_manager.h
+++ b/headless/lib/browser/headless_permission_manager.h
@@ -15,6 +15,7 @@
 
 namespace content {
 class BrowserContext;
+struct PermissionResult;
 }
 
 namespace headless {
@@ -59,6 +60,9 @@
       blink::PermissionType permission,
       const GURL& requesting_origin,
       const GURL& embedding_origin) override;
+  content::PermissionResult GetPermissionResultForOriginWithoutContext(
+      blink::PermissionType permission,
+      const url::Origin& origin) override;
   blink::mojom::PermissionStatus GetPermissionStatusForCurrentDocument(
       blink::PermissionType permission,
       content::RenderFrameHost* render_frame_host) override;
diff --git a/ios/chrome/browser/autofill/form_structure_browsertest.mm b/ios/chrome/browser/autofill/form_structure_browsertest.mm
index c8eaccd..8b739f3 100644
--- a/ios/chrome/browser/autofill/form_structure_browsertest.mm
+++ b/ios/chrome/browser/autofill/form_structure_browsertest.mm
@@ -198,7 +198,9 @@
        // TODO(crbug.com/1190334): Remove once launched.
        autofill::features::kAutofillParseMerchantPromoCodeFields,
        // TODO(crbug.com/1113970): Remove once launched.
-       features::kAutofillSectionUponRedundantNameInfo},
+       features::kAutofillSectionUponRedundantNameInfo,
+       // TODO(crbug.com/1335549): Remove once launched.
+       autofill::features::kAutofillParseIBANFields},
       // Disabled
       {});
 }
diff --git a/ios/chrome/browser/link_to_text/BUILD.gn b/ios/chrome/browser/link_to_text/BUILD.gn
index 9c4b5dd..e3881faf 100644
--- a/ios/chrome/browser/link_to_text/BUILD.gn
+++ b/ios/chrome/browser/link_to_text/BUILD.gn
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//ios/web/public/js_messaging/compile_ts.gni")
 import("//ios/web/public/js_messaging/optimize_js.gni")
 
 source_set("link_to_text") {
@@ -68,13 +69,23 @@
   ]
 }
 
-optimize_js("link_to_text_js") {
-  visibility = [ ":link_to_text" ]
-
-  primary_script = "resources/link_to_text.js"
+compile_ts("compile_link_to_text_js") {
+  allow_js = true
   sources = [
+    "//ios/web/public/js_messaging/resources/gcrweb.ts",
     "//third_party/text-fragments-polyfill/src/src/fragment-generation-utils.js",
     "//third_party/text-fragments-polyfill/src/src/text-fragment-utils.js",
-    "resources/link_to_text.js",
+    "resources/link_to_text.ts",
   ]
 }
+
+optimize_js("link_to_text_js") {
+  visibility = [ ":link_to_text" ]
+  _script = filter_include(get_target_outputs(":compile_link_to_text_js"),
+                           [ "*link_to_text.js" ])
+  primary_script = _script[0]
+
+  sources = _script
+
+  deps = [ ":compile_link_to_text_js" ]
+}
diff --git a/ios/chrome/browser/link_to_text/resources/link_to_text.js b/ios/chrome/browser/link_to_text/resources/link_to_text.js
deleted file mode 100644
index 2585b5ee..0000000
--- a/ios/chrome/browser/link_to_text/resources/link_to_text.js
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import * as utils from '//third_party/text-fragments-polyfill/src/src/fragment-generation-utils.js';
-
-/**
- * @fileoverview Interface used for Chrome to use link-to-text link generation
- * the utility functions from the text-fragments-polyfill library.
- */
-
-__gCrWeb['linkToText'] = {};
-
-/**
- * Attempts to generate a link with text fragments for the current
- * page's selection.
- */
-__gCrWeb.linkToText.getLinkToText =
-    function() {
-  const selection = window.getSelection();
-  const selectedText = `"${selection.toString()}"`;
-  const selectionRect = {x: 0, y: 0, width: 0, height: 0};
-
-  if (selection.rangeCount) {
-    // Get the selection range's first client rect.
-    const domRect = selection.getRangeAt(0).getClientRects()[0];
-    selectionRect.x = domRect.x;
-    selectionRect.y = domRect.y;
-    selectionRect.width = domRect.width;
-    selectionRect.height = domRect.height;
-  }
-
-  const canonicalLinkNode = document.querySelector('link[rel=\'canonical\']');
-
-  const response = utils.generateFragment(selection);
-  return {
-    status: response.status,
-    fragment: response.fragment,
-    selectedText: selectedText,
-    selectionRect: selectionRect,
-    canonicalUrl: canonicalLinkNode && canonicalLinkNode.getAttribute('href')
-  };
-}
-
-/**
- * Checks if the range is suitable to attempt link generation; the feature
- * should be disabled if this does not return true.
- */
-__gCrWeb.linkToText.checkPreconditions = function() {
-  return utils.isValidRangeForFragmentGeneration(
-      window.getSelection().getRangeAt(0));
-}
diff --git a/ios/chrome/browser/link_to_text/resources/link_to_text.ts b/ios/chrome/browser/link_to_text/resources/link_to_text.ts
new file mode 100644
index 0000000..2b10d13
--- /dev/null
+++ b/ios/chrome/browser/link_to_text/resources/link_to_text.ts
@@ -0,0 +1,46 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {gCrWeb} from '//ios/web/public/js_messaging/resources/gcrweb.js';
+import * as utils from '//third_party/text-fragments-polyfill/src/src/fragment-generation-utils.js';
+
+/**
+ * @fileoverview Interface used for Chrome to use link-to-text link generation
+ * the utility functions from the text-fragments-polyfill library.
+ */
+
+/**
+ * Attempts to generate a link with text fragments for the current
+ * page's selection.
+ */
+function getLinkToText() {
+  const selection = window.getSelection();
+  let selectionRect = {x: 0, y: 0, width: 0, height: 0};
+
+  if (selection && selection.rangeCount) {
+    // Get the selection range's first client rect.
+    const domRect = selection.getRangeAt(0).getClientRects()[0];
+    if (domRect) {
+      selectionRect.x = domRect.x;
+      selectionRect.y = domRect.y;
+      selectionRect.width = domRect.width;
+      selectionRect.height = domRect.height;
+    }
+  }
+
+  const selectedText = `"${selection?.toString()}"`;
+  const canonicalLinkNode = document.querySelector('link[rel=\'canonical\']');
+
+  const response = utils.generateFragment(selection as Selection);
+  return {
+    status: response.status,
+    fragment: response.fragment,
+    selectedText: selectedText,
+    selectionRect: selectionRect,
+    canonicalUrl: canonicalLinkNode
+        && canonicalLinkNode.getAttribute('href')
+  };
+}
+
+gCrWeb.linkToText =  { getLinkToText };
diff --git a/ios/chrome/browser/signin/authentication_service.mm b/ios/chrome/browser/signin/authentication_service.mm
index 68a26fd..947278a 100644
--- a/ios/chrome/browser/signin/authentication_service.mm
+++ b/ios/chrome/browser/signin/authentication_service.mm
@@ -321,49 +321,13 @@
   return account_manager_service_->GetIdentityWithGaiaID(authenticated_gaia_id);
 }
 
+// TODO(crbug.com/1351423): Remove asynchronous callback from SignIn function,
+// since this is no longer used by existing callers.
 void AuthenticationService::SignIn(ChromeIdentity* identity,
                                    signin_ui::CompletionCallback completion) {
-  base::WeakPtr<AuthenticationService> weak_ptr = GetWeakPtr();
-  ProceduralBlock signin_callback = ^() {
-    bool has_primary_identity = false;
-    AuthenticationService* strong_ptr = weak_ptr.get();
-    if (strong_ptr) {
-      strong_ptr->SignInInternal(identity);
-      has_primary_identity =
-          strong_ptr->HasPrimaryIdentity(signin::ConsentLevel::kSignin);
-    }
-    if (completion) {
-      completion(has_primary_identity);
-    }
-  };
-
-  if (base::FeatureList::IsEnabled(signin::kEnableUnicornAccountSupport)) {
-    ios::ChromeIdentityService* identity_service =
-        ios::GetChromeBrowserProvider().GetChromeIdentityService();
-    identity_service->IsSubjectToParentalControls(
-        identity, ^(ios::ChromeIdentityCapabilityResult result) {
-          AuthenticationService* strong_ptr = weak_ptr.get();
-          if (strong_ptr) {
-            strong_ptr->OnIsSubjectToParentalControlsResult(result,
-                                                            signin_callback);
-          }
-        });
-    return;
-  }
-
-  // When supervised user account are not enabled, sign in the account by
-  // default.
-  signin_callback();
-}
-
-void AuthenticationService::OnIsSubjectToParentalControlsResult(
-    ios::ChromeIdentityCapabilityResult result,
-    ProceduralBlock completion) {
-  // Clears browsing data for supervised users before sign-in operation.
-  if (result == ios::ChromeIdentityCapabilityResult::kTrue) {
-    delegate_->ClearBrowsingData(completion);
-  } else if (completion) {
-    completion();
+  SignInInternal(identity);
+  if (completion) {
+    completion(HasPrimaryIdentity(signin::ConsentLevel::kSignin));
   }
 }
 
@@ -484,10 +448,6 @@
   // GetPrimaryAccountMutator() returns nullptr on ChromeOS only.
   DCHECK(account_mutator);
 
-  // Retrieve primary identity before clearing in the account mutator.
-  ChromeIdentity* primary_identity =
-      GetPrimaryIdentity(signin::ConsentLevel::kSignin);
-
   account_mutator->ClearPrimaryAccount(
       signout_source, signin_metrics::SignoutDelete::kIgnoreMetric);
   crash_keys::SetCurrentlySignedIn(false);
@@ -497,18 +457,6 @@
   // started at least once.
   if (force_clear_browsing_data || (is_managed && is_first_setup_complete)) {
     delegate_->ClearBrowsingData(completion);
-  } else if (base::FeatureList::IsEnabled(
-                 signin::kEnableUnicornAccountSupport)) {
-    ios::ChromeIdentityService* identity_service =
-        ios::GetChromeBrowserProvider().GetChromeIdentityService();
-    base::WeakPtr<AuthenticationService> weak_ptr = GetWeakPtr();
-    identity_service->IsSubjectToParentalControls(
-        primary_identity, ^(ios::ChromeIdentityCapabilityResult result) {
-          AuthenticationService* strong_ptr = weak_ptr.get();
-          if (strong_ptr) {
-            strong_ptr->OnIsSubjectToParentalControlsResult(result, completion);
-          }
-        });
   } else if (completion) {
     base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
                                                   base::BindOnce(completion));
diff --git a/ios/chrome/browser/signin/authentication_service_unittest.mm b/ios/chrome/browser/signin/authentication_service_unittest.mm
index 9436b994..8d2592f 100644
--- a/ios/chrome/browser/signin/authentication_service_unittest.mm
+++ b/ios/chrome/browser/signin/authentication_service_unittest.mm
@@ -790,59 +790,3 @@
 
   EXPECT_OCMOCK_VERIFY(observer_delegate);
 }
-
-// Tests that a supervised user has all local data cleared on sign-in when
-// there was a previous account on the device.
-TEST_F(AuthenticationServiceTest, SupervisedAccountSwitch) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(
-      signin::kEnableUnicornAccountSupport);
-
-  SetExpectationsForSignIn();
-  identity_service()->SetCapabilities(
-      identity(1), @{
-        @(kIsSubjectToParentalControlsCapabilityName) :
-            @(static_cast<int>(ios::ChromeIdentityCapabilityResult::kTrue))
-      });
-
-  authentication_service()->SignIn(identity(0), nil);
-  EXPECT_TRUE(authentication_service()->HasPrimaryIdentity(
-      signin::ConsentLevel::kSignin));
-  ON_CALL(*mock_sync_service()->GetMockUserSettings(), IsFirstSetupComplete())
-      .WillByDefault(Return(true));
-
-  authentication_service()->SignOut(signin_metrics::ABORT_SIGNIN,
-                                    /*force_clear_browsing_data=*/false, nil);
-
-  EXPECT_EQ(ClearBrowsingDataCount(), 0);
-
-  // Clears browsing data when signed-in as a child.
-  authentication_service()->SignIn(identity(1), nil);
-  EXPECT_EQ(ClearBrowsingDataCount(), 1);
-}
-
-// Tests that supervised user's local data is correctly cleared when signing
-// out.
-TEST_F(AuthenticationServiceTest, SupervisedAccountSignOut) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(
-      signin::kEnableUnicornAccountSupport);
-
-  SetExpectationsForSignIn();
-  identity_service()->SetCapabilities(
-      identity(0), @{
-        @(kIsSubjectToParentalControlsCapabilityName) :
-            @(static_cast<int>(ios::ChromeIdentityCapabilityResult::kTrue))
-      });
-
-  authentication_service()->SignIn(identity(0), nil);
-  EXPECT_TRUE(authentication_service()->HasPrimaryIdentity(
-      signin::ConsentLevel::kSignin));
-  ON_CALL(*mock_sync_service()->GetMockUserSettings(), IsFirstSetupComplete())
-      .WillByDefault(Return(true));
-  EXPECT_EQ(ClearBrowsingDataCount(), 1);
-
-  authentication_service()->SignOut(signin_metrics::ABORT_SIGNIN,
-                                    /*force_clear_browsing_data=*/false, nil);
-  EXPECT_EQ(ClearBrowsingDataCount(), 2);
-}
diff --git a/ios/chrome/browser/ui/authentication/BUILD.gn b/ios/chrome/browser/ui/authentication/BUILD.gn
index 648ee0a..f6376dc 100644
--- a/ios/chrome/browser/ui/authentication/BUILD.gn
+++ b/ios/chrome/browser/ui/authentication/BUILD.gn
@@ -33,6 +33,7 @@
     "//components/infobars/core",
     "//components/policy/core/common",
     "//components/prefs",
+    "//components/signin/ios/browser",
     "//components/signin/public/identity_manager",
     "//components/signin/public/identity_manager/objc",
     "//components/strings",
diff --git a/ios/chrome/browser/ui/authentication/authentication_flow.mm b/ios/chrome/browser/ui/authentication/authentication_flow.mm
index b487fe54..d8f4567 100644
--- a/ios/chrome/browser/ui/authentication/authentication_flow.mm
+++ b/ios/chrome/browser/ui/authentication/authentication_flow.mm
@@ -8,6 +8,7 @@
 #include "base/check_op.h"
 #import "base/ios/block_types.h"
 #include "base/notreached.h"
+#import "components/signin/ios/browser/features.h"
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #include "ios/chrome/browser/main/browser.h"
 #include "ios/chrome/browser/policy/cloud/user_policy_switch.h"
@@ -302,17 +303,25 @@
     case CHECK_MERGE_CASE:
       DCHECK_EQ(SHOULD_CLEAR_DATA_USER_CHOICE, self.localDataClearingStrategy);
       if (_postSignInAction == POST_SIGNIN_ACTION_COMMIT_SYNC) {
-        if (([_performer shouldHandleMergeCaseForIdentity:_identityToSignIn
-                                             browserState:browserState])) {
-          [_performer promptMergeCaseForIdentity:_identityToSignIn
-                                         browser:_browser
-                                  viewController:_presentingViewController];
-          return;
+        if (base::FeatureList::IsEnabled(
+                signin::kEnableUnicornAccountSupport)) {
+          ios::ChromeIdentityService* identity_service =
+              ios::GetChromeBrowserProvider().GetChromeIdentityService();
+          __weak AuthenticationFlow* weakSelf = self;
+          identity_service->IsSubjectToParentalControls(
+              _identityToSignIn, ^(ios::ChromeIdentityCapabilityResult result) {
+                if (result == ios::ChromeIdentityCapabilityResult::kTrue) {
+                  weakSelf.localDataClearingStrategy =
+                      SHOULD_CLEAR_DATA_CLEAR_DATA;
+                  [weakSelf continueSignin];
+                  return;
+                }
+                [weakSelf checkMergeCaseForUnsupervisedAccounts];
+              });
         } else {
-          // If the user is not prompted to choose a data clearing strategy,
-          // Chrome defaults to merging the account data.
-          self.localDataClearingStrategy = SHOULD_CLEAR_DATA_MERGE_DATA;
+          [self checkMergeCaseForUnsupervisedAccounts];
         }
+        return;
       }
       [self continueSignin];
       return;
@@ -382,6 +391,21 @@
   NOTREACHED();
 }
 
+- (void)checkMergeCaseForUnsupervisedAccounts {
+  if (([_performer
+          shouldHandleMergeCaseForIdentity:_identityToSignIn
+                              browserState:_browser->GetBrowserState()])) {
+    [_performer promptMergeCaseForIdentity:_identityToSignIn
+                                   browser:_browser
+                            viewController:_presentingViewController];
+  } else {
+    // If the user is not prompted to choose a data clearing strategy,
+    // Chrome defaults to merging the account data.
+    self.localDataClearingStrategy = SHOULD_CLEAR_DATA_MERGE_DATA;
+    [self continueSignin];
+  }
+}
+
 - (void)checkSigninSteps {
   ChromeIdentity* currentIdentity =
       AuthenticationServiceFactory::GetForBrowserState(
diff --git a/ios/chrome/browser/ui/authentication/signin/BUILD.gn b/ios/chrome/browser/ui/authentication/signin/BUILD.gn
index 745bdca..ba60a8e 100644
--- a/ios/chrome/browser/ui/authentication/signin/BUILD.gn
+++ b/ios/chrome/browser/ui/authentication/signin/BUILD.gn
@@ -122,6 +122,8 @@
     "//base",
     "//base/test:test_support",
     "//components/policy:policy_code_generate",
+    "//components/signin/internal/identity_manager",
+    "//components/signin/ios/browser:features",
     "//components/signin/public/base",
     "//components/strings:components_strings_grit",
     "//ios/chrome/app/strings",
@@ -131,11 +133,13 @@
     "//ios/chrome/browser/policy:policy_util",
     "//ios/chrome/browser/ui/authentication:eg_test_support+eg2",
     "//ios/chrome/browser/ui/authentication/views:views_constants",
+    "//ios/chrome/browser/ui/bookmarks:eg_test_support+eg2",
     "//ios/chrome/browser/ui/content_suggestions:feature_flags",
     "//ios/chrome/browser/ui/recent_tabs:recent_tabs_ui_constants",
     "//ios/chrome/browser/ui/settings:constants",
     "//ios/chrome/browser/ui/settings/google_services:constants",
     "//ios/chrome/test/earl_grey:eg_test_support+eg2",
+    "//ios/public/provider/chrome/browser/signin",
     "//ios/public/provider/chrome/browser/signin:constants",
     "//ios/public/provider/chrome/browser/signin:fake_chrome_identity",
     "//ios/testing/earl_grey:eg_test_support+eg2",
diff --git a/ios/chrome/browser/ui/authentication/signin/signin_coordinator_egtest.mm b/ios/chrome/browser/ui/authentication/signin/signin_coordinator_egtest.mm
index 3d11dd5..30fe624 100644
--- a/ios/chrome/browser/ui/authentication/signin/signin_coordinator_egtest.mm
+++ b/ios/chrome/browser/ui/authentication/signin/signin_coordinator_egtest.mm
@@ -7,6 +7,8 @@
 #include "base/strings/sys_string_conversions.h"
 #import "base/test/ios/wait_util.h"
 #include "components/policy/policy_constants.h"
+#import "components/signin/internal/identity_manager/account_capabilities_constants.h"
+#import "components/signin/ios/browser/features.h"
 #include "components/signin/public/base/signin_metrics.h"
 #include "components/strings/grit/components_strings.h"
 #import "ios/chrome/browser/metrics/metrics_app_interface.h"
@@ -18,6 +20,8 @@
 #import "ios/chrome/browser/ui/authentication/signin_earl_grey_ui_test_util.h"
 #import "ios/chrome/browser/ui/authentication/signin_matchers.h"
 #import "ios/chrome/browser/ui/authentication/views/views_constants.h"
+#import "ios/chrome/browser/ui/bookmarks/bookmark_earl_grey.h"
+#import "ios/chrome/browser/ui/bookmarks/bookmark_earl_grey_ui.h"
 #import "ios/chrome/browser/ui/content_suggestions/content_suggestions_feature.h"
 #import "ios/chrome/browser/ui/recent_tabs/recent_tabs_constants.h"
 #import "ios/chrome/browser/ui/settings/google_services/manage_sync_settings_constants.h"
@@ -27,6 +31,8 @@
 #import "ios/chrome/test/earl_grey/chrome_earl_grey_ui.h"
 #import "ios/chrome/test/earl_grey/chrome_matchers.h"
 #import "ios/chrome/test/earl_grey/chrome_test_case.h"
+#import "ios/chrome/test/earl_grey/web_http_server_chrome_test_case.h"
+#import "ios/public/provider/chrome/browser/signin/chrome_identity_service.h"
 #import "ios/public/provider/chrome/browser/signin/fake_chrome_identity.h"
 #import "ios/public/provider/chrome/browser/signin/fake_chrome_identity_interaction_manager_constants.h"
 #import "ios/testing/earl_grey/earl_grey_test.h"
@@ -74,6 +80,21 @@
 
 NSString* const kPassphrase = @"hello";
 
+// Timeout in seconds to wait for asynchronous sync operations.
+const NSTimeInterval kSyncOperationTimeout = 5.0;
+
+// Sets parental control capability for the given identity.
+void SetParentalControlsCapabilityForIdentity(FakeChromeIdentity* identity) {
+  // The identity must exist in the test storage to be able to set capabilities
+  // through the fake identity service.
+  [SigninEarlGrey addFakeIdentity:identity];
+
+  NSDictionary* capabilities = @{
+    @(kIsSubjectToParentalControlsCapabilityName) : [NSNumber
+        numberWithInt:(int)ios::ChromeIdentityCapabilityResult::kTrue],
+  };
+  [SigninEarlGrey setCapabilities:capabilities forIdentity:identity];
+}
 // Closes the sign-in import data dialog and choose either to combine the data
 // or keep the data separate.
 void CloseImportDataDialog(id<GREYMatcher> choiceButtonMatcher) {
@@ -148,22 +169,31 @@
 
 // Sign-in interaction tests that work both with Unified Consent enabled or
 // disabled.
-@interface SigninCoordinatorTestCase : ChromeTestCase
+@interface SigninCoordinatorTestCase : WebHttpServerChromeTestCase
 @end
 
 @implementation SigninCoordinatorTestCase
 
+- (AppLaunchConfiguration)appConfigurationForTestCase {
+  AppLaunchConfiguration config = [super appConfigurationForTestCase];
+  config.features_enabled.push_back(signin::kEnableUnicornAccountSupport);
+  return config;
+}
+
 - (void)setUp {
   [super setUp];
   // Remove closed tab history to make sure the sign-in promo is always visible
   // in recent tabs.
   [ChromeEarlGrey clearBrowsingHistory];
+  [ChromeEarlGrey waitForBookmarksToFinishLoading];
+  [ChromeEarlGrey clearBookmarks];
   GREYAssertNil([MetricsAppInterface setupHistogramTester],
                 @"Failed to set up histogram tester.");
 }
 
 - (void)tearDown {
   [super tearDown];
+  [BookmarkEarlGrey clearBookmarksPositionCache];
   GREYAssertNil([MetricsAppInterface releaseHistogramTester],
                 @"Cannot reset histogram tester.");
 }
@@ -181,6 +211,64 @@
   ExpectSyncConsentHistogram(signin_metrics::SigninAccountType::kRegular);
 }
 
+// Tests that opening the sign-in screen from the Settings and signing in works
+// correctly when there is a supervised user identity on the device.
+- (void)testSignInSupervisedUser {
+  // Set up a fake supervised identity.
+  FakeChromeIdentity* fakeIdentity = [FakeChromeIdentity fakeIdentity1];
+  SetParentalControlsCapabilityForIdentity(fakeIdentity);
+
+  [SigninEarlGreyUI signinWithFakeIdentity:fakeIdentity];
+
+  [SigninEarlGrey verifySignedInWithFakeIdentity:fakeIdentity];
+}
+
+// Tests that signing out a supervised user account with the keep local data
+// option is honored.
+- (void)testSignOutWithKeepDataForSupervisedUser {
+  // Sign in with a fake supervised identity.
+  FakeChromeIdentity* fakeSupervisedIdentity =
+      [FakeChromeIdentity fakeIdentity1];
+  SetParentalControlsCapabilityForIdentity(fakeSupervisedIdentity);
+  [SigninEarlGreyUI signinWithFakeIdentity:fakeSupervisedIdentity];
+
+  // Add a bookmark after sync is initialized.
+  [ChromeEarlGrey waitForSyncInitialized:YES syncTimeout:kSyncOperationTimeout];
+  [ChromeEarlGrey waitForBookmarksToFinishLoading];
+  [BookmarkEarlGrey setupStandardBookmarks];
+
+  // Sign out from the supervised account with option to keep local data.
+  [SigninEarlGreyUI
+      signOutWithConfirmationChoice:SignOutConfirmationChoiceKeepData];
+
+  // Verify bookmarks are available.
+  [BookmarkEarlGreyUI openBookmarks];
+  [BookmarkEarlGreyUI verifyEmptyBackgroundIsAbsent];
+}
+
+// Tests that signing out a supervised user account with the clear local data
+// option is honored.
+- (void)testSignOutWithClearDataForSupervisedUser {
+  // Sign in with a fake supervised identity.
+  FakeChromeIdentity* fakeSupervisedIdentity =
+      [FakeChromeIdentity fakeIdentity1];
+  SetParentalControlsCapabilityForIdentity(fakeSupervisedIdentity);
+  [SigninEarlGreyUI signinWithFakeIdentity:fakeSupervisedIdentity];
+
+  // Add a bookmark after sync is initialized.
+  [ChromeEarlGrey waitForSyncInitialized:YES syncTimeout:kSyncOperationTimeout];
+  [ChromeEarlGrey waitForBookmarksToFinishLoading];
+  [BookmarkEarlGrey setupStandardBookmarks];
+
+  // Sign out from the supervised account with option to clear local data.
+  [SigninEarlGreyUI
+      signOutWithConfirmationChoice:SignOutConfirmationChoiceClearData];
+
+  // Verify bookmarks are cleared.
+  [BookmarkEarlGreyUI openBookmarks];
+  [BookmarkEarlGreyUI verifyEmptyBackgroundAppears];
+}
+
 // Tests signing in with one account, switching sync account to a second and
 // choosing to keep the browsing data separate during the switch.
 // Flaky, crbug.com/1279995.
diff --git a/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm b/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
index ccb641e5..73a08f3c 100644
--- a/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
+++ b/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
@@ -713,14 +713,8 @@
                             [NSString stringWithFormat:@"%i", 2])];
 
   // Test the same thing after opening a tab from the tab grid.
-  // TODO(crbug.com/933953) For an unknown reason synchronization doesn't work
-  // well with tapping on the tabgrid button, and instead triggers the long
-  // press gesture recognizer.  Disable this here so the test can be re-enabled.
-  {
-    ScopedSynchronizationDisabler disabler;
-    [[EarlGrey selectElementWithMatcher:chrome_test_util::ShowTabsButton()]
-        performAction:grey_longPressWithDuration(0.05)];
-  }
+  [ChromeEarlGreyUI openTabGrid];
+
   [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridNewTabButton()]
       performAction:grey_tap()];
   [[EarlGrey selectElementWithMatcher:chrome_test_util::FakeOmnibox()]
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm b/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm
index 1b0abcc6..bb163a4 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_coordinator.mm
@@ -1063,18 +1063,7 @@
 
   // Requests feeds here if the correct flags and prefs are enabled.
   if ([self shouldFeedBeVisible]) {
-    FeedModelConfiguration* discoverFeedConfiguration =
-        [FeedModelConfiguration discoverFeedModelConfiguration];
-    self.discoverFeedService->CreateFeedModel(discoverFeedConfiguration);
-
     if ([self isFollowingFeedAvailable]) {
-      FeedModelConfiguration* followingFeedConfiguration =
-          [FeedModelConfiguration
-              followingModelConfigurationWithSortType:
-                  (FollowingFeedSortType)self.prefService->GetInteger(
-                      prefs::kNTPFollowingFeedSortType)];
-      self.discoverFeedService->CreateFeedModel(followingFeedConfiguration);
-
       switch (self.selectedFeed) {
         case FeedTypeDiscover:
           self.feedViewController = [self discoverFeed];
@@ -1108,6 +1097,10 @@
     return nil;
   }
 
+  FeedModelConfiguration* discoverFeedConfiguration =
+      [FeedModelConfiguration discoverFeedModelConfiguration];
+  self.discoverFeedService->CreateFeedModel(discoverFeedConfiguration);
+
   UIViewController* discoverFeed =
       self.discoverFeedService->NewDiscoverFeedViewControllerWithConfiguration(
           [self feedViewControllerConfiguration]);
@@ -1120,6 +1113,12 @@
     return nil;
   }
 
+  FeedModelConfiguration* followingFeedConfiguration = [FeedModelConfiguration
+      followingModelConfigurationWithSortType:
+          (FollowingFeedSortType)self.prefService->GetInteger(
+              prefs::kNTPFollowingFeedSortType)];
+  self.discoverFeedService->CreateFeedModel(followingFeedConfiguration);
+
   UIViewController* followingFeed =
       self.discoverFeedService->NewFollowingFeedViewControllerWithConfiguration(
           [self feedViewControllerConfiguration]);
diff --git a/ios/chrome/browser/ui/omnibox/popup/BUILD.gn b/ios/chrome/browser/ui/omnibox/popup/BUILD.gn
index 794b2b0e..57a33e7 100644
--- a/ios/chrome/browser/ui/omnibox/popup/BUILD.gn
+++ b/ios/chrome/browser/ui/omnibox/popup/BUILD.gn
@@ -146,6 +146,7 @@
     "resources:omnibox_suggestion_answer_icon_dark_color",
     "resources:omnibox_suggestion_icon_color",
     "resources:omnibox_suggestion_icon_dark_color",
+    "resources:omnibox_suggestion_row_highlight_color",
     "//base",
     "//components/omnibox/common",
     "//ios/chrome/app/strings:ios_strings_grit",
@@ -162,6 +163,7 @@
     "//ios/chrome/browser/ui/toolbar/public",
     "//ios/chrome/browser/ui/util",
     "//ios/chrome/common/ui/colors",
+    "//ios/chrome/common/ui/elements:elements",
     "//ios/chrome/common/ui/util",
     "//ios/public/provider/chrome/browser",
     "//ios/public/provider/chrome/browser/branded_images:branded_images_api",
diff --git a/ios/chrome/browser/ui/omnibox/popup/omnibox_icon_view.h b/ios/chrome/browser/ui/omnibox/popup/omnibox_icon_view.h
index 1112300..84cf737 100644
--- a/ios/chrome/browser/ui/omnibox/popup/omnibox_icon_view.h
+++ b/ios/chrome/browser/ui/omnibox/popup/omnibox_icon_view.h
@@ -21,6 +21,8 @@
 @property(nonatomic, weak) id<ImageRetriever> imageRetriever;
 // Used for testing to check whether this view is displaying anything.
 @property(nonatomic, readonly) UIImage* mainImage;
+// Same as UIImageView.
+@property(nonatomic, getter=isHighlighted) BOOL highlighted;
 
 - (void)prepareForReuse;
 
diff --git a/ios/chrome/browser/ui/omnibox/popup/omnibox_icon_view.mm b/ios/chrome/browser/ui/omnibox/popup/omnibox_icon_view.mm
index b9d59fa..bec2d669 100644
--- a/ios/chrome/browser/ui/omnibox/popup/omnibox_icon_view.mm
+++ b/ios/chrome/browser/ui/omnibox/popup/omnibox_icon_view.mm
@@ -5,6 +5,7 @@
 #import "ios/chrome/browser/ui/omnibox/popup/omnibox_icon_view.h"
 
 #import "ios/chrome/browser/net/crurl.h"
+#import "ios/chrome/browser/ui/omnibox/omnibox_ui_features.h"
 #import "ios/chrome/browser/ui/omnibox/popup/favicon_retriever.h"
 #import "ios/chrome/browser/ui/omnibox/popup/image_retriever.h"
 #import "ios/chrome/browser/ui/omnibox/popup/omnibox_icon.h"
@@ -20,6 +21,8 @@
 @property(nonatomic, strong) UIImageView* mainImageView;
 @property(nonatomic, strong) UIImageView* overlayImageView;
 
+@property(nonatomic, strong) id<OmniboxIcon> omniboxIcon;
+
 @end
 
 @implementation OmniboxIconView
@@ -95,6 +98,8 @@
 }
 
 - (void)setOmniboxIcon:(id<OmniboxIcon>)omniboxIcon {
+  _omniboxIcon = omniboxIcon;
+
   // Setup the view layout the first time the cell is setup.
   if (self.subviews.count == 0) {
     [self setupLayout];
@@ -150,4 +155,16 @@
   return self.mainImageView.image;
 }
 
+- (void)setHighlighted:(BOOL)highlighted {
+  _highlighted = highlighted;
+  self.backgroundImageView.highlighted = highlighted;
+  self.mainImageView.highlighted = highlighted;
+  self.overlayImageView.highlighted = highlighted;
+
+  if (IsOmniboxActionsEnabled()) {
+    self.mainImageView.tintColor =
+        highlighted ? UIColor.whiteColor : self.omniboxIcon.iconImageTintColor;
+  }
+}
+
 @end
diff --git a/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_row_cell.mm b/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_row_cell.mm
index f630353..d3994e46 100644
--- a/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_row_cell.mm
+++ b/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_row_cell.mm
@@ -15,6 +15,7 @@
 #import "ios/chrome/browser/ui/util/named_guide.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
 #import "ios/chrome/common/ui/colors/semantic_color_names.h"
+#import "ios/chrome/common/ui/elements/gradient_view.h"
 #import "ios/chrome/common/ui/util/pointer_interaction_util.h"
 #include "ios/chrome/grit/ios_strings.h"
 #include "ios/chrome/grit/ios_theme_resources.h"
@@ -29,6 +30,7 @@
 const CGFloat kTextTopMargin = 6;
 const CGFloat kTrailingButtonSize = 24;
 const CGFloat kTrailingButtonTrailingMargin = 14;
+const CGFloat kTopGradientColorOpacity = 0.85;
 
 NSString* const kOmniboxPopupRowSwitchTabAccessibilityIdentifier =
     @"OmniboxPopupRowSwitchTabAccessibilityIdentifier";
@@ -74,9 +76,19 @@
   if (self) {
     _incognito = NO;
 
-    self.selectedBackgroundView = [[UIView alloc] initWithFrame:CGRectZero];
-    self.selectedBackgroundView.backgroundColor =
-        [UIColor colorNamed:kTableViewRowHighlightColor];
+    if (IsOmniboxActionsEnabled()) {
+      self.selectedBackgroundView = [[GradientView alloc]
+          initWithTopColor:
+              [[UIColor colorNamed:@"omnibox_suggestion_row_highlight_color"]
+                  colorWithAlphaComponent:kTopGradientColorOpacity]
+               bottomColor:
+                   [UIColor
+                       colorNamed:@"omnibox_suggestion_row_highlight_color"]];
+    } else {
+      self.selectedBackgroundView = [[UIView alloc] initWithFrame:CGRectZero];
+      self.selectedBackgroundView.backgroundColor =
+          [UIColor colorNamed:kTableViewRowHighlightColor];
+    }
 
     _textTruncatingLabel =
         [[FadeTruncatingLabel alloc] initWithFrame:CGRectZero];
@@ -157,6 +169,25 @@
   }
 }
 
+#pragma mark - UITableViewCell
+
+- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated {
+  [super setHighlighted:highlighted animated:animated];
+
+  if (!IsOmniboxActionsEnabled()) {
+    return;
+  }
+
+  UIColor* textColor = highlighted ? [UIColor whiteColor] : nil;
+  self.textTruncatingLabel.textColor = textColor;
+  self.detailTruncatingLabel.textColor = textColor;
+  self.detailAnswerLabel.textColor = textColor;
+
+  self.leadingIconView.highlighted = highlighted;
+  self.trailingButton.tintColor =
+      highlighted ? [UIColor whiteColor] : [UIColor colorNamed:kBlueColor];
+}
+
 #pragma mark - Property setter/getters
 
 - (void)setImageRetriever:(id<ImageRetriever>)imageRetriever {
diff --git a/ios/chrome/browser/ui/omnibox/popup/resources/BUILD.gn b/ios/chrome/browser/ui/omnibox/popup/resources/BUILD.gn
index 08f18d87..4f454036 100644
--- a/ios/chrome/browser/ui/omnibox/popup/resources/BUILD.gn
+++ b/ios/chrome/browser/ui/omnibox/popup/resources/BUILD.gn
@@ -31,6 +31,10 @@
   sources = [ "omnibox_suggestion_icon_dark_color.colorset/Contents.json" ]
 }
 
+colorset("omnibox_suggestion_row_highlight_color") {
+  sources = [ "omnibox_suggestion_row_highlight_color.colorset/Contents.json" ]
+}
+
 imageset("omnibox_popup_tab_match") {
   sources = [
     "omnibox_popup_tab_match.imageset/Contents.json",
diff --git a/ios/chrome/browser/ui/omnibox/popup/resources/omnibox_suggestion_row_highlight_color.colorset/Contents.json b/ios/chrome/browser/ui/omnibox/popup/resources/omnibox_suggestion_row_highlight_color.colorset/Contents.json
new file mode 100644
index 0000000..e36b0c4
--- /dev/null
+++ b/ios/chrome/browser/ui/omnibox/popup/resources/omnibox_suggestion_row_highlight_color.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  },
+  "colors" : [
+    {
+      "idiom" : "universal",
+      "color" : {
+        "color-space" : "srgb",
+        "components" : {
+          "red" : "0x1A",
+          "alpha" : "1.000",
+          "blue" : "0xE8",
+          "green" : "0x73"
+        }
+      }
+    }
+  ]
+}
diff --git a/ios/chrome/test/data/policy/policy_test_cases.json b/ios/chrome/test/data/policy/policy_test_cases.json
index 72f0165..c03799d 100644
--- a/ios/chrome/test/data/policy/policy_test_cases.json
+++ b/ios/chrome/test/data/policy/policy_test_cases.json
@@ -212,6 +212,9 @@
   "CloudPolicyOverridesPlatformPolicy": {
     "reason_for_missing_test": "This policy has no pref as it is only directly read by the policy system."
   },
+  "CloudUserPolicyMerge": {
+    "reason_for_missing_test": "This policy has no pref as it is only directly read by the policy system."
+  },
   "CloudUserPolicyOverridesCloudMachinePolicy": {
     "reason_for_missing_test": "This policy has no pref as it is only directly read by the policy system."
   },
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey_ui.h b/ios/chrome/test/earl_grey/chrome_earl_grey_ui.h
index a86df20..19b706a 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey_ui.h
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey_ui.h
@@ -114,6 +114,9 @@
 // Opens a new incognito tab via the tools menu.
 - (void)openNewIncognitoTab;
 
+// Opens the tab grid.
+- (void)openTabGrid;
+
 // Opens and clear browsing data from history.
 - (void)openAndClearBrowsingDataFromHistory;
 
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey_ui.mm b/ios/chrome/test/earl_grey/chrome_earl_grey_ui.mm
index 4ad15a6..09e3f271 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey_ui.mm
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey_ui.mm
@@ -11,6 +11,7 @@
 #include "ios/chrome/grit/ios_strings.h"
 #import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
 #import "ios/chrome/test/earl_grey/chrome_matchers.h"
+#import "ios/chrome/test/scoped_eg_synchronization_disabler.h"
 #import "ios/testing/earl_grey/earl_grey_test.h"
 #include "ui/base/l10n/l10n_util.h"
 
@@ -363,6 +364,15 @@
   [self waitForAppToIdle];
 }
 
+- (void)openTabGrid {
+  // TODO(crbug.com/933953) For an unknown reason synchronization doesn't work
+  // well with tapping on the tabgrid button, and instead triggers the long
+  // press gesture recognizer.  Disable this here so the test can be re-enabled.
+  ScopedSynchronizationDisabler disabler;
+  [[EarlGrey selectElementWithMatcher:chrome_test_util::ShowTabsButton()]
+      performAction:grey_longPressWithDuration(0.05)];
+}
+
 - (void)reload {
   // On iPhone Reload button is a part of tools menu, so open it.
   if (IsAppCompactWidth()) {
diff --git a/ipc/ipc_message.cc b/ipc/ipc_message.cc
index a27afc7..510d80a 100644
--- a/ipc/ipc_message.cc
+++ b/ipc/ipc_message.cc
@@ -70,7 +70,7 @@
   Init();
 }
 
-Message::Message(const char* data, int data_len)
+Message::Message(const char* data, size_t data_len)
     : base::Pickle(data, data_len) {
   Init();
 }
diff --git a/ipc/ipc_message.h b/ipc/ipc_message.h
index 8302cee2..b15bc20 100644
--- a/ipc/ipc_message.h
+++ b/ipc/ipc_message.h
@@ -66,7 +66,7 @@
   // Initializes a message from a const block of data.  The data is not copied;
   // instead the data is merely referenced by this message.  Only const methods
   // should be used on the message when initialized this way.
-  Message(const char* data, int data_len);
+  Message(const char* data, size_t data_len);
 
   Message(const Message& other);
   Message& operator=(const Message& other);
@@ -111,7 +111,7 @@
     if (unblock) {
       header()->flags |= UNBLOCK_BIT;
     } else {
-      header()->flags &= ~UNBLOCK_BIT;
+      header()->flags &= static_cast<uint32_t>(~UNBLOCK_BIT);
     }
   }
 
diff --git a/ipc/ipc_message_utils.h b/ipc/ipc_message_utils.h
index 3462b855..77efa8c 100644
--- a/ipc/ipc_message_utils.h
+++ b/ipc/ipc_message_utils.h
@@ -191,7 +191,9 @@
 template <>
 struct ParamTraits<unsigned int> {
   typedef unsigned int param_type;
-  static void Write(base::Pickle* m, const param_type& p) { m->WriteInt(p); }
+  static void Write(base::Pickle* m, const param_type& p) {
+    m->WriteInt(static_cast<int>(p));
+  }
   static bool Read(const base::Pickle* m,
                    base::PickleIterator* iter,
                    param_type* r) {
@@ -260,7 +262,9 @@
 template <>
 struct ParamTraits<unsigned long long> {
   typedef unsigned long long param_type;
-  static void Write(base::Pickle* m, const param_type& p) { m->WriteInt64(p); }
+  static void Write(base::Pickle* m, const param_type& p) {
+    m->WriteInt64(static_cast<int64_t>(p));
+  }
   static bool Read(const base::Pickle* m,
                    base::PickleIterator* iter,
                    param_type* r) {
diff --git a/media/gpu/chromeos/BUILD.gn b/media/gpu/chromeos/BUILD.gn
index 84c78a1..455435b 100644
--- a/media/gpu/chromeos/BUILD.gn
+++ b/media/gpu/chromeos/BUILD.gn
@@ -18,6 +18,7 @@
   defines = [ "MEDIA_GPU_IMPLEMENTATION" ]
   sources = [
     "image_processor_factory.cc",
+    "oop_video_decoder.cc",
     "video_decoder_pipeline.cc",
   ]
 
@@ -30,6 +31,7 @@
     "//media/gpu:buildflags",
     "//media/gpu:command_buffer_helper",
     "//media/gpu:common",
+    "//media/mojo/common:common",
   ]
 
   if (use_vaapi) {
@@ -42,6 +44,11 @@
   if (use_v4l2_codec) {
     deps += [ "//media/gpu/v4l2" ]
   }
+
+  if (is_chromeos) {
+    deps +=
+        [ "//chromeos/components/cdm_factory_daemon:cdm_factory_daemon_gpu" ]
+  }
 }
 
 # This target can be depended by targets in //media/gpu/{v4l2,vaapi}.
@@ -66,7 +73,6 @@
     "libyuv_image_processor_backend.h",
     "mailbox_video_frame_converter.cc",
     "mailbox_video_frame_converter.h",
-    "oop_video_decoder.cc",
     "oop_video_decoder.h",
     "platform_video_frame_pool.cc",
     "platform_video_frame_pool.h",
@@ -100,7 +106,6 @@
     "//media/gpu:command_buffer_helper",
     "//media/gpu:common",
     "//media/gpu:video_frame_mapper_common",
-    "//media/mojo/common:common",
     "//third_party/libyuv",
     "//ui/gfx:buffer_types",
     "//ui/gfx:memory_buffer",
@@ -109,7 +114,7 @@
     "//ui/gfx/linux:gbm",
     "//ui/gl",
   ]
-  if (is_chromeos_ash) {
+  if (is_chromeos) {
     sources += [
       "decoder_buffer_transcryptor.cc",
       "decoder_buffer_transcryptor.h",
@@ -156,7 +161,7 @@
   ]
 }
 
-if (is_chromeos_ash) {
+if (is_chromeos) {
   proto_library("cdm-oemcrypto-proto") {
     sources = [
       "//third_party/cros_system_api/dbus/cdm_oemcrypto/secure_buffer.proto",
diff --git a/media/gpu/chromeos/oop_video_decoder.cc b/media/gpu/chromeos/oop_video_decoder.cc
index f1fedd7..471e077 100644
--- a/media/gpu/chromeos/oop_video_decoder.cc
+++ b/media/gpu/chromeos/oop_video_decoder.cc
@@ -5,11 +5,18 @@
 #include "media/gpu/chromeos/oop_video_decoder.h"
 
 #include "base/memory/ptr_util.h"
+#include "build/chromeos_buildflags.h"
+#include "chromeos/components/cdm_factory_daemon/stable_cdm_context_impl.h"
 #include "media/base/bind_to_current_loop.h"
 #include "media/gpu/macros.h"
 #include "media/mojo/common/mojo_decoder_buffer_converter.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/self_owned_receiver.h"
+
+#if BUILDFLAG(USE_VAAPI)
+#include "media/gpu/vaapi/vaapi_wrapper.h"
+#endif  // BUILDFLAG(USE_VAAPI)
 
 // Throughout this file, we have sprinkled many CHECK()s to assert invariants
 // that should hold regardless of the behavior of the remote decoder or
@@ -170,16 +177,38 @@
     return;
   }
 
+  mojo::PendingRemote<stable::mojom::StableCdmContext>
+      pending_remote_stable_cdm_context;
+  if (config.is_encrypted()) {
+#if BUILDFLAG(IS_CHROMEOS)
+    if (!cdm_context || !cdm_context->GetChromeOsCdmContext()) {
+      std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
+      return;
+    }
+    mojo::PendingReceiver<stable::mojom::StableCdmContext> cdm_receiver;
+    pending_remote_stable_cdm_context =
+        cdm_receiver.InitWithNewPipeAndPassRemote();
+    mojo::MakeSelfOwnedReceiver(
+        std::make_unique<chromeos::StableCdmContextImpl>(cdm_context),
+        std::move(cdm_receiver));
+#if BUILDFLAG(USE_VAAPI)
+    // We need to signal that for AMD we will do transcryption on the GPU side.
+    // Then on the other end we just make transcryption a no-op.
+    needs_transcryption_ = (VaapiWrapper::GetImplementationType() ==
+                            VAImplementation::kMesaGallium);
+#endif  // BUILDFLAG(USE_VAAPI)
+#else
+    std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
+    return;
+#endif  // BUILDFLAG(IS_CHROMEOS)
+  }
+
   init_cb_ = std::move(init_cb);
   output_cb_ = output_cb;
   waiting_cb_ = waiting_cb;
 
-  // TODO(b/171813538): plumb protected content.
-  mojo::PendingRemote<stable::mojom::StableCdmContext>
-      pending_remote_cdm_context;
-
   remote_decoder_->Initialize(config, low_delay,
-                              std::move(pending_remote_cdm_context),
+                              std::move(pending_remote_stable_cdm_context),
                               base::BindOnce(&OOPVideoDecoder::OnInitializeDone,
                                              weak_this_factory_.GetWeakPtr()));
 }
@@ -390,7 +419,8 @@
 }
 
 bool OOPVideoDecoder::NeedsTranscryption() {
-  return false;
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return needs_transcryption_;
 }
 
 void OOPVideoDecoder::OnVideoFrameDecoded(
diff --git a/media/gpu/chromeos/oop_video_decoder.h b/media/gpu/chromeos/oop_video_decoder.h
index d61f3039..0ca038a1 100644
--- a/media/gpu/chromeos/oop_video_decoder.h
+++ b/media/gpu/chromeos/oop_video_decoder.h
@@ -114,6 +114,10 @@
   std::unique_ptr<MojoDecoderBufferWriter> mojo_decoder_buffer_writer_
       GUARDED_BY_CONTEXT(sequence_checker_);
 
+  // This is to indicate we should perform transcryption before sending the data
+  // to the video decoder utility process.
+  bool needs_transcryption_ GUARDED_BY_CONTEXT(sequence_checker_) = false;
+
   SEQUENCE_CHECKER(sequence_checker_);
 
   base::WeakPtrFactory<OOPVideoDecoder> weak_this_factory_
diff --git a/media/gpu/chromeos/video_decoder_pipeline.cc b/media/gpu/chromeos/video_decoder_pipeline.cc
index bbabd6b..c491e5f 100644
--- a/media/gpu/chromeos/video_decoder_pipeline.cc
+++ b/media/gpu/chromeos/video_decoder_pipeline.cc
@@ -295,9 +295,9 @@
   main_frame_pool_.reset();
   frame_converter_.reset();
   decoder_.reset();
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
   buffer_transcryptor_.reset();
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // BUILDFLAG(IS_CHROMEOS)
 }
 
 // static
@@ -451,7 +451,7 @@
   MEDIA_LOG(INFO, media_log_)
       << "VideoDecoderPipeline |decoder_| Initialize() successful";
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
   if (decoder_ && decoder_->NeedsTranscryption()) {
     if (!cdm_context) {
       VLOGF(1) << "CdmContext required for transcryption";
@@ -470,7 +470,7 @@
     // In case this was created on a prior initialization but no longer needed.
     buffer_transcryptor_.reset();
   }
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
   client_task_runner_->PostTask(FROM_HERE,
                                 base::BindOnce(std::move(init_cb), status));
@@ -502,10 +502,10 @@
     image_processor_->Reset();
   frame_converter_->AbortPendingFrames();
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
   if (buffer_transcryptor_)
     buffer_transcryptor_->Reset(DecoderStatus::Codes::kAborted);
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
   CallFlushCbIfNeeded(DecoderStatus::Codes::kAborted);
 
@@ -545,7 +545,7 @@
   }
 
   const bool is_flush = buffer->end_of_stream();
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
   if (buffer_transcryptor_) {
     buffer_transcryptor_->EnqueueBuffer(
         std::move(buffer),
@@ -553,7 +553,7 @@
                        is_flush, std::move(decode_cb)));
     return;
   }
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
   decoder_->Decode(
       std::move(buffer),
@@ -656,10 +656,10 @@
   MEDIA_LOG(ERROR, media_log_) << "VideoDecoderPipeline " << msg;
 
   has_error_ = true;
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
   if (buffer_transcryptor_)
     buffer_transcryptor_->Reset(DecoderStatus::Codes::kFailed);
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // BUILDFLAG(IS_CHROMEOS)
   CallFlushCbIfNeeded(DecoderStatus::Codes::kFailed);
 }
 
@@ -877,7 +877,7 @@
                               gfx::NativePixmapHandle::kNoModifier};
 }
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
 void VideoDecoderPipeline::OnBufferTranscrypted(
     scoped_refptr<DecoderBuffer> transcrypted_buffer,
     DecodeCB decode_callback) {
@@ -891,6 +891,6 @@
 
   decoder_->Decode(std::move(transcrypted_buffer), std::move(decode_callback));
 }
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
 }  // namespace media
diff --git a/media/gpu/chromeos/video_decoder_pipeline.h b/media/gpu/chromeos/video_decoder_pipeline.h
index 2e984eb..2435f8ab 100644
--- a/media/gpu/chromeos/video_decoder_pipeline.h
+++ b/media/gpu/chromeos/video_decoder_pipeline.h
@@ -27,9 +27,9 @@
 #include "ui/gfx/native_pixmap.h"
 #include "ui/gfx/native_pixmap_handle.h"
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
 #include "media/gpu/chromeos/decoder_buffer_transcryptor.h"
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // BUILDFLAG(IS_CHROMEOS)
 namespace base {
 class SequencedTaskRunner;
 }
@@ -233,11 +233,11 @@
   // Call |client_flush_cb_| with |status|.
   void CallFlushCbIfNeeded(DecoderStatus status);
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
   // Callback for when transcryption of a buffer completes.
   void OnBufferTranscrypted(scoped_refptr<DecoderBuffer> transcrypted_buffer,
                             DecodeCB decode_callback);
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
   // The client task runner and its sequence checker. All public methods should
   // run on this task runner.
@@ -274,12 +274,12 @@
 
   const std::unique_ptr<MediaLog> media_log_;
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS)
   // The transcryptor for transcrypting DecoderBuffers when needed by the HW
   // decoder implementation.
   std::unique_ptr<DecoderBufferTranscryptor> buffer_transcryptor_
       GUARDED_BY_CONTEXT(decoder_sequence_checker_);
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // BUILDFLAG(IS_CHROMEOS)
 
   // The current video decoder implementation. Valid after initialization is
   // successfully done.
diff --git a/media/gpu/chromeos/video_decoder_pipeline_unittest.cc b/media/gpu/chromeos/video_decoder_pipeline_unittest.cc
index 8e79aea..b3875cb2 100644
--- a/media/gpu/chromeos/video_decoder_pipeline_unittest.cc
+++ b/media/gpu/chromeos/video_decoder_pipeline_unittest.cc
@@ -114,6 +114,7 @@
                void(chromeos::ChromeOsCdmContext::GetScreenResolutionsCB));
   MOCK_METHOD0(GetCdmContextRef, std::unique_ptr<CdmContextRef>());
   MOCK_CONST_METHOD0(UsingArcCdm, bool());
+  MOCK_CONST_METHOD0(IsRemoteCdm, bool());
 };
 // A real implementation of this class would actually hold onto a reference of
 // the owner of the CdmContext to ensure it is not destructed before the
diff --git a/media/gpu/mac/vt_video_decode_accelerator_mac.cc b/media/gpu/mac/vt_video_decode_accelerator_mac.cc
index e7d412b..2779486 100644
--- a/media/gpu/mac/vt_video_decode_accelerator_mac.cc
+++ b/media/gpu/mac/vt_video_decode_accelerator_mac.cc
@@ -2149,7 +2149,6 @@
       gl_params.format = gl_format;
       gl_params.type = GL_UNSIGNED_BYTE;
       gl_params.is_cleared = true;
-      gpu::GLTextureImageBackingHelper::UnpackStateAttribs gl_attribs;
 
       // Making the GL context current before performing below shared image
       // tasks.
@@ -2163,7 +2162,7 @@
       auto shared_image = std::make_unique<gpu::GLImageBacking>(
           gl_image, mailbox, viz_resource_format, plane_size, color_space,
           kTopLeft_GrSurfaceOrigin, kOpaque_SkAlphaType, shared_image_usage,
-          gl_params, gl_attribs, gl_client_.is_passthrough);
+          gl_params, gl_client_.is_passthrough);
 
       const bool success = shared_image_stub->factory()->RegisterBacking(
           std::move(shared_image), /*allow_legacy_mailbox=*/false);
diff --git a/media/gpu/vaapi/vaapi_video_decoder.cc b/media/gpu/vaapi/vaapi_video_decoder.cc
index 0dbf3b9..551b8aa7 100644
--- a/media/gpu/vaapi/vaapi_video_decoder.cc
+++ b/media/gpu/vaapi/vaapi_video_decoder.cc
@@ -946,13 +946,13 @@
   DCHECK(state_ == State::kWaitingForInput);
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-  // We do not need to invoke transcryption if this is coming from ARC since
-  // that will already be done.
+  // We do not need to invoke transcryption if this is coming from a remote CDM
+  // since it will already have been done.
   if (cdm_context_ref_ &&
       cdm_context_ref_->GetCdmContext()->GetChromeOsCdmContext() &&
       cdm_context_ref_->GetCdmContext()
           ->GetChromeOsCdmContext()
-          ->UsingArcCdm()) {
+          ->IsRemoteCdm()) {
     return false;
   }
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/media/mojo/mojom/stable/BUILD.gn b/media/mojo/mojom/stable/BUILD.gn
index 0ccab6e3..6f1b0d3 100644
--- a/media/mojo/mojom/stable/BUILD.gn
+++ b/media/mojo/mojom/stable/BUILD.gn
@@ -31,6 +31,10 @@
     {
       types = [
         {
+          mojom = "media.stable.mojom.CdmContextEvent"
+          cpp = "::media::CdmContext::Event"
+        },
+        {
           mojom = "media.stable.mojom.ColorSpacePrimaryID"
           cpp = "::gfx::ColorSpace::PrimaryID"
         },
@@ -66,6 +70,10 @@
           nullable_is_same_type = true
         },
         {
+          mojom = "media.stable.mojom.DecryptStatus"
+          cpp = "::media::Decryptor::Status"
+        },
+        {
           mojom = "media.stable.mojom.EncryptionScheme"
           cpp = "::media::EncryptionScheme"
         },
diff --git a/media/mojo/mojom/stable/stable_video_decoder.mojom b/media/mojo/mojom/stable/stable_video_decoder.mojom
index a4da00f..83b6bd9 100644
--- a/media/mojo/mojom/stable/stable_video_decoder.mojom
+++ b/media/mojo/mojom/stable/stable_video_decoder.mojom
@@ -7,6 +7,7 @@
 import "media/mojo/mojom/stable/stable_video_decoder_types.mojom";
 import "mojo/public/mojom/base/unguessable_token.mojom";
 import "sandbox/policy/mojom/sandbox.mojom";
+import "ui/gfx/geometry/mojom/geometry.mojom";
 
 // This API is a stable version of VideoDecoder. This is used both by LaCrOS and
 // by out-of-process video decoding to allow the GPU process to forward video
@@ -58,9 +59,37 @@
   OnWaiting@1(WaitingReason reason);
 };
 
-// TODO(b/194429120): Implement when protected content support is integrated.
+// Interface for handling callbacks from the StableCdmContext interface below.
+// Next min method ID: 1
+[Stable, Uuid="a1a73e1f-5297-49a2-a4e5-df875a44b61e"]
+interface CdmContextEventCallback {
+  // Sends the event back to the registrar.
+  EventCallback@0(CdmContextEvent event);
+};
+
+// Maps to the media::CdmContext interface for remoting it to another process.
+// Next MinVersion: 2
+// Next min method ID: 4
 [Stable, Uuid="33c7a00e-2970-41b3-8c7b-f1074a539740"]
 interface StableCdmContext {
+  // Proxies to media::CdmContext::GetChromeOsCdmContext()->GetHwKeyData.
+  [MinVersion=1]
+  GetHwKeyData@0(DecryptConfig decrypt_config, array<uint8> hw_identifier) =>
+      (DecryptStatus status, array<uint8> key_data);
+
+  // Registers an interface for receiving event callbacks. This maps to
+  // media::CdmContext::RegisterEventCB.
+  [MinVersion=1]
+  RegisterEventCallback@1(pending_remote<CdmContextEventCallback> callback);
+
+  // Proxies to media::CdmContext::GetChromeOsCdmContext()->GetHwConfigData.
+  [MinVersion=1]
+  GetHwConfigData@2() => (bool success, array<uint8> config_data);
+
+  // Proxies to media::CdmContext::GetChromeOsCdmContext()->
+  // GetScreenResolutions.
+  [MinVersion=1]
+  GetScreenResolutions@3() => (array<gfx.mojom.Size> resolutions);
 };
 
 // Based on |media.mojom.VideoDecoder|.
diff --git a/media/mojo/mojom/stable/stable_video_decoder_types.mojom b/media/mojo/mojom/stable/stable_video_decoder_types.mojom
index 810c4fc6a..1f9b0058 100644
--- a/media/mojo/mojom/stable/stable_video_decoder_types.mojom
+++ b/media/mojo/mojom/stable/stable_video_decoder_types.mojom
@@ -528,3 +528,21 @@
   mojo_base.mojom.DeprecatedDictionaryValue params@2;
   mojo_base.mojom.TimeTicks time@3;
 };
+
+[Stable, Extensible]
+enum DecryptStatus {
+  kSuccess,
+  kNoKey,
+  [Default] kFailure,
+};
+
+[Stable, Extensible]
+enum CdmContextEvent {
+  // We use |kHasAdditionalUsableKey| as the default since this maps to an
+  // existing enum in Chrome that only has the two values below. Receiving an
+  // event for |kHasAdditionalUsableKey| is always safe because it is a benign
+  // indicator that if something was waiting for a key, it should check again.
+  // It is not an indicator that the key it actually wanted is ready.
+  [Default] kHasAdditionalUsableKey,
+  kHardwareContextReset,
+};
diff --git a/media/mojo/mojom/stable/stable_video_decoder_types_mojom_traits.h b/media/mojo/mojom/stable/stable_video_decoder_types_mojom_traits.h
index ab317b6..63c9a4d 100644
--- a/media/mojo/mojom/stable/stable_video_decoder_types_mojom_traits.h
+++ b/media/mojo/mojom/stable/stable_video_decoder_types_mojom_traits.h
@@ -6,6 +6,7 @@
 #define MEDIA_MOJO_MOJOM_STABLE_STABLE_VIDEO_DECODER_TYPES_MOJOM_TRAITS_H_
 
 #include "base/notreached.h"
+#include "media/base/cdm_context.h"
 #include "media/base/video_frame.h"
 #include "media/base/video_frame_metadata.h"
 #include "media/mojo/mojom/stable/stable_video_decoder_types.mojom.h"
@@ -14,6 +15,37 @@
 namespace mojo {
 
 template <>
+struct EnumTraits<media::stable::mojom::CdmContextEvent,
+                  ::media::CdmContext::Event> {
+  static media::stable::mojom::CdmContextEvent ToMojom(
+      ::media::CdmContext::Event input) {
+    switch (input) {
+      case ::media::CdmContext::Event::kHasAdditionalUsableKey:
+        return media::stable::mojom::CdmContextEvent::kHasAdditionalUsableKey;
+      case ::media::CdmContext::Event::kHardwareContextReset:
+        return media::stable::mojom::CdmContextEvent::kHardwareContextReset;
+    }
+
+    NOTREACHED();
+    return media::stable::mojom::CdmContextEvent::kHasAdditionalUsableKey;
+  }
+
+  static bool FromMojom(media::stable::mojom::CdmContextEvent input,
+                        ::media::CdmContext::Event* output) {
+    switch (input) {
+      case media::stable::mojom::CdmContextEvent::kHasAdditionalUsableKey:
+        *output = ::media::CdmContext::Event::kHasAdditionalUsableKey;
+        return true;
+      case media::stable::mojom::CdmContextEvent::kHardwareContextReset:
+        *output = ::media::CdmContext::Event::kHardwareContextReset;
+        return true;
+    }
+    NOTREACHED();
+    return false;
+  }
+};
+
+template <>
 struct EnumTraits<media::stable::mojom::ColorSpacePrimaryID,
                   gfx::ColorSpace::PrimaryID> {
   static media::stable::mojom::ColorSpacePrimaryID ToMojom(
@@ -508,6 +540,44 @@
 };
 
 template <>
+struct EnumTraits<media::stable::mojom::DecryptStatus,
+                  ::media::Decryptor::Status> {
+  static media::stable::mojom::DecryptStatus ToMojom(
+      ::media::Decryptor::Status input) {
+    switch (input) {
+      case ::media::Decryptor::Status::kSuccess:
+        return media::stable::mojom::DecryptStatus::kSuccess;
+      case ::media::Decryptor::Status::kNoKey:
+        return media::stable::mojom::DecryptStatus::kNoKey;
+      case ::media::Decryptor::Status::kNeedMoreData:
+        return media::stable::mojom::DecryptStatus::kFailure;
+      case ::media::Decryptor::Status::kError:
+        return media::stable::mojom::DecryptStatus::kFailure;
+    }
+
+    NOTREACHED();
+    return media::stable::mojom::DecryptStatus::kFailure;
+  }
+
+  static bool FromMojom(media::stable::mojom::DecryptStatus input,
+                        ::media::Decryptor::Status* output) {
+    switch (input) {
+      case media::stable::mojom::DecryptStatus::kSuccess:
+        *output = ::media::Decryptor::Status::kSuccess;
+        return true;
+      case media::stable::mojom::DecryptStatus::kNoKey:
+        *output = ::media::Decryptor::Status::kNoKey;
+        return true;
+      case media::stable::mojom::DecryptStatus::kFailure:
+        *output = ::media::Decryptor::Status::kError;
+        return true;
+    }
+    NOTREACHED();
+    return false;
+  }
+};
+
+template <>
 struct EnumTraits<media::stable::mojom::EncryptionScheme,
                   ::media::EncryptionScheme> {
   static media::stable::mojom::EncryptionScheme ToMojom(
diff --git a/media/mojo/services/BUILD.gn b/media/mojo/services/BUILD.gn
index 22d16c17..5bfd1e9 100644
--- a/media/mojo/services/BUILD.gn
+++ b/media/mojo/services/BUILD.gn
@@ -116,15 +116,15 @@
     sources += [ "gpu_mojo_media_client_win.cc" ]
   } else if (use_vaapi || use_v4l2_codec) {
     sources += [ "gpu_mojo_media_client_cros.cc" ]
-
-    if (is_chromeos) {
-      deps +=
-          [ "//chromeos/components/cdm_factory_daemon:cdm_factory_daemon_gpu" ]
-    }
   } else {
     sources += [ "gpu_mojo_media_client_stubs.cc" ]
   }
 
+  if (is_chromeos) {
+    deps +=
+        [ "//chromeos/components/cdm_factory_daemon:cdm_factory_daemon_gpu" ]
+  }
+
   if (is_android) {
     sources += [
       "android_mojo_media_client.cc",
diff --git a/media/mojo/services/DEPS b/media/mojo/services/DEPS
index a3c15af..3b5b92e 100644
--- a/media/mojo/services/DEPS
+++ b/media/mojo/services/DEPS
@@ -5,6 +5,12 @@
   "media_manifest\.cc": [
     "+chromecast/common/mojom",
   ],
+  "mojo_cdm_service_context\.h": [
+    "+chromeos/components/cdm_factory_daemon/remote_cdm_context.h",
+  ],
+  "stable_video_decoder_service\.h": [
+    "+chromeos/components/cdm_factory_daemon/remote_cdm_context.h",
+  ],
   "webrtc_video_perf_mojolpm_fuzzer\.cc": [
     "+third_party/libprotobuf-mutator/src/src",
     "+components/leveldb_proto/testing/fake_db.h",
diff --git a/media/mojo/services/mojo_cdm_service_context.cc b/media/mojo/services/mojo_cdm_service_context.cc
index 2ef0f97..e5a4a91 100644
--- a/media/mojo/services/mojo_cdm_service_context.cc
+++ b/media/mojo/services/mojo_cdm_service_context.cc
@@ -44,6 +44,25 @@
   cdm_services_.erase(cdm_id);
 }
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+base::UnguessableToken MojoCdmServiceContext::RegisterRemoteCdmContext(
+    chromeos::RemoteCdmContext* remote_context) {
+  DCHECK(remote_context);
+  base::UnguessableToken cdm_id = GetNextCdmId();
+  remote_cdm_contexts_[cdm_id] = remote_context;
+  DVLOG(1) << __func__ << ": RemoteCdmContext registered with CDM ID "
+           << cdm_id;
+  return cdm_id;
+}
+
+void MojoCdmServiceContext::UnregisterRemoteCdmContext(
+    const base::UnguessableToken& cdm_id) {
+  DVLOG(1) << __func__ << ": cdm_id = " << cdm_id;
+  DCHECK(remote_cdm_contexts_.count(cdm_id));
+  remote_cdm_contexts_.erase(cdm_id);
+}
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
 std::unique_ptr<CdmContextRef> MojoCdmServiceContext::GetCdmContextRef(
     const base::UnguessableToken& cdm_id) {
   DVLOG(1) << __func__ << ": cdm_id = " << cdm_id;
@@ -58,6 +77,13 @@
     return std::make_unique<CdmContextRefImpl>(cdm_service->second->GetCdm());
   }
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  // Try the remote contexts now.
+  auto remote_context = remote_cdm_contexts_.find(cdm_id);
+  if (remote_context != remote_cdm_contexts_.end())
+    return remote_context->second->GetCdmContextRef();
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
   LOG(ERROR) << "CdmContextRef cannot be obtained for CDM ID: " << cdm_id;
   return nullptr;
 }
diff --git a/media/mojo/services/mojo_cdm_service_context.h b/media/mojo/services/mojo_cdm_service_context.h
index 790d290..41114d93 100644
--- a/media/mojo/services/mojo_cdm_service_context.h
+++ b/media/mojo/services/mojo_cdm_service_context.h
@@ -11,9 +11,14 @@
 #include <memory>
 
 #include "base/unguessable_token.h"
+#include "build/chromeos_buildflags.h"
 #include "media/media_buildflags.h"
 #include "media/mojo/services/media_mojo_export.h"
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "chromeos/components/cdm_factory_daemon/remote_cdm_context.h"
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
 namespace media {
 
 class CdmContextRef;
@@ -35,6 +40,21 @@
   // Unregisters the CDM. Must be called before the CDM is destroyed.
   void UnregisterCdm(const base::UnguessableToken& cdm_id);
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  // Registers the |remote_context| and returns a unique (per-process) CDM ID.
+  // This is used with out-of-process video decoding with HWDRM. We run
+  // MojoCdmServiceContext in the GPU process which works with MojoCdmService.
+  // We also run MojoCdmServiceContext in the Video Decoder process which
+  // doesn't use MojoCdmService, which is why we need to deal with
+  // RemoteCdmContext directly.
+  base::UnguessableToken RegisterRemoteCdmContext(
+      chromeos::RemoteCdmContext* remote_context);
+
+  // Unregisters the RemoteCdmContext. Must be called before the
+  // RemoteCdmContext is destroyed.
+  void UnregisterRemoteCdmContext(const base::UnguessableToken& cdm_id);
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
   // Returns the CdmContextRef associated with |cdm_id|.
   std::unique_ptr<CdmContextRef> GetCdmContextRef(
       const base::UnguessableToken& cdm_id);
@@ -42,6 +62,12 @@
  private:
   // A map between CDM ID and MojoCdmService.
   std::map<base::UnguessableToken, MojoCdmService*> cdm_services_;
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  // A map between CDM ID and RemoteCdmContext.
+  std::map<base::UnguessableToken, chromeos::RemoteCdmContext*>
+      remote_cdm_contexts_;
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 };
 
 }  // namespace media
diff --git a/media/mojo/services/stable_video_decoder_factory_service.cc b/media/mojo/services/stable_video_decoder_factory_service.cc
index 5163fae..f77e39e9 100644
--- a/media/mojo/services/stable_video_decoder_factory_service.cc
+++ b/media/mojo/services/stable_video_decoder_factory_service.cc
@@ -118,9 +118,9 @@
         mojo_media_client_.get(), &cdm_service_context_,
         mojo::PendingRemote<stable::mojom::StableVideoDecoder>());
   }
-  video_decoders_.Add(
-      std::make_unique<StableVideoDecoderService>(std::move(dst_video_decoder)),
-      std::move(receiver));
+  video_decoders_.Add(std::make_unique<StableVideoDecoderService>(
+                          std::move(dst_video_decoder), &cdm_service_context_),
+                      std::move(receiver));
 }
 
 }  // namespace media
diff --git a/media/mojo/services/stable_video_decoder_service.cc b/media/mojo/services/stable_video_decoder_service.cc
index ce70dcf1..0a5ea217 100644
--- a/media/mojo/services/stable_video_decoder_service.cc
+++ b/media/mojo/services/stable_video_decoder_service.cc
@@ -9,12 +9,18 @@
 namespace media {
 
 StableVideoDecoderService::StableVideoDecoderService(
-    std::unique_ptr<mojom::VideoDecoder> dst_video_decoder)
+    std::unique_ptr<mojom::VideoDecoder> dst_video_decoder,
+    MojoCdmServiceContext* cdm_service_context)
     : video_decoder_client_receiver_(this),
       media_log_receiver_(this),
       stable_video_frame_handle_releaser_receiver_(this),
       dst_video_decoder_(std::move(dst_video_decoder)),
-      dst_video_decoder_receiver_(dst_video_decoder_.get()) {
+      dst_video_decoder_receiver_(dst_video_decoder_.get())
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+      ,
+      cdm_service_context_(cdm_service_context)
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+{
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   CHECK(!!dst_video_decoder_);
   dst_video_decoder_remote_.Bind(
@@ -23,6 +29,11 @@
 
 StableVideoDecoderService::~StableVideoDecoderService() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  if (cdm_id_)
+    cdm_service_context_->UnregisterRemoteCdmContext(cdm_id_.value());
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 }
 
 void StableVideoDecoderService::GetSupportedConfigs(
@@ -85,17 +96,42 @@
   // The |config| should have been validated at deserialization time.
   DCHECK(config.IsValidConfig());
 
-  // TODO(b/195769334): implement out-of-process video decoding of hardware
-  // protected content.
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  // This can change across initializations, so reset this state.
+  if (cdm_id_) {
+    cdm_service_context_->UnregisterRemoteCdmContext(cdm_id_.value());
+    cdm_id_.reset();
+  }
+  remote_cdm_context_.reset();
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
   if (config.is_encrypted()) {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+    if (!cdm_context) {
+      std::move(callback).Run(DecoderStatus::Codes::kMissingCDM,
+                              /*needs_bitstream_conversion=*/false,
+                              /*max_decode_requests=*/1,
+                              VideoDecoderType::kUnknown);
+      return;
+    }
+    remote_cdm_context_ = base::WrapRefCounted(
+        new chromeos::RemoteCdmContext(std::move(cdm_context)));
+    cdm_id_ = cdm_service_context_->RegisterRemoteCdmContext(
+        remote_cdm_context_.get());
+#else
     std::move(callback).Run(DecoderStatus::Codes::kUnsupportedConfig,
                             /*needs_bitstream_conversion=*/false,
                             /*max_decode_requests=*/1,
                             VideoDecoderType::kUnknown);
     return;
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
   }
-  dst_video_decoder_remote_->Initialize(
-      config, low_delay, /*cdm_id=*/absl::nullopt, std::move(callback));
+
+  // Even though this is in-process, we still need to pass a |cdm_id_|
+  // instead of a media::CdmContext* since this goes through Mojo IPC. This is
+  // why we need to register with the |cdm_service_context_| above.
+  dst_video_decoder_remote_->Initialize(config, low_delay, cdm_id_,
+                                        std::move(callback));
 }
 
 void StableVideoDecoderService::Decode(
diff --git a/media/mojo/services/stable_video_decoder_service.h b/media/mojo/services/stable_video_decoder_service.h
index 20ee73a..0e708221 100644
--- a/media/mojo/services/stable_video_decoder_service.h
+++ b/media/mojo/services/stable_video_decoder_service.h
@@ -5,17 +5,25 @@
 #ifndef MEDIA_MOJO_SERVICES_STABLE_VIDEO_DECODER_SERVICE_H_
 #define MEDIA_MOJO_SERVICES_STABLE_VIDEO_DECODER_SERVICE_H_
 
+#include "base/memory/raw_ptr.h"
 #include "base/sequence_checker.h"
 #include "base/thread_annotations.h"
+#include "base/unguessable_token.h"
+#include "build/chromeos_buildflags.h"
 #include "media/mojo/mojom/media_log.mojom.h"
 #include "media/mojo/mojom/stable/stable_video_decoder.mojom.h"
 #include "media/mojo/mojom/video_decoder.mojom.h"
 #include "media/mojo/services/media_mojo_export.h"
+#include "media/mojo/services/mojo_cdm_service_context.h"
 #include "mojo/public/cpp/bindings/associated_receiver.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "chromeos/components/cdm_factory_daemon/remote_cdm_context.h"
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
 namespace media {
 
 // A StableVideoDecoderService serves as an adapter between the
@@ -44,8 +52,9 @@
       public mojom::VideoDecoderClient,
       public mojom::MediaLog {
  public:
-  explicit StableVideoDecoderService(
-      std::unique_ptr<mojom::VideoDecoder> dst_video_decoder);
+  StableVideoDecoderService(
+      std::unique_ptr<mojom::VideoDecoder> dst_video_decoder,
+      MojoCdmServiceContext* cdm_service_context);
   StableVideoDecoderService(const StableVideoDecoderService&) = delete;
   StableVideoDecoderService& operator=(const StableVideoDecoderService&) =
       delete;
@@ -124,6 +133,18 @@
   mojo::Remote<mojom::VideoDecoder> dst_video_decoder_remote_
       GUARDED_BY_CONTEXT(sequence_checker_);
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  // Used for registering the |remote_cdm_context_| so that it can be resolved
+  // from the |cdm_id_| later.
+  const raw_ptr<MojoCdmServiceContext> cdm_service_context_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+  scoped_refptr<chromeos::RemoteCdmContext> remote_cdm_context_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
+  absl::optional<base::UnguessableToken> cdm_id_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+
   SEQUENCE_CHECKER(sequence_checker_);
 };
 
diff --git a/mojo/core/BUILD.gn b/mojo/core/BUILD.gn
index e3fca5cb..cc2e2444 100644
--- a/mojo/core/BUILD.gn
+++ b/mojo/core/BUILD.gn
@@ -49,6 +49,7 @@
       "configuration.h",
       "connection_params.h",
       "core.h",
+      "core_ipcz.h",
       "data_pipe_consumer_dispatcher.h",
       "data_pipe_control_message.h",
       "data_pipe_producer_dispatcher.h",
@@ -59,6 +60,7 @@
       "handle_signals_state.h",
       "handle_table.h",
       "invitation_dispatcher.h",
+      "ipcz_api.h",
       "message_pipe_dispatcher.h",
       "node_channel.h",
       "node_controller.h",
@@ -79,6 +81,7 @@
       "configuration.cc",
       "connection_params.cc",
       "core.cc",
+      "core_ipcz.cc",
       "data_pipe_consumer_dispatcher.cc",
       "data_pipe_control_message.cc",
       "data_pipe_producer_dispatcher.cc",
@@ -86,6 +89,9 @@
       "entrypoints.cc",
       "handle_table.cc",
       "invitation_dispatcher.cc",
+      "ipcz_api.cc",
+      "ipcz_driver/driver.cc",
+      "ipcz_driver/driver.h",
       "message_pipe_dispatcher.cc",
       "node_channel.cc",
       "node_controller.cc",
@@ -111,6 +117,7 @@
       "//mojo/core/ports",
       "//mojo/public/c/system:headers",
       "//mojo/public/cpp/platform",
+      "//third_party/ipcz/src:ipcz_chromium",
     ]
 
     if (is_fuchsia) {
@@ -284,6 +291,7 @@
   testonly = true
   sources = [
     "channel_unittest.cc",
+    "core_ipcz_test.cc",
     "core_test_base.cc",
     "core_test_base.h",
     "core_unittest.cc",
@@ -322,6 +330,7 @@
     "//mojo/public/cpp/system",
     "//testing/gmock",
     "//testing/gtest",
+    "//third_party/ipcz/src:ipcz_chromium",
   ]
 }
 
diff --git a/mojo/core/DEPS b/mojo/core/DEPS
index 4cc26fb..7dbf802 100644
--- a/mojo/core/DEPS
+++ b/mojo/core/DEPS
@@ -6,6 +6,7 @@
   "+native_client/src/public",
   "+testing",
   "+third_party/ashmem",
+  "+third_party/ipcz",
 
   # internal includes.
   "+mojo",
diff --git a/mojo/core/core_ipcz.cc b/mojo/core/core_ipcz.cc
new file mode 100644
index 0000000..36d4685
--- /dev/null
+++ b/mojo/core/core_ipcz.cc
@@ -0,0 +1,386 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/core/core_ipcz.h"
+
+#include "base/notreached.h"
+#include "base/time/time.h"
+#include "mojo/core/ipcz_api.h"
+#include "third_party/ipcz/include/ipcz/ipcz.h"
+
+namespace mojo::core {
+
+namespace {
+
+extern "C" {
+
+MojoResult MojoInitializeIpcz(const struct MojoInitializeOptions* options) {
+  NOTREACHED();
+  return MOJO_RESULT_OK;
+}
+
+MojoTimeTicks MojoGetTimeTicksNowIpcz() {
+  return base::Time::Now().ToDeltaSinceWindowsEpoch().InMicroseconds();
+}
+
+MojoResult MojoCloseIpcz(MojoHandle handle) {
+  return GetIpczAPI().Close(handle, IPCZ_NO_FLAGS, nullptr);
+}
+
+MojoResult MojoQueryHandleSignalsStateIpcz(
+    MojoHandle handle,
+    MojoHandleSignalsState* signals_state) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoCreateMessagePipeIpcz(
+    const MojoCreateMessagePipeOptions* options,
+    MojoHandle* message_pipe_handle0,
+    MojoHandle* message_pipe_handle1) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoWriteMessageIpcz(MojoHandle message_pipe_handle,
+                                MojoMessageHandle message,
+                                const MojoWriteMessageOptions* options) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoReadMessageIpcz(MojoHandle message_pipe_handle,
+                               const MojoReadMessageOptions* options,
+                               MojoMessageHandle* message) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoFuseMessagePipesIpcz(
+    MojoHandle handle0,
+    MojoHandle handle1,
+    const MojoFuseMessagePipesOptions* options) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoCreateMessageIpcz(const MojoCreateMessageOptions* options,
+                                 MojoMessageHandle* message) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoDestroyMessageIpcz(MojoMessageHandle message) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoSerializeMessageIpcz(
+    MojoMessageHandle message,
+    const MojoSerializeMessageOptions* options) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoAppendMessageDataIpcz(
+    MojoMessageHandle message,
+    uint32_t additional_payload_size,
+    const MojoHandle* handles,
+    uint32_t num_handles,
+    const MojoAppendMessageDataOptions* options,
+    void** buffer,
+    uint32_t* buffer_size) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoGetMessageDataIpcz(MojoMessageHandle message,
+                                  const MojoGetMessageDataOptions* options,
+                                  void** buffer,
+                                  uint32_t* num_bytes,
+                                  MojoHandle* handles,
+                                  uint32_t* num_handles) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoSetMessageContextIpcz(
+    MojoMessageHandle message,
+    uintptr_t context,
+    MojoMessageContextSerializer serializer,
+    MojoMessageContextDestructor destructor,
+    const MojoSetMessageContextOptions* options) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoGetMessageContextIpcz(
+    MojoMessageHandle message,
+    const MojoGetMessageContextOptions* options,
+    uintptr_t* context) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoNotifyBadMessageIpcz(
+    MojoMessageHandle message,
+    const char* error,
+    uint32_t error_num_bytes,
+    const MojoNotifyBadMessageOptions* options) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoCreateDataPipeIpcz(const MojoCreateDataPipeOptions* options,
+                                  MojoHandle* data_pipe_producer_handle,
+                                  MojoHandle* data_pipe_consumer_handle) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoWriteDataIpcz(MojoHandle data_pipe_producer_handle,
+                             const void* elements,
+                             uint32_t* num_elements,
+                             const MojoWriteDataOptions* options) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoBeginWriteDataIpcz(MojoHandle data_pipe_producer_handle,
+                                  const MojoBeginWriteDataOptions* options,
+                                  void** buffer,
+                                  uint32_t* buffer_num_elements) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoEndWriteDataIpcz(MojoHandle data_pipe_producer_handle,
+                                uint32_t num_elements_written,
+                                const MojoEndWriteDataOptions* options) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoReadDataIpcz(MojoHandle data_pipe_consumer_handle,
+                            const MojoReadDataOptions* options,
+                            void* elements,
+                            uint32_t* num_elements) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoBeginReadDataIpcz(MojoHandle data_pipe_consumer_handle,
+                                 const MojoBeginReadDataOptions* options,
+                                 const void** buffer,
+                                 uint32_t* buffer_num_elements) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoEndReadDataIpcz(MojoHandle data_pipe_consumer_handle,
+                               uint32_t num_elements_read,
+                               const MojoEndReadDataOptions* options) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoCreateSharedBufferIpcz(
+    uint64_t num_bytes,
+    const MojoCreateSharedBufferOptions* options,
+    MojoHandle* shared_buffer_handle) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoDuplicateBufferHandleIpcz(
+    MojoHandle buffer_handle,
+    const MojoDuplicateBufferHandleOptions* options,
+    MojoHandle* new_buffer_handle) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoMapBufferIpcz(MojoHandle buffer_handle,
+                             uint64_t offset,
+                             uint64_t num_bytes,
+                             const MojoMapBufferOptions* options,
+                             void** address) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoUnmapBufferIpcz(void* address) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoGetBufferInfoIpcz(MojoHandle buffer_handle,
+                                 const MojoGetBufferInfoOptions* options,
+                                 MojoSharedBufferInfo* info) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoCreateTrapIpcz(MojoTrapEventHandler handler,
+                              const MojoCreateTrapOptions* options,
+                              MojoHandle* trap_handle) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoAddTriggerIpcz(MojoHandle trap_handle,
+                              MojoHandle handle,
+                              MojoHandleSignals signals,
+                              MojoTriggerCondition condition,
+                              uintptr_t context,
+                              const MojoAddTriggerOptions* options) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoRemoveTriggerIpcz(MojoHandle trap_handle,
+                                 uintptr_t context,
+                                 const MojoRemoveTriggerOptions* options) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoArmTrapIpcz(MojoHandle trap_handle,
+                           const MojoArmTrapOptions* options,
+                           uint32_t* num_blocking_events,
+                           MojoTrapEvent* blocking_events) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoWrapPlatformHandleIpcz(
+    const MojoPlatformHandle* platform_handle,
+    const MojoWrapPlatformHandleOptions* options,
+    MojoHandle* mojo_handle) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoUnwrapPlatformHandleIpcz(
+    MojoHandle mojo_handle,
+    const MojoUnwrapPlatformHandleOptions* options,
+    MojoPlatformHandle* platform_handle) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoWrapPlatformSharedMemoryRegionIpcz(
+    const MojoPlatformHandle* platform_handles,
+    uint32_t num_platform_handles,
+    uint64_t num_bytes,
+    const MojoSharedBufferGuid* guid,
+    MojoPlatformSharedMemoryRegionAccessMode access_mode,
+    const MojoWrapPlatformSharedMemoryRegionOptions* options,
+    MojoHandle* mojo_handle) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoUnwrapPlatformSharedMemoryRegionIpcz(
+    MojoHandle mojo_handle,
+    const MojoUnwrapPlatformSharedMemoryRegionOptions* options,
+    MojoPlatformHandle* platform_handles,
+    uint32_t* num_platform_handles,
+    uint64_t* num_bytes,
+    MojoSharedBufferGuid* mojo_guid,
+    MojoPlatformSharedMemoryRegionAccessMode* access_mode) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoCreateInvitationIpcz(const MojoCreateInvitationOptions* options,
+                                    MojoHandle* invitation_handle) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoAttachMessagePipeToInvitationIpcz(
+    MojoHandle invitation_handle,
+    const void* name,
+    uint32_t name_num_bytes,
+    const MojoAttachMessagePipeToInvitationOptions* options,
+    MojoHandle* message_pipe_handle) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoExtractMessagePipeFromInvitationIpcz(
+    MojoHandle invitation_handle,
+    const void* name,
+    uint32_t name_num_bytes,
+    const MojoExtractMessagePipeFromInvitationOptions* options,
+    MojoHandle* message_pipe_handle) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoSendInvitationIpcz(
+    MojoHandle invitation_handle,
+    const MojoPlatformProcessHandle* process_handle,
+    const MojoInvitationTransportEndpoint* transport_endpoint,
+    MojoProcessErrorHandler error_handler,
+    uintptr_t error_handler_context,
+    const MojoSendInvitationOptions* options) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoAcceptInvitationIpcz(
+    const MojoInvitationTransportEndpoint* transport_endpoint,
+    const MojoAcceptInvitationOptions* options,
+    MojoHandle* invitation_handle) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoSetQuotaIpcz(MojoHandle handle,
+                            MojoQuotaType type,
+                            uint64_t limit,
+                            const MojoSetQuotaOptions* options) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoQueryQuotaIpcz(MojoHandle handle,
+                              MojoQuotaType type,
+                              const MojoQueryQuotaOptions* options,
+                              uint64_t* current_limit,
+                              uint64_t* current_usage) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+MojoResult MojoShutdownIpcz(const MojoShutdownOptions* options) {
+  NOTREACHED();
+  return MOJO_RESULT_OK;
+}
+
+MojoResult MojoSetDefaultProcessErrorHandlerIpcz(
+    MojoDefaultProcessErrorHandler handler,
+    const MojoSetDefaultProcessErrorHandlerOptions* options) {
+  return MOJO_RESULT_UNIMPLEMENTED;
+}
+
+}  // extern "C"
+
+MojoSystemThunks2 g_mojo_ipcz_thunks = {
+    sizeof(MojoSystemThunks2),
+    MojoInitializeIpcz,
+    MojoGetTimeTicksNowIpcz,
+    MojoCloseIpcz,
+    MojoQueryHandleSignalsStateIpcz,
+    MojoCreateMessagePipeIpcz,
+    MojoWriteMessageIpcz,
+    MojoReadMessageIpcz,
+    MojoFuseMessagePipesIpcz,
+    MojoCreateMessageIpcz,
+    MojoDestroyMessageIpcz,
+    MojoSerializeMessageIpcz,
+    MojoAppendMessageDataIpcz,
+    MojoGetMessageDataIpcz,
+    MojoSetMessageContextIpcz,
+    MojoGetMessageContextIpcz,
+    MojoNotifyBadMessageIpcz,
+    MojoCreateDataPipeIpcz,
+    MojoWriteDataIpcz,
+    MojoBeginWriteDataIpcz,
+    MojoEndWriteDataIpcz,
+    MojoReadDataIpcz,
+    MojoBeginReadDataIpcz,
+    MojoEndReadDataIpcz,
+    MojoCreateSharedBufferIpcz,
+    MojoDuplicateBufferHandleIpcz,
+    MojoMapBufferIpcz,
+    MojoUnmapBufferIpcz,
+    MojoGetBufferInfoIpcz,
+    MojoCreateTrapIpcz,
+    MojoAddTriggerIpcz,
+    MojoRemoveTriggerIpcz,
+    MojoArmTrapIpcz,
+    MojoWrapPlatformHandleIpcz,
+    MojoUnwrapPlatformHandleIpcz,
+    MojoWrapPlatformSharedMemoryRegionIpcz,
+    MojoUnwrapPlatformSharedMemoryRegionIpcz,
+    MojoCreateInvitationIpcz,
+    MojoAttachMessagePipeToInvitationIpcz,
+    MojoExtractMessagePipeFromInvitationIpcz,
+    MojoSendInvitationIpcz,
+    MojoAcceptInvitationIpcz,
+    MojoSetQuotaIpcz,
+    MojoQueryQuotaIpcz,
+    MojoShutdownIpcz,
+    MojoSetDefaultProcessErrorHandlerIpcz};
+
+}  // namespace
+
+const MojoSystemThunks2* GetMojoIpczImpl() {
+  return &g_mojo_ipcz_thunks;
+}
+
+}  // namespace mojo::core
diff --git a/mojo/core/core_ipcz.h b/mojo/core/core_ipcz.h
new file mode 100644
index 0000000..9519d64
--- /dev/null
+++ b/mojo/core/core_ipcz.h
@@ -0,0 +1,17 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_CORE_CORE_IPCZ_H_
+#define MOJO_CORE_CORE_IPCZ_H_
+
+#include "mojo/core/system_impl_export.h"
+#include "mojo/public/c/system/thunks.h"
+
+namespace mojo::core {
+
+MOJO_SYSTEM_IMPL_EXPORT const MojoSystemThunks2* GetMojoIpczImpl();
+
+}  // namespace mojo::core
+
+#endif  // MOJO_CORE_CORE_IPCZ_H_
diff --git a/mojo/core/core_ipcz_test.cc b/mojo/core/core_ipcz_test.cc
new file mode 100644
index 0000000..460b52c
--- /dev/null
+++ b/mojo/core/core_ipcz_test.cc
@@ -0,0 +1,52 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/core/core_ipcz.h"
+
+#include "base/check.h"
+#include "mojo/core/ipcz_api.h"
+#include "mojo/public/c/system/thunks.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo::core {
+namespace {
+
+// Basic smoke tests for the Mojo Core API as implemented over ipcz.
+class CoreIpczTest : public testing::Test {
+ public:
+  const MojoSystemThunks2& mojo() const { return *mojo_; }
+  const IpczAPI& ipcz() const { return GetIpczAPI(); }
+  IpczHandle node() const { return GetIpczNode(); }
+
+  CoreIpczTest() { CHECK(InitializeIpczNodeForProcess({.is_broker = true})); }
+  ~CoreIpczTest() override { DestroyIpczNodeForProcess(); }
+
+ private:
+  const MojoSystemThunks2* const mojo_{GetMojoIpczImpl()};
+};
+
+TEST_F(CoreIpczTest, Close) {
+  // With ipcz-based Mojo Core, Mojo handles are ipcz handles. So Mojo Close()
+  // forwards to ipcz Close().
+
+  IpczHandle a, b;
+  EXPECT_EQ(IPCZ_RESULT_OK,
+            ipcz().OpenPortals(node(), IPCZ_NO_FLAGS, nullptr, &a, &b));
+
+  IpczPortalStatus status = {.size = sizeof(status)};
+  EXPECT_EQ(IPCZ_RESULT_OK,
+            ipcz().QueryPortalStatus(b, IPCZ_NO_FLAGS, nullptr, &status));
+  EXPECT_FALSE(status.flags & IPCZ_PORTAL_STATUS_PEER_CLOSED);
+
+  EXPECT_EQ(MOJO_RESULT_OK, mojo().Close(a));
+
+  EXPECT_EQ(IPCZ_RESULT_OK,
+            ipcz().QueryPortalStatus(b, IPCZ_NO_FLAGS, nullptr, &status));
+  EXPECT_TRUE(status.flags & IPCZ_PORTAL_STATUS_PEER_CLOSED);
+
+  EXPECT_EQ(MOJO_RESULT_OK, mojo().Close(b));
+}
+
+}  // namespace
+}  // namespace mojo::core
diff --git a/mojo/core/ipcz_api.cc b/mojo/core/ipcz_api.cc
new file mode 100644
index 0000000..e52fe0a
--- /dev/null
+++ b/mojo/core/ipcz_api.cc
@@ -0,0 +1,60 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/core/ipcz_api.h"
+
+#include "base/check_op.h"
+#include "mojo/core/ipcz_driver/driver.h"
+#include "third_party/ipcz/include/ipcz/ipcz.h"
+#include "third_party/ipcz/src/api.h"
+
+namespace mojo::core {
+
+namespace {
+
+class IpczAPIInitializer {
+ public:
+  explicit IpczAPIInitializer(IpczAPI& api) {
+    IpczResult result = IpczGetAPI(&api);
+    CHECK_EQ(result, IPCZ_RESULT_OK);
+  }
+};
+
+IpczHandle g_node = IPCZ_INVALID_HANDLE;
+IpczNodeOptions g_options = {.is_broker = false,
+                             .use_local_shared_memory_allocation = false};
+
+}  // namespace
+
+const IpczAPI& GetIpczAPI() {
+  static IpczAPI api = {sizeof(api)};
+  static IpczAPIInitializer initializer(api);
+  return api;
+}
+
+IpczHandle GetIpczNode() {
+  return g_node;
+}
+
+bool InitializeIpczNodeForProcess(const IpczNodeOptions& options) {
+  g_options = options;
+  IpczCreateNodeFlags flags =
+      options.is_broker ? IPCZ_CREATE_NODE_AS_BROKER : IPCZ_NO_FLAGS;
+  IpczResult result =
+      GetIpczAPI().CreateNode(&ipcz_driver::kDriver, IPCZ_INVALID_DRIVER_HANDLE,
+                              flags, nullptr, &g_node);
+  return result == IPCZ_RESULT_OK;
+}
+
+void DestroyIpczNodeForProcess() {
+  CHECK(g_node);
+  GetIpczAPI().Close(g_node, IPCZ_NO_FLAGS, nullptr);
+  g_node = IPCZ_INVALID_HANDLE;
+}
+
+const IpczNodeOptions& GetIpczNodeOptions() {
+  return g_options;
+}
+
+}  // namespace mojo::core
diff --git a/mojo/core/ipcz_api.h b/mojo/core/ipcz_api.h
new file mode 100644
index 0000000..3dd74fb
--- /dev/null
+++ b/mojo/core/ipcz_api.h
@@ -0,0 +1,39 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_CORE_IPCZ_API_H_
+#define MOJO_CORE_IPCZ_API_H_
+
+#include "mojo/core/system_impl_export.h"
+#include "third_party/ipcz/include/ipcz/ipcz.h"
+
+namespace mojo::core {
+
+// Returns a reference to the global ipcz implementation.
+MOJO_SYSTEM_IMPL_EXPORT const IpczAPI& GetIpczAPI();
+
+// Returns a handle to the global ipcz node for the calling process, as
+// initialized by InitializeIpczForProcess().
+MOJO_SYSTEM_IMPL_EXPORT IpczHandle GetIpczNode();
+
+// Initializes a global ipcz node for the calling process with a set of options
+// to configure the node.
+struct IpczNodeOptions {
+  bool is_broker;
+  bool use_local_shared_memory_allocation;
+};
+MOJO_SYSTEM_IMPL_EXPORT bool InitializeIpczNodeForProcess(
+    const IpczNodeOptions& options = {});
+
+// Used by tests to tear down global state initialized by
+// InitializeIpczNodeForProcess().
+MOJO_SYSTEM_IMPL_EXPORT void DestroyIpczNodeForProcess();
+
+// Retrieves the global ipcz node options configured by a call to
+// InitializeIpczNodeForProcess().
+MOJO_SYSTEM_IMPL_EXPORT const IpczNodeOptions& GetIpczNodeOptions();
+
+}  // namespace mojo::core
+
+#endif  // MOJO_CORE_IPCZ_API_H_
diff --git a/mojo/core/ipcz_driver/driver.cc b/mojo/core/ipcz_driver/driver.cc
new file mode 100644
index 0000000..25229d9
--- /dev/null
+++ b/mojo/core/ipcz_driver/driver.cc
@@ -0,0 +1,134 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/core/ipcz_driver/driver.h"
+
+#include "base/rand_util.h"
+#include "third_party/ipcz/include/ipcz/ipcz.h"
+
+namespace mojo::core::ipcz_driver {
+
+namespace {
+
+IpczResult IPCZ_API Close(IpczDriverHandle handle,
+                          uint32_t flags,
+                          const void* options) {
+  return IPCZ_RESULT_UNIMPLEMENTED;
+}
+
+IpczResult IPCZ_API Serialize(IpczDriverHandle handle,
+                              IpczDriverHandle transport_handle,
+                              uint32_t flags,
+                              const void* options,
+                              void* data,
+                              size_t* num_bytes,
+                              IpczDriverHandle* handles,
+                              size_t* num_handles) {
+  return IPCZ_RESULT_UNIMPLEMENTED;
+}
+
+IpczResult IPCZ_API Deserialize(const void* data,
+                                size_t num_bytes,
+                                const IpczDriverHandle* handles,
+                                size_t num_handles,
+                                IpczDriverHandle transport_handle,
+                                uint32_t flags,
+                                const void* options,
+                                IpczDriverHandle* driver_handle) {
+  return IPCZ_RESULT_UNIMPLEMENTED;
+}
+
+IpczResult IPCZ_API CreateTransports(IpczDriverHandle transport0,
+                                     IpczDriverHandle transport1,
+                                     uint32_t flags,
+                                     const void* options,
+                                     IpczDriverHandle* new_transport0,
+                                     IpczDriverHandle* new_transport1) {
+  return IPCZ_RESULT_UNIMPLEMENTED;
+}
+
+IpczResult IPCZ_API
+ActivateTransport(IpczDriverHandle driver_transport,
+                  IpczHandle ipcz_transport,
+                  IpczTransportActivityHandler activity_handler,
+                  uint32_t flags,
+                  const void* options) {
+  return IPCZ_RESULT_UNIMPLEMENTED;
+}
+
+IpczResult IPCZ_API DeactivateTransport(IpczDriverHandle driver_transport,
+                                        uint32_t flags,
+                                        const void* options) {
+  return IPCZ_RESULT_UNIMPLEMENTED;
+}
+
+IpczResult IPCZ_API Transmit(IpczDriverHandle driver_transport,
+                             const void* data,
+                             size_t num_bytes,
+                             const IpczDriverHandle* handles,
+                             size_t num_handles,
+                             uint32_t flags,
+                             const void* options) {
+  return IPCZ_RESULT_UNIMPLEMENTED;
+}
+
+IpczResult IPCZ_API AllocateSharedMemory(size_t num_bytes,
+                                         uint32_t flags,
+                                         const void* options,
+                                         IpczDriverHandle* driver_memory) {
+  return IPCZ_RESULT_UNIMPLEMENTED;
+}
+
+IpczResult IPCZ_API GetSharedMemoryInfo(IpczDriverHandle driver_memory,
+                                        uint32_t flags,
+                                        const void* options,
+                                        IpczSharedMemoryInfo* info) {
+  return IPCZ_RESULT_UNIMPLEMENTED;
+}
+
+IpczResult IPCZ_API DuplicateSharedMemory(IpczDriverHandle driver_memory,
+                                          uint32_t flags,
+                                          const void* options,
+                                          IpczDriverHandle* new_driver_memory) {
+  return IPCZ_RESULT_UNIMPLEMENTED;
+}
+
+IpczResult IPCZ_API MapSharedMemory(IpczDriverHandle driver_memory,
+                                    uint32_t flags,
+                                    const void* options,
+                                    void** address,
+                                    IpczDriverHandle* driver_mapping) {
+  return IPCZ_RESULT_UNIMPLEMENTED;
+}
+
+IpczResult IPCZ_API GenerateRandomBytes(size_t num_bytes,
+                                        uint32_t flags,
+                                        const void* options,
+                                        void* buffer) {
+  if (!buffer || !num_bytes) {
+    return IPCZ_RESULT_INVALID_ARGUMENT;
+  }
+  base::RandBytes(buffer, num_bytes);
+  return IPCZ_RESULT_OK;
+}
+
+}  // namespace
+
+const IpczDriver kDriver = {
+    sizeof(kDriver),
+    Close,
+    Serialize,
+    Deserialize,
+    CreateTransports,
+    ActivateTransport,
+    DeactivateTransport,
+    Transmit,
+    AllocateSharedMemory,
+    GetSharedMemoryInfo,
+    DuplicateSharedMemory,
+    MapSharedMemory,
+    GenerateRandomBytes,
+};
+
+}  // namespace mojo::core::ipcz_driver
diff --git a/mojo/core/ipcz_driver/driver.h b/mojo/core/ipcz_driver/driver.h
new file mode 100644
index 0000000..b5190db
--- /dev/null
+++ b/mojo/core/ipcz_driver/driver.h
@@ -0,0 +1,23 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_CORE_IPCZ_DRIVER_DRIVER_H_
+#define MOJO_CORE_IPCZ_DRIVER_DRIVER_H_
+
+#include "mojo/core/system_impl_export.h"
+#include "third_party/ipcz/include/ipcz/ipcz.h"
+
+namespace mojo::core::ipcz_driver {
+
+// The IpczDriver implementation provided by Mojo. This driver uses a transport
+// based on mojo::core::Channel, and shared memory is implemented using //base
+// shared memory APIs.
+//
+// The driver also supports boxing of platform handles and shared memory regions
+// to simplify the transition of the Mojo bindings implementation to ipcz.
+MOJO_SYSTEM_IMPL_EXPORT extern const IpczDriver kDriver;
+
+}  // namespace mojo::core::ipcz_driver
+
+#endif  // MOJO_CORE_IPCZ_DRIVER_DRIVER_H_
diff --git a/mojo/public/DEPS b/mojo/public/DEPS
index 8bed1ad..f9b6d57 100644
--- a/mojo/public/DEPS
+++ b/mojo/public/DEPS
@@ -3,6 +3,7 @@
   "+build",
   "+mojo/public",
   "+testing",
+  "+third_party/ipcz/include",
 
   "-mojo",
   "+mojo/public",
diff --git a/net/base/features.cc b/net/base/features.cc
index 2b6b99c..b4154a26 100644
--- a/net/base/features.cc
+++ b/net/base/features.cc
@@ -336,6 +336,10 @@
 const base::FeatureParam<int> kStorageAccessAPIImplicitGrantLimit{
     &kStorageAccessAPI, "storage-access-api-implicit-grant-limit",
     kStorageAccessAPIDefaultImplicitGrantLimit};
+const base::FeatureParam<bool> kStorageAccessAPIGrantsUnpartitionedStorage(
+    &kStorageAccessAPI,
+    "storage-access-api-grants-unpartitioned-storage",
+    false);
 
 // Enables partitioning of third party storage (IndexedDB, CacheStorage, etc.)
 // by the top level site to reduce fingerprinting.
diff --git a/net/base/features.h b/net/base/features.h
index 5807d3e..f01a1d2 100644
--- a/net/base/features.h
+++ b/net/base/features.h
@@ -476,8 +476,14 @@
 NET_EXPORT extern const int kStorageAccessAPIDefaultImplicitGrantLimit;
 NET_EXPORT extern const base::FeatureParam<int>
     kStorageAccessAPIImplicitGrantLimit;
+// Whether the Storage Access API can grant access to storage (even if it is
+// unpartitioned). When this feature is disabled, access to storage is only
+// granted if the storage is partitioned.
+NET_EXPORT extern const base::FeatureParam<bool>
+    kStorageAccessAPIGrantsUnpartitionedStorage;
 
 NET_EXPORT extern const base::Feature kThirdPartyStoragePartitioning;
+
 }  // namespace net::features
 
 #endif  // NET_BASE_FEATURES_H_
diff --git a/net/cookies/cookie_monster.cc b/net/cookies/cookie_monster.cc
index 65225db5..c433232 100644
--- a/net/cookies/cookie_monster.cc
+++ b/net/cookies/cookie_monster.cc
@@ -2265,6 +2265,20 @@
   // Can be up to kMaxCookies.
   UMA_HISTOGRAM_COUNTS_10000("Cookie.NumKeys", num_keys_);
 
+  std::map<std::string, size_t> n_same_site_none_cookies;
+  for (const auto& [host_key, host_cookie] : cookies_) {
+    if (!host_cookie || !host_cookie->IsEffectivelySameSiteNone())
+      continue;
+    n_same_site_none_cookies[host_key]++;
+  }
+  size_t max_n_cookies = 0;
+  for (const auto& entry : n_same_site_none_cookies) {
+    max_n_cookies = std::max(max_n_cookies, entry.second);
+  }
+  // Can be up to 180 cookies, the max per-domain.
+  base::UmaHistogramCounts1000("Cookie.MaxSameSiteNoneCookiesPerKey",
+                               max_n_cookies);
+
   // Collect stats for partitioned cookies if they are enabled.
   if (base::FeatureList::IsEnabled(features::kPartitionedCookies)) {
     base::UmaHistogramCounts1000("Cookie.PartitionCount",
diff --git a/net/cookies/cookie_monster_unittest.cc b/net/cookies/cookie_monster_unittest.cc
index 6da3a84c..bdf4289 100644
--- a/net/cookies/cookie_monster_unittest.cc
+++ b/net/cookies/cookie_monster_unittest.cc
@@ -3469,6 +3469,57 @@
   }
 }
 
+TEST_F(CookieMonsterTest, MaxSameSiteNoneCookiesPerKey) {
+  const char kHistogramName[] = "Cookie.MaxSameSiteNoneCookiesPerKey";
+
+  auto store = base::MakeRefCounted<MockPersistentCookieStore>();
+  auto cm = std::make_unique<CookieMonster>(store.get(), net::NetLog::Get(),
+                                            kFirstPartySetsDefault);
+  ASSERT_EQ(0u, GetAllCookies(cm.get()).size());
+
+  {  // Only SameSite cookies should not log a sample.
+    base::HistogramTester histogram_tester;
+
+    ASSERT_TRUE(CreateAndSetCookie(cm.get(), GURL("https://domain1.test"),
+                                   "A=1;SameSite=Lax",
+                                   CookieOptions::MakeAllInclusive()));
+    ASSERT_EQ(1u, GetAllCookies(cm.get()).size());
+    ASSERT_TRUE(cm->DoRecordPeriodicStatsForTesting());
+    histogram_tester.ExpectUniqueSample(kHistogramName, 0 /* sample */,
+                                        1 /* count */);
+  }
+
+  {  // SameSite=None cookie should log a sample.
+    base::HistogramTester histogram_tester;
+
+    ASSERT_TRUE(CreateAndSetCookie(cm.get(), GURL("https://domain1.test"),
+                                   "B=2;SameSite=None;Secure",
+                                   CookieOptions::MakeAllInclusive()));
+    ASSERT_EQ(2u, GetAllCookies(cm.get()).size());
+    ASSERT_TRUE(cm->DoRecordPeriodicStatsForTesting());
+    histogram_tester.ExpectUniqueSample(kHistogramName, 1 /* sample */,
+                                        1 /* count */);
+  }
+
+  {  // Should log the maximum number of SameSite=None cookies.
+    base::HistogramTester histogram_tester;
+
+    ASSERT_TRUE(CreateAndSetCookie(cm.get(), GURL("https://domain2.test"),
+                                   "A=1;SameSite=None;Secure",
+                                   CookieOptions::MakeAllInclusive()));
+    ASSERT_TRUE(CreateAndSetCookie(cm.get(), GURL("https://domain2.test"),
+                                   "B=2;SameSite=None;Secure",
+                                   CookieOptions::MakeAllInclusive()));
+    ASSERT_TRUE(CreateAndSetCookie(cm.get(), GURL("https://domain3.test"),
+                                   "A=1;SameSite=None;Secure",
+                                   CookieOptions::MakeAllInclusive()));
+    ASSERT_EQ(5u, GetAllCookies(cm.get()).size());
+    ASSERT_TRUE(cm->DoRecordPeriodicStatsForTesting());
+    histogram_tester.ExpectUniqueSample(kHistogramName, 2 /* sample */,
+                                        1 /* count */);
+  }
+}
+
 // Test that localhost URLs can set and get secure cookies, even if
 // non-cryptographic.
 TEST_F(CookieMonsterTest, SecureCookieLocalhost) {
@@ -5308,12 +5359,14 @@
   net::SchemefulSite member4(GURL("https://member4.test"));
 
   access_delegate_->SetFirstPartySets({
-      {owner1, net::FirstPartySetEntry(owner1, net::SiteType::kPrimary)},
-      {member1, net::FirstPartySetEntry(owner1, net::SiteType::kAssociated)},
-      {member2, net::FirstPartySetEntry(owner1, net::SiteType::kAssociated)},
-      {owner2, net::FirstPartySetEntry(owner2, net::SiteType::kPrimary)},
-      {member3, net::FirstPartySetEntry(owner2, net::SiteType::kAssociated)},
-      {member4, net::FirstPartySetEntry(owner2, net::SiteType::kAssociated)},
+      {owner1,
+       net::FirstPartySetEntry(owner1, net::SiteType::kPrimary, absl::nullopt)},
+      {member1, net::FirstPartySetEntry(owner1, net::SiteType::kAssociated, 0)},
+      {member2, net::FirstPartySetEntry(owner1, net::SiteType::kAssociated, 1)},
+      {owner2,
+       net::FirstPartySetEntry(owner2, net::SiteType::kPrimary, absl::nullopt)},
+      {member3, net::FirstPartySetEntry(owner2, net::SiteType::kAssociated, 0)},
+      {member4, net::FirstPartySetEntry(owner2, net::SiteType::kAssociated, 1)},
   });
 
   ASSERT_TRUE(SetCookie(cm(), GURL("https://owner1.test"), kValidCookieLine));
diff --git a/net/cookies/cookie_partition_key_collection_unittest.cc b/net/cookies/cookie_partition_key_collection_unittest.cc
index 30df324..01a9bad 100644
--- a/net/cookies/cookie_partition_key_collection_unittest.cc
+++ b/net/cookies/cookie_partition_key_collection_unittest.cc
@@ -113,10 +113,10 @@
 
   TestCookieAccessDelegate delegate;
   delegate.SetFirstPartySets({
-      {kOwnerSite,
-       net::FirstPartySetEntry(kOwnerSite, net::SiteType::kPrimary)},
+      {kOwnerSite, net::FirstPartySetEntry(kOwnerSite, net::SiteType::kPrimary,
+                                           absl::nullopt)},
       {kMemberSite,
-       net::FirstPartySetEntry(kOwnerSite, net::SiteType::kAssociated)},
+       net::FirstPartySetEntry(kOwnerSite, net::SiteType::kAssociated, 0)},
   });
 
   CookiePartitionKeyCollection empty_key_collection;
diff --git a/net/cookies/first_party_set_entry.cc b/net/cookies/first_party_set_entry.cc
index 689789b..0bb5cca 100644
--- a/net/cookies/first_party_set_entry.cc
+++ b/net/cookies/first_party_set_entry.cc
@@ -10,11 +10,33 @@
 
 namespace net {
 
+FirstPartySetEntry::SiteIndex::SiteIndex() = default;
+
+FirstPartySetEntry::SiteIndex::SiteIndex(uint32_t value) : value_(value) {}
+
+bool FirstPartySetEntry::SiteIndex::operator==(const SiteIndex& other) const {
+  return value_ == other.value_;
+}
+
 FirstPartySetEntry::FirstPartySetEntry() = default;
 
+FirstPartySetEntry::FirstPartySetEntry(
+    SchemefulSite primary,
+    SiteType site_type,
+    absl::optional<FirstPartySetEntry::SiteIndex> site_index)
+    : primary_(primary), site_type_(site_type), site_index_(site_index) {
+  if (site_type_ == SiteType::kPrimary) {
+    DCHECK(!site_index_.has_value());
+  }
+}
+
 FirstPartySetEntry::FirstPartySetEntry(SchemefulSite primary,
-                                       SiteType site_type)
-    : primary_(primary), site_type_(site_type) {}
+                                       SiteType site_type,
+                                       uint32_t site_index)
+    : FirstPartySetEntry(
+          primary,
+          site_type,
+          absl::make_optional(FirstPartySetEntry::SiteIndex(site_index))) {}
 
 FirstPartySetEntry::FirstPartySetEntry(const FirstPartySetEntry&) = default;
 FirstPartySetEntry& FirstPartySetEntry::operator=(const FirstPartySetEntry&) =
@@ -26,17 +48,29 @@
 FirstPartySetEntry::~FirstPartySetEntry() = default;
 
 bool FirstPartySetEntry::operator==(const FirstPartySetEntry& other) const {
-  return std::tie(primary_, site_type_) ==
-         std::tie(other.primary_, other.site_type_);
+  return std::tie(primary_, site_type_, site_index_) ==
+         std::tie(other.primary_, other.site_type_, other.site_index_);
 }
 
 bool FirstPartySetEntry::operator!=(const FirstPartySetEntry& other) const {
   return !(*this == other);
 }
 
+std::ostream& operator<<(std::ostream& os,
+                         const FirstPartySetEntry::SiteIndex& index) {
+  os << index.value();
+  return os;
+}
+
 std::ostream& operator<<(std::ostream& os, const FirstPartySetEntry& entry) {
   os << "{" << entry.primary() << ", " << static_cast<int>(entry.site_type())
-     << "}";
+     << ", ";
+  if (entry.site_index().has_value()) {
+    os << entry.site_index().value();
+  } else {
+    os << "{}";
+  }
+  os << "}";
   return os;
 }
 
diff --git a/net/cookies/first_party_set_entry.h b/net/cookies/first_party_set_entry.h
index 458b05a..d4a8b7e 100644
--- a/net/cookies/first_party_set_entry.h
+++ b/net/cookies/first_party_set_entry.h
@@ -23,11 +23,28 @@
 // First-Party Set.
 class NET_EXPORT FirstPartySetEntry {
  public:
-  FirstPartySetEntry();
+  class NET_EXPORT SiteIndex {
+   public:
+    SiteIndex();
+    explicit SiteIndex(uint32_t value);
 
+    bool operator==(const SiteIndex& other) const;
+
+    uint32_t value() const { return value_; }
+
+   private:
+    uint32_t value_;
+  };
+
+  FirstPartySetEntry();
   // `primary` is the primary site in the First-Party Set associated with this
   // entry.
-  FirstPartySetEntry(SchemefulSite primary, SiteType site_type);
+  FirstPartySetEntry(SchemefulSite primary,
+                     SiteType site_type,
+                     absl::optional<SiteIndex> site_index);
+  FirstPartySetEntry(SchemefulSite primary,
+                     SiteType site_type,
+                     uint32_t site_index);
 
   FirstPartySetEntry(const FirstPartySetEntry&);
   FirstPartySetEntry& operator=(const FirstPartySetEntry&);
@@ -43,11 +60,22 @@
 
   SiteType site_type() const { return site_type_; }
 
+  const absl::optional<SiteIndex>& site_index() const { return site_index_; }
+
  private:
+  // The primary site associated with this site's set.
   SchemefulSite primary_;
+  // The type associated with this site.
   SiteType site_type_;
+  // The index of this site in the set declaration, if a meaningful index
+  // exists. Primary sites do not have indices, nor do sites that were defined
+  // or affected by an enterprise policy set.
+  absl::optional<SiteIndex> site_index_;
 };
 
+NET_EXPORT std::ostream& operator<<(
+    std::ostream& os,
+    const FirstPartySetEntry::SiteIndex& site_index);
 NET_EXPORT std::ostream& operator<<(std::ostream& os,
                                     const FirstPartySetEntry& fpse);
 
diff --git a/net/http/http_version.h b/net/http/http_version.h
index 13864a6..29cbf81 100644
--- a/net/http/http_version.h
+++ b/net/http/http_version.h
@@ -16,7 +16,8 @@
   HttpVersion() : value_(0) { }
 
   // Build from unsigned major/minor pair.
-  HttpVersion(uint16_t major, uint16_t minor) : value_(major << 16 | minor) {}
+  HttpVersion(uint16_t major, uint16_t minor)
+      : value_(static_cast<uint32_t>(major << 16) | minor) {}
 
   // Major version number.
   uint16_t major_value() const { return value_ >> 16; }
diff --git a/net/traffic_annotation/network_traffic_annotation.h b/net/traffic_annotation/network_traffic_annotation.h
index 1d01213..108717e 100644
--- a/net/traffic_annotation/network_traffic_annotation.h
+++ b/net/traffic_annotation/network_traffic_annotation.h
@@ -21,8 +21,9 @@
 // Recursively compute hash code of the given string as a constant expression.
 template <int N>
 constexpr uint32_t recursive_hash(const char* str) {
-  return static_cast<uint32_t>((recursive_hash<N - 1>(str) * 31 + str[N - 1]) %
-                               138003713);
+  return (recursive_hash<N - 1>(str) * 31u +
+          static_cast<uint32_t>(str[N - 1])) %
+         138003713u;
 }
 
 // Recursion stopper for the above function. Note that string of size 0 will
diff --git a/net/url_request/url_request_http_job_unittest.cc b/net/url_request/url_request_http_job_unittest.cc
index 9a19dd7..8d85ff0 100644
--- a/net/url_request/url_request_http_job_unittest.cc
+++ b/net/url_request/url_request_http_job_unittest.cc
@@ -1923,10 +1923,10 @@
       /*first_party_sets_enabled=*/false);
   auto cookie_access_delegate = std::make_unique<TestCookieAccessDelegate>();
   cookie_access_delegate->SetFirstPartySets({
-      {kOwnerSite,
-       net::FirstPartySetEntry(kOwnerSite, net::SiteType::kPrimary)},
+      {kOwnerSite, net::FirstPartySetEntry(kOwnerSite, net::SiteType::kPrimary,
+                                           absl::nullopt)},
       {kMemberSite,
-       net::FirstPartySetEntry(kOwnerSite, net::SiteType::kAssociated)},
+       net::FirstPartySetEntry(kOwnerSite, net::SiteType::kAssociated, 0)},
   });
   cookie_monster->SetCookieAccessDelegate(std::move(cookie_access_delegate));
   context_builder->SetCookieStore(std::move(cookie_monster));
diff --git a/services/cert_verifier/BUILD.gn b/services/cert_verifier/BUILD.gn
index bb1d831..0852a382 100644
--- a/services/cert_verifier/BUILD.gn
+++ b/services/cert_verifier/BUILD.gn
@@ -70,18 +70,3 @@
     "//testing/gtest",
   ]
 }
-
-source_set("test_support") {
-  testonly = true
-
-  sources = [
-    "test_cert_verifier_service_factory.cc",
-    "test_cert_verifier_service_factory.h",
-  ]
-
-  deps = [
-    ":lib",
-    "//base",
-    "//net",
-  ]
-}
diff --git a/services/network/cookie_manager_unittest.cc b/services/network/cookie_manager_unittest.cc
index 705f07ef..a426f12 100644
--- a/services/network/cookie_manager_unittest.cc
+++ b/services/network/cookie_manager_unittest.cc
@@ -2960,10 +2960,11 @@
     delegate_ = std::make_unique<net::TestCookieAccessDelegate>();
     first_party_sets_.insert(
         {owner_site_,
-         net::FirstPartySetEntry(owner_site_, net::SiteType::kPrimary)});
+         net::FirstPartySetEntry(owner_site_, net::SiteType::kPrimary,
+                                 absl::nullopt)});
     first_party_sets_.insert(
         {member_site_,
-         net::FirstPartySetEntry(owner_site_, net::SiteType::kAssociated)});
+         net::FirstPartySetEntry(owner_site_, net::SiteType::kAssociated, 0)});
     delegate_->SetFirstPartySets(first_party_sets_);
     cookie_store()->SetCookieAccessDelegate(std::move(delegate_));
   }
diff --git a/services/network/cookie_settings.cc b/services/network/cookie_settings.cc
index 98856bb..bc8267d 100644
--- a/services/network/cookie_settings.cc
+++ b/services/network/cookie_settings.cc
@@ -14,6 +14,8 @@
 #include "base/ranges/algorithm.h"
 #include "base/stl_util.h"
 #include "components/content_settings/core/common/content_settings.h"
+#include "components/content_settings/core/common/cookie_settings_base.h"
+#include "net/base/features.h"
 #include "net/base/net_errors.h"
 #include "net/base/network_delegate.h"
 #include "net/cookies/canonical_cookie.h"
diff --git a/services/network/first_party_sets/first_party_sets_access_delegate_unittest.cc b/services/network/first_party_sets/first_party_sets_access_delegate_unittest.cc
index 596ac68..53a4356 100644
--- a/services/network/first_party_sets/first_party_sets_access_delegate_unittest.cc
+++ b/services/network/first_party_sets/first_party_sets_access_delegate_unittest.cc
@@ -73,15 +73,15 @@
             &first_party_sets_manager_) {
     first_party_sets_manager_.SetCompleteSets({
         {kSet1Member1,
-         net::FirstPartySetEntry(kSet1Owner, net::SiteType::kAssociated)},
+         net::FirstPartySetEntry(kSet1Owner, net::SiteType::kAssociated, 0)},
         {kSet1Member2,
-         net::FirstPartySetEntry(kSet1Owner, net::SiteType::kAssociated)},
-        {kSet1Owner,
-         net::FirstPartySetEntry(kSet1Owner, net::SiteType::kPrimary)},
+         net::FirstPartySetEntry(kSet1Owner, net::SiteType::kAssociated, 1)},
+        {kSet1Owner, net::FirstPartySetEntry(
+                         kSet1Owner, net::SiteType::kPrimary, absl::nullopt)},
         {kSet2Member1,
-         net::FirstPartySetEntry(kSet2Owner, net::SiteType::kAssociated)},
-        {kSet2Owner,
-         net::FirstPartySetEntry(kSet2Owner, net::SiteType::kPrimary)},
+         net::FirstPartySetEntry(kSet2Owner, net::SiteType::kAssociated, 0)},
+        {kSet2Owner, net::FirstPartySetEntry(
+                         kSet2Owner, net::SiteType::kPrimary, absl::nullopt)},
     });
   }
 
@@ -108,10 +108,10 @@
 TEST_F(NoopFirstPartySetsAccessDelegateTest, FindOwner) {
   EXPECT_THAT(delegate().FindOwner(kSet1Owner, base::NullCallback()),
               absl::make_optional(net::FirstPartySetEntry(
-                  kSet1Owner, net::SiteType::kPrimary)));
+                  kSet1Owner, net::SiteType::kPrimary, absl::nullopt)));
   EXPECT_THAT(delegate().FindOwner(kSet2Member1, base::NullCallback()),
               absl::make_optional(net::FirstPartySetEntry(
-                  kSet2Owner, net::SiteType::kAssociated)));
+                  kSet2Owner, net::SiteType::kAssociated, 0)));
 }
 
 TEST_F(NoopFirstPartySetsAccessDelegateTest, FindOwners) {
@@ -119,9 +119,9 @@
       delegate().FindOwners({kSet1Member1, kSet2Member1}, base::NullCallback()),
       FirstPartySetsAccessDelegate::OwnersResult({
           {kSet1Member1,
-           net::FirstPartySetEntry(kSet1Owner, net::SiteType::kAssociated)},
+           net::FirstPartySetEntry(kSet1Owner, net::SiteType::kAssociated, 0)},
           {kSet2Member1,
-           net::FirstPartySetEntry(kSet2Owner, net::SiteType::kAssociated)},
+           net::FirstPartySetEntry(kSet2Owner, net::SiteType::kAssociated, 0)},
       }));
 }
 
@@ -134,15 +134,15 @@
                   &first_party_sets_manager_) {
     first_party_sets_manager_.SetCompleteSets({
         {kSet1Member1,
-         net::FirstPartySetEntry(kSet1Owner, net::SiteType::kAssociated)},
+         net::FirstPartySetEntry(kSet1Owner, net::SiteType::kAssociated, 0)},
         {kSet1Member2,
-         net::FirstPartySetEntry(kSet1Owner, net::SiteType::kAssociated)},
-        {kSet1Owner,
-         net::FirstPartySetEntry(kSet1Owner, net::SiteType::kPrimary)},
+         net::FirstPartySetEntry(kSet1Owner, net::SiteType::kAssociated, 1)},
+        {kSet1Owner, net::FirstPartySetEntry(
+                         kSet1Owner, net::SiteType::kPrimary, absl::nullopt)},
         {kSet2Member1,
-         net::FirstPartySetEntry(kSet2Owner, net::SiteType::kAssociated)},
-        {kSet2Owner,
-         net::FirstPartySetEntry(kSet2Owner, net::SiteType::kPrimary)},
+         net::FirstPartySetEntry(kSet2Owner, net::SiteType::kAssociated, 0)},
+        {kSet2Owner, net::FirstPartySetEntry(
+                         kSet2Owner, net::SiteType::kPrimary, absl::nullopt)},
     });
   }
 
@@ -198,8 +198,8 @@
 TEST_F(FirstPartySetsAccessDelegateDisabledTest, ComputeMetadata) {
   // Same as the default ctor, but just to be explicit:
   net::FirstPartySetMetadata expected_metadata(net::SamePartyContext(),
-                                               /*frame_owner=*/nullptr,
-                                               /*top_frame_owner=*/nullptr);
+                                               /*frame_entry=*/nullptr,
+                                               /*top_frame_entry=*/nullptr);
 
   EXPECT_THAT(delegate().ComputeMetadata(
                   kSet1Member1, &kSet1Member1, {kSet1Member1, kSet1Owner},
@@ -250,7 +250,7 @@
 
   delegate_remote()->NotifyReady(mojom::FirstPartySetsReadyEvent::New());
 
-  net::FirstPartySetEntry entry(kSet1Owner, net::SiteType::kAssociated);
+  net::FirstPartySetEntry entry(kSet1Owner, net::SiteType::kAssociated, 0);
   EXPECT_EQ(future.Get(),
             net::FirstPartySetMetadata(net::SamePartyContext(Type::kSameParty),
                                        &entry, &entry));
@@ -263,7 +263,7 @@
   delegate_remote()->NotifyReady(mojom::FirstPartySetsReadyEvent::New());
 
   EXPECT_THAT(future.Get(), absl::make_optional(net::FirstPartySetEntry(
-                                kSet1Owner, net::SiteType::kAssociated)));
+                                kSet1Owner, net::SiteType::kAssociated, 0)));
 }
 
 TEST_F(AsyncFirstPartySetsAccessDelegateTest, QueryBeforeReady_FindOwners) {
@@ -273,26 +273,29 @@
 
   delegate_remote()->NotifyReady(mojom::FirstPartySetsReadyEvent::New());
 
-  EXPECT_THAT(future.Get(),
-              FirstPartySetsAccessDelegate::OwnersResult({
-                  {kSet1Member1, net::FirstPartySetEntry(
-                                     kSet1Owner, net::SiteType::kAssociated)},
-                  {kSet2Member1, net::FirstPartySetEntry(
-                                     kSet2Owner, net::SiteType::kAssociated)},
-              }));
+  EXPECT_THAT(
+      future.Get(),
+      FirstPartySetsAccessDelegate::OwnersResult({
+          {kSet1Member1,
+           net::FirstPartySetEntry(kSet1Owner, net::SiteType::kAssociated, 0)},
+          {kSet2Member1,
+           net::FirstPartySetEntry(kSet2Owner, net::SiteType::kAssociated, 0)},
+      }));
 }
 
 TEST_F(AsyncFirstPartySetsAccessDelegateTest, OverrideSets_ComputeMetadata) {
   delegate_remote()->NotifyReady(CreateFirstPartySetsReadyEvent({
       {kSet1Member1,
-       {net::FirstPartySetEntry(kSet3Owner, net::SiteType::kAssociated)}},
+       {net::FirstPartySetEntry(kSet3Owner, net::SiteType::kAssociated, 0)}},
       {kSet3Owner,
-       {net::FirstPartySetEntry(kSet3Owner, net::SiteType::kPrimary)}},
+       {net::FirstPartySetEntry(kSet3Owner, net::SiteType::kPrimary,
+                                absl::nullopt)}},
   }));
 
-  net::FirstPartySetEntry primary_entry(kSet3Owner, net::SiteType::kPrimary);
+  net::FirstPartySetEntry primary_entry(kSet3Owner, net::SiteType::kPrimary,
+                                        absl::nullopt);
   net::FirstPartySetEntry associated_entry(kSet3Owner,
-                                           net::SiteType::kAssociated);
+                                           net::SiteType::kAssociated, 0);
   EXPECT_EQ(ComputeMetadataAndWait(kSet3Owner, &kSet1Member1, {kSet1Member1}),
             net::FirstPartySetMetadata(net::SamePartyContext(Type::kSameParty),
                                        &primary_entry, &associated_entry));
@@ -301,7 +304,8 @@
 TEST_F(AsyncFirstPartySetsAccessDelegateTest, OverrideSets_FindOwner) {
   delegate_remote()->NotifyReady(CreateFirstPartySetsReadyEvent({
       {kSet3Owner,
-       {net::FirstPartySetEntry(kSet3Owner, net::SiteType::kPrimary)}},
+       {net::FirstPartySetEntry(kSet3Owner, net::SiteType::kPrimary,
+                                absl::nullopt)}},
   }));
 
   EXPECT_THAT(FindOwnerAndWait(kSet3Owner), Optional(_));
@@ -310,7 +314,8 @@
 TEST_F(AsyncFirstPartySetsAccessDelegateTest, OverrideSets_FindOwners) {
   delegate_remote()->NotifyReady(CreateFirstPartySetsReadyEvent({
       {kSet3Owner,
-       {net::FirstPartySetEntry(kSet3Owner, net::SiteType::kPrimary)}},
+       {net::FirstPartySetEntry(kSet3Owner, net::SiteType::kPrimary,
+                                absl::nullopt)}},
   }));
 
   EXPECT_THAT(FindOwnersAndWait({kSet3Owner}),
@@ -323,15 +328,16 @@
   SyncFirstPartySetsAccessDelegateTest() {
     delegate_remote()->NotifyReady(CreateFirstPartySetsReadyEvent({
         {kSet3Member1,
-         {net::FirstPartySetEntry(kSet3Owner, net::SiteType::kAssociated)}},
+         {net::FirstPartySetEntry(kSet3Owner, net::SiteType::kAssociated, 0)}},
         {kSet3Owner,
-         {net::FirstPartySetEntry(kSet3Owner, net::SiteType::kPrimary)}},
+         {net::FirstPartySetEntry(kSet3Owner, net::SiteType::kPrimary,
+                                  absl::nullopt)}},
     }));
   }
 };
 
 TEST_F(SyncFirstPartySetsAccessDelegateTest, ComputeMetadata) {
-  net::FirstPartySetEntry entry(kSet1Owner, net::SiteType::kAssociated);
+  net::FirstPartySetEntry entry(kSet1Owner, net::SiteType::kAssociated, 0);
   EXPECT_EQ(ComputeMetadataAndWait(kSet1Member1, &kSet1Member1, {kSet1Member1}),
             net::FirstPartySetMetadata(net::SamePartyContext(Type::kSameParty),
                                        &entry, &entry));
@@ -340,7 +346,7 @@
 TEST_F(SyncFirstPartySetsAccessDelegateTest, FindOwner) {
   EXPECT_THAT(FindOwnerAndWait(kSet1Member1),
               absl::make_optional(net::FirstPartySetEntry(
-                  kSet1Owner, net::SiteType::kAssociated)));
+                  kSet1Owner, net::SiteType::kAssociated, 0)));
 }
 
 TEST_F(SyncFirstPartySetsAccessDelegateTest, FindOwners) {
@@ -348,11 +354,11 @@
       FindOwnersAndWait({kSet1Member1, kSet2Member1, kSet3Member1}),
       FirstPartySetsAccessDelegate::OwnersResult({
           {kSet1Member1,
-           net::FirstPartySetEntry(kSet1Owner, net::SiteType::kAssociated)},
+           net::FirstPartySetEntry(kSet1Owner, net::SiteType::kAssociated, 0)},
           {kSet2Member1,
-           net::FirstPartySetEntry(kSet2Owner, net::SiteType::kAssociated)},
+           net::FirstPartySetEntry(kSet2Owner, net::SiteType::kAssociated, 0)},
           {kSet3Member1,
-           net::FirstPartySetEntry(kSet3Owner, net::SiteType::kAssociated)},
+           net::FirstPartySetEntry(kSet3Owner, net::SiteType::kAssociated, 0)},
       }));
 }
 
diff --git a/services/network/first_party_sets/first_party_sets_manager.cc b/services/network/first_party_sets/first_party_sets_manager.cc
index 4218814..d08e661 100644
--- a/services/network/first_party_sets/first_party_sets_manager.cc
+++ b/services/network/first_party_sets/first_party_sets_manager.cc
@@ -167,11 +167,11 @@
             fps_context_config.customizations().find(normalized_site);
         it != fps_context_config.customizations().end()) {
       if (it->second.has_value()) {
-        entry = net::FirstPartySetEntry(it->second.value());
+        entry = it->second.value();
       }
     } else if (const auto it = sets_->find(normalized_site);
                it != sets_->end()) {
-      entry = net::FirstPartySetEntry(it->second);
+      entry = it->second;
     }
   }
 
diff --git a/services/network/first_party_sets/first_party_sets_manager_unittest.cc b/services/network/first_party_sets/first_party_sets_manager_unittest.cc
index b3561c1..60dc3861 100644
--- a/services/network/first_party_sets/first_party_sets_manager_unittest.cc
+++ b/services/network/first_party_sets/first_party_sets_manager_unittest.cc
@@ -106,11 +106,11 @@
   SetCompleteSets({{net::SchemefulSite(GURL("https://aaaa.test")),
                     net::FirstPartySetEntry(
                         net::SchemefulSite(GURL("https://example.test")),
-                        net::SiteType::kAssociated)},
+                        net::SiteType::kAssociated, 0)},
                    {net::SchemefulSite(GURL("https://example.test")),
                     net::FirstPartySetEntry(
                         net::SchemefulSite(GURL("https://example.test")),
-                        net::SiteType::kPrimary)}});
+                        net::SiteType::kPrimary, absl::nullopt)}});
 
   EXPECT_THAT(FindOwnersAndWait({
                   net::SchemefulSite(GURL("https://aaaa.test")),
@@ -135,11 +135,11 @@
       true, {{net::SchemefulSite(GURL("https://member1.test")),
               {net::FirstPartySetEntry(
                   net::SchemefulSite(GURL("https://example.test")),
-                  net::SiteType::kAssociated)}},
+                  net::SiteType::kAssociated, 0)}},
              {net::SchemefulSite(GURL("https://example.test")),
               {net::FirstPartySetEntry(
                   net::SchemefulSite(GURL("https://example.test")),
-                  net::SiteType::kPrimary)}}});
+                  net::SiteType::kPrimary, absl::nullopt)}}});
 
   // Works if the site is provided with WSS scheme instead of HTTPS.
   EXPECT_THAT(
@@ -160,21 +160,21 @@
   SetCompleteSets({{net::SchemefulSite(GURL("https://member.test")),
                     net::FirstPartySetEntry(
                         net::SchemefulSite(GURL("https://example.test")),
-                        net::SiteType::kAssociated)},
+                        net::SiteType::kAssociated, 0)},
                    {net::SchemefulSite(GURL("https://example.test")),
                     net::FirstPartySetEntry(
                         net::SchemefulSite(GURL("https://example.test")),
-                        net::SiteType::kPrimary)}});
+                        net::SiteType::kPrimary, absl::nullopt)}});
 
   SetFirstPartySetsContextConfig(
       true, {{net::SchemefulSite(GURL("https://aaaa.test")),
               {net::FirstPartySetEntry(
                   net::SchemefulSite(GURL("https://example.test")),
-                  net::SiteType::kAssociated)}},
+                  net::SiteType::kAssociated, 0)}},
              {net::SchemefulSite(GURL("https://example.test")),
               {net::FirstPartySetEntry(
                   net::SchemefulSite(GURL("https://example.test")),
-                  net::SiteType::kPrimary)}}});
+                  net::SiteType::kPrimary, absl::nullopt)}}});
 
   EXPECT_FALSE(
       FindOwnerAndWait(net::SchemefulSite(GURL("https://example.test"))));
@@ -192,11 +192,11 @@
   SetCompleteSets({{net::SchemefulSite(GURL("https://aaaa.test")),
                     net::FirstPartySetEntry(
                         net::SchemefulSite(GURL("https://example.test")),
-                        net::SiteType::kAssociated)},
+                        net::SiteType::kAssociated, 0)},
                    {net::SchemefulSite(GURL("https://example.test")),
                     net::FirstPartySetEntry(
                         net::SchemefulSite(GURL("https://example.test")),
-                        net::SiteType::kPrimary)}});
+                        net::SiteType::kPrimary, absl::nullopt)}});
 
   EXPECT_THAT(FindOwnersAndWait({
                   net::SchemefulSite(GURL("https://aaaa.test")),
@@ -206,11 +206,11 @@
                   Pair(SerializesTo("https://example.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://aaaa.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated))));
+                           net::SiteType::kAssociated, 0))));
 }
 
 TEST_F(FirstPartySetsEnabledTest, SetCompleteSets_Idempotent) {
@@ -221,11 +221,11 @@
   SetCompleteSets({{net::SchemefulSite(GURL("https://aaaa.test")),
                     net::FirstPartySetEntry(
                         net::SchemefulSite(GURL("https://example.test")),
-                        net::SiteType::kAssociated)},
+                        net::SiteType::kAssociated, 0)},
                    {net::SchemefulSite(GURL("https://example.test")),
                     net::FirstPartySetEntry(
                         net::SchemefulSite(GURL("https://example.test")),
-                        net::SiteType::kPrimary)}});
+                        net::SiteType::kPrimary, absl::nullopt)}});
   EXPECT_THAT(FindOwnersAndWait({
                   net::SchemefulSite(GURL("https://aaaa.test")),
                   net::SchemefulSite(GURL("https://example.test")),
@@ -259,21 +259,21 @@
         {net::SchemefulSite(GURL("https://member1.test")),
          net::FirstPartySetEntry(
              net::SchemefulSite(GURL("https://example.test")),
-             net::SiteType::kAssociated)},
+             net::SiteType::kAssociated, 0)},
         {net::SchemefulSite(GURL("https://member3.test")),
          net::FirstPartySetEntry(
              net::SchemefulSite(GURL("https://example.test")),
-             net::SiteType::kAssociated)},
+             net::SiteType::kAssociated, 0)},
         {net::SchemefulSite(GURL("https://example.test")),
          net::FirstPartySetEntry(
              net::SchemefulSite(GURL("https://example.test")),
-             net::SiteType::kPrimary)},
+             net::SiteType::kPrimary, absl::nullopt)},
         {net::SchemefulSite(GURL("https://member2.test")),
          net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                                 net::SiteType::kAssociated)},
+                                 net::SiteType::kAssociated, 0)},
         {net::SchemefulSite(GURL("https://foo.test")),
          net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                                 net::SiteType::kPrimary)},
+                                 net::SiteType::kPrimary, absl::nullopt)},
     });
 
     // We don't wait for the sets to be loaded before returning, in order to let
@@ -296,7 +296,7 @@
 
   {
     net::SchemefulSite owner(GURL("https://example.test"));
-    net::FirstPartySetEntry entry(owner, net::SiteType::kAssociated);
+    net::FirstPartySetEntry entry(owner, net::SiteType::kAssociated, 0);
 
     EXPECT_EQ(future.Get(),
               net::FirstPartySetMetadata(
@@ -315,7 +315,7 @@
   EXPECT_THAT(future.Get(),
               absl::make_optional(net::FirstPartySetEntry(
                   net::SchemefulSite(GURL("https://example.test")),
-                  net::SiteType::kAssociated)));
+                  net::SiteType::kAssociated, 0)));
 }
 
 TEST_F(AsyncPopulatedFirstPartySetsManagerTest, QueryBeforeReady_FindOwners) {
@@ -334,11 +334,11 @@
                   Pair(SerializesTo("https://member1.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated)),
+                           net::SiteType::kAssociated, 0)),
                   Pair(SerializesTo("https://member2.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://foo.test")),
-                           net::SiteType::kAssociated))));
+                           net::SiteType::kAssociated, 0))));
 }
 
 class PopulatedFirstPartySetsManagerTest
@@ -784,8 +784,10 @@
   net::SchemefulSite owner(GURL("https://example.test"));
   net::SchemefulSite wss_member(GURL("wss://member1.test"));
   net::SchemefulSite wss_nonmember(GURL("wss://nonmember.test"));
-  net::FirstPartySetEntry primary_entry(owner, net::SiteType::kPrimary);
-  net::FirstPartySetEntry associated_entry(owner, net::SiteType::kAssociated);
+  net::FirstPartySetEntry primary_entry(owner, net::SiteType::kPrimary,
+                                        absl::nullopt);
+  net::FirstPartySetEntry associated_entry(owner, net::SiteType::kAssociated,
+                                           0);
 
   // Works as usual for sites that are in First-Party sets.
   EXPECT_EQ(ComputeMetadataAndWait(member, &member, {member}),
@@ -837,18 +839,20 @@
     const absl::optional<net::FirstPartySetEntry> expected;
   } test_cases[] = {
       {"https://example.test",
-       net::FirstPartySetEntry(kSetOwner1, net::SiteType::kPrimary)},
+       net::FirstPartySetEntry(kSetOwner1, net::SiteType::kPrimary,
+                               absl::nullopt)},
       // Insecure URL
       {"http://example.test", absl::nullopt},
       // Test member
       {"https://member1.test",
-       net::FirstPartySetEntry(kSetOwner1, net::SiteType::kAssociated)},
+       net::FirstPartySetEntry(kSetOwner1, net::SiteType::kAssociated, 0)},
       {"http://member1.test", absl::nullopt},
       // Test another disjoint set
       {"https://foo.test",
-       net::FirstPartySetEntry(kSetOwner2, net::SiteType::kPrimary)},
+       net::FirstPartySetEntry(kSetOwner2, net::SiteType::kPrimary,
+                               absl::nullopt)},
       {"https://member2.test",
-       net::FirstPartySetEntry(kSetOwner2, net::SiteType::kAssociated)},
+       net::FirstPartySetEntry(kSetOwner2, net::SiteType::kAssociated, 0)},
       // Test a site not in a set
       {"https://nonmember.test", absl::nullopt},
   };
@@ -871,13 +875,13 @@
                   Pair(SerializesTo("https://example.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kPrimary))));
+                           net::SiteType::kPrimary, absl::nullopt))));
   EXPECT_THAT(FindOwnersAndWait({kMember1}),
               UnorderedElementsAre(
                   Pair(SerializesTo("https://member1.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated))));
+                           net::SiteType::kAssociated, 0))));
   EXPECT_THAT(FindOwnersAndWait({kNonmember}), IsEmpty());
 
   EXPECT_THAT(FindOwnersAndWait({kExample, kNonmember}),
@@ -885,54 +889,54 @@
                   Pair(SerializesTo("https://example.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kPrimary))));
+                           net::SiteType::kPrimary, absl::nullopt))));
   EXPECT_THAT(FindOwnersAndWait({kMember1, kNonmember}),
               UnorderedElementsAre(
                   Pair(SerializesTo("https://member1.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated))));
+                           net::SiteType::kAssociated, 0))));
 
   EXPECT_THAT(FindOwnersAndWait({kExample, kFoo}),
               UnorderedElementsAre(
                   Pair(SerializesTo("https://example.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://foo.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://foo.test")),
-                           net::SiteType::kPrimary))));
+                           net::SiteType::kPrimary, absl::nullopt))));
   EXPECT_THAT(FindOwnersAndWait({kMember1, kFoo}),
               UnorderedElementsAre(
                   Pair(SerializesTo("https://member1.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated)),
+                           net::SiteType::kAssociated, 0)),
                   Pair(SerializesTo("https://foo.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://foo.test")),
-                           net::SiteType::kPrimary))));
+                           net::SiteType::kPrimary, absl::nullopt))));
   EXPECT_THAT(FindOwnersAndWait({kExample, kMember2}),
               UnorderedElementsAre(
                   Pair(SerializesTo("https://example.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member2.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://foo.test")),
-                           net::SiteType::kAssociated))));
+                           net::SiteType::kAssociated, 0))));
   EXPECT_THAT(FindOwnersAndWait({kMember1, kMember2}),
               UnorderedElementsAre(
                   Pair(SerializesTo("https://member1.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://example.test")),
-                           net::SiteType::kAssociated)),
+                           net::SiteType::kAssociated, 0)),
                   Pair(SerializesTo("https://member2.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://foo.test")),
-                           net::SiteType::kAssociated))));
+                           net::SiteType::kAssociated, 0))));
 }
 
 class OverrideSetsFirstPartySetsManagerTest : public FirstPartySetsEnabledTest {
@@ -942,16 +946,16 @@
         {net::SchemefulSite(GURL("https://member1.test")),
          net::FirstPartySetEntry(
              net::SchemefulSite(GURL("https://example.test")),
-             net::SiteType::kAssociated)},
+             net::SiteType::kAssociated, 0)},
         {net::SchemefulSite(GURL("https://member2.test")),
          net::FirstPartySetEntry(
              net::SchemefulSite(GURL("https://example.test")),
-             net::SiteType::kAssociated)},
+             net::SiteType::kAssociated, 0)},
         // Below are the owner self mappings.
         {net::SchemefulSite(GURL("https://example.test")),
          net::FirstPartySetEntry(
              net::SchemefulSite(GURL("https://example.test")),
-             net::SiteType::kPrimary)},
+             net::SiteType::kPrimary, absl::nullopt)},
     });
 
     SetFirstPartySetsContextConfig(
@@ -961,14 +965,14 @@
             {net::SchemefulSite(GURL("https://foo.test")),
              {net::FirstPartySetEntry(
                  net::SchemefulSite(GURL("https://foo.test")),
-                 net::SiteType::kPrimary)}},
+                 net::SiteType::kPrimary, absl::nullopt)}},
             // Removed entry:
             {net::SchemefulSite(GURL("https://member1.test")), absl::nullopt},
             // Remapped entry:
             {net::SchemefulSite(GURL("https://member2.test")),
              {net::FirstPartySetEntry(
                  net::SchemefulSite(GURL("https://foo.test")),
-                 net::SiteType::kAssociated)}},
+                 net::SiteType::kAssociated, 0)}},
         });
   }
 };
@@ -977,13 +981,13 @@
   EXPECT_EQ(
       FindOwnerAndWait(net::SchemefulSite(GURL("https://foo.test"))),
       net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                              net::SiteType::kPrimary));
+                              net::SiteType::kPrimary, absl::nullopt));
   EXPECT_EQ(FindOwnerAndWait(net::SchemefulSite(GURL("https://member1.test"))),
             absl::nullopt);
   EXPECT_EQ(
       FindOwnerAndWait(net::SchemefulSite(GURL("https://member2.test"))),
       net::FirstPartySetEntry(net::SchemefulSite(GURL("https://foo.test")),
-                              net::SiteType::kAssociated));
+                              net::SiteType::kAssociated, 0));
 }
 
 TEST_F(OverrideSetsFirstPartySetsManagerTest, FindOwners) {
@@ -996,11 +1000,11 @@
                   Pair(SerializesTo("https://foo.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://foo.test")),
-                           net::SiteType::kPrimary)),
+                           net::SiteType::kPrimary, absl::nullopt)),
                   Pair(SerializesTo("https://member2.test"),
                        net::FirstPartySetEntry(
                            net::SchemefulSite(GURL("https://foo.test")),
-                           net::SiteType::kAssociated))));
+                           net::SiteType::kAssociated, 0))));
 }
 
 TEST_F(OverrideSetsFirstPartySetsManagerTest, ComputeMetadata) {
@@ -1009,10 +1013,12 @@
   net::SchemefulSite member1(GURL("https://member1.test"));
   net::SchemefulSite member2(GURL("https://member2.test"));
 
-  net::FirstPartySetEntry example_primary_entry(example,
-                                                net::SiteType::kPrimary);
-  net::FirstPartySetEntry foo_primary_entry(foo, net::SiteType::kPrimary);
-  net::FirstPartySetEntry foo_associated_entry(foo, net::SiteType::kAssociated);
+  net::FirstPartySetEntry example_primary_entry(
+      example, net::SiteType::kPrimary, absl::nullopt);
+  net::FirstPartySetEntry foo_primary_entry(foo, net::SiteType::kPrimary,
+                                            absl::nullopt);
+  net::FirstPartySetEntry foo_associated_entry(foo, net::SiteType::kAssociated,
+                                               0);
   {
     // member1 has been removed from its set.
     net::FirstPartySetMetadata expected(
diff --git a/services/network/public/cpp/first_party_sets_mojom_traits.cc b/services/network/public/cpp/first_party_sets_mojom_traits.cc
index ef51d08..cbc0fe1 100644
--- a/services/network/public/cpp/first_party_sets_mojom_traits.cc
+++ b/services/network/public/cpp/first_party_sets_mojom_traits.cc
@@ -15,6 +15,14 @@
 
 namespace mojo {
 
+bool StructTraits<network::mojom::SiteIndexDataView,
+                  net::FirstPartySetEntry::SiteIndex>::
+    Read(network::mojom::SiteIndexDataView index,
+         net::FirstPartySetEntry::SiteIndex* out) {
+  *out = net::FirstPartySetEntry::SiteIndex(index.value());
+  return true;
+}
+
 bool EnumTraits<network::mojom::SiteType, net::SiteType>::FromMojom(
     network::mojom::SiteType site_type,
     net::SiteType* out) {
@@ -54,7 +62,11 @@
   if (!entry.ReadSiteType(&site_type))
     return false;
 
-  *out = net::FirstPartySetEntry(primary, site_type);
+  absl::optional<net::FirstPartySetEntry::SiteIndex> site_index;
+  if (!entry.ReadSiteIndex(&site_index))
+    return false;
+
+  *out = net::FirstPartySetEntry(primary, site_type, site_index);
   return true;
 }
 
diff --git a/services/network/public/cpp/first_party_sets_mojom_traits.h b/services/network/public/cpp/first_party_sets_mojom_traits.h
index d7de0e8..3cfaf32 100644
--- a/services/network/public/cpp/first_party_sets_mojom_traits.h
+++ b/services/network/public/cpp/first_party_sets_mojom_traits.h
@@ -17,6 +17,18 @@
 
 template <>
 struct COMPONENT_EXPORT(FIRST_PARTY_SETS_MOJOM_TRAITS)
+    StructTraits<network::mojom::SiteIndexDataView,
+                 net::FirstPartySetEntry::SiteIndex> {
+  static uint32_t value(const net::FirstPartySetEntry::SiteIndex& i) {
+    return i.value();
+  }
+
+  static bool Read(network::mojom::SiteIndexDataView index,
+                   net::FirstPartySetEntry::SiteIndex* out);
+};
+
+template <>
+struct COMPONENT_EXPORT(FIRST_PARTY_SETS_MOJOM_TRAITS)
     EnumTraits<network::mojom::SiteType, net::SiteType> {
   static network::mojom::SiteType ToMojom(net::SiteType site_type);
 
@@ -35,6 +47,11 @@
     return e.site_type();
   }
 
+  static const absl::optional<net::FirstPartySetEntry::SiteIndex>& site_index(
+      const net::FirstPartySetEntry& e) {
+    return e.site_index();
+  }
+
   static bool Read(network::mojom::FirstPartySetEntryDataView entry,
                    net::FirstPartySetEntry* out);
 };
diff --git a/services/network/public/cpp/first_party_sets_mojom_traits_unittest.cc b/services/network/public/cpp/first_party_sets_mojom_traits_unittest.cc
index 0c8c8d7..eb1893a 100644
--- a/services/network/public/cpp/first_party_sets_mojom_traits_unittest.cc
+++ b/services/network/public/cpp/first_party_sets_mojom_traits_unittest.cc
@@ -15,10 +15,20 @@
 namespace network {
 namespace {
 
+TEST(FirstPartySetsTraitsTest, Roundtrips_SiteIndex) {
+  net::FirstPartySetEntry::SiteIndex original(1337);
+  net::FirstPartySetEntry::SiteIndex round_tripped;
+
+  EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::SiteIndex>(
+      original, round_tripped));
+
+  EXPECT_EQ(original, round_tripped);
+}
+
 TEST(FirstPartySetsTraitsTest, Roundtrips_FirstPartySetEntry) {
   net::SchemefulSite primary(GURL("https://primary.test"));
 
-  net::FirstPartySetEntry original(primary, net::SiteType::kAssociated);
+  net::FirstPartySetEntry original(primary, net::SiteType::kAssociated, 1);
   net::FirstPartySetEntry round_tripped;
 
   EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::FirstPartySetEntry>(
@@ -64,9 +74,10 @@
   net::SchemefulSite frame_owner(GURL("https://frame.test"));
   net::SchemefulSite top_frame_owner(GURL("https://top_frame.test"));
 
-  net::FirstPartySetEntry frame_entry(frame_owner, net::SiteType::kAssociated);
+  net::FirstPartySetEntry frame_entry(frame_owner, net::SiteType::kAssociated,
+                                      1);
   net::FirstPartySetEntry top_frame_entry(top_frame_owner,
-                                          net::SiteType::kAssociated);
+                                          net::SiteType::kAssociated, 2);
 
   auto make_metadata = [&]() {
     // Use non-default values to ensure serialization/deserialization works
diff --git a/services/network/public/cpp/network_param_mojom_traits.h b/services/network/public/cpp/network_param_mojom_traits.h
index 2f97594..2c39e62 100644
--- a/services/network/public/cpp/network_param_mojom_traits.h
+++ b/services/network/public/cpp/network_param_mojom_traits.h
@@ -52,10 +52,10 @@
 class COMPONENT_EXPORT(NETWORK_CPP_NETWORK_PARAM)
     StructTraits<network::mojom::HttpVersionDataView, net::HttpVersion> {
  public:
-  static int16_t major_value(net::HttpVersion version) {
+  static uint16_t major_value(net::HttpVersion version) {
     return version.major_value();
   }
-  static int16_t minor_value(net::HttpVersion version) {
+  static uint16_t minor_value(net::HttpVersion version) {
     return version.minor_value();
   }
 
diff --git a/services/network/public/mojom/BUILD.gn b/services/network/public/mojom/BUILD.gn
index 38abaac..df80f5b7 100644
--- a/services/network/public/mojom/BUILD.gn
+++ b/services/network/public/mojom/BUILD.gn
@@ -752,6 +752,10 @@
         {
           types = [
             {
+              mojom = "network.mojom.SiteIndex"
+              cpp = "::net::FirstPartySetEntry::SiteIndex"
+            },
+            {
               mojom = "network.mojom.FirstPartySetEntry"
               cpp = "::net::FirstPartySetEntry"
             },
diff --git a/services/network/public/mojom/first_party_sets.mojom b/services/network/public/mojom/first_party_sets.mojom
index 0422b46a..74416d6 100644
--- a/services/network/public/mojom/first_party_sets.mojom
+++ b/services/network/public/mojom/first_party_sets.mojom
@@ -6,6 +6,12 @@
 
 import "services/network/public/mojom/schemeful_site.mojom";
 
+// This struct should match net::FirstPartySetEntry::SiteIndex in
+// //net/cookies/first_party_set_entry.h
+struct SiteIndex {
+  uint32 value;
+};
+
 // This enum should match //net/cookies/first_party_set_entry.h.
 enum SiteType {
   kPrimary,
@@ -16,6 +22,7 @@
 struct FirstPartySetEntry {
   SchemefulSite primary;
   SiteType site_type;
+  SiteIndex? site_index;
 };
 
 // Computed for every cookie access attempt but is only relevant for SameParty
diff --git a/services/network/restricted_cookie_manager_unittest.cc b/services/network/restricted_cookie_manager_unittest.cc
index 6e1e2c3..08538f3 100644
--- a/services/network/restricted_cookie_manager_unittest.cc
+++ b/services/network/restricted_cookie_manager_unittest.cc
@@ -464,11 +464,11 @@
         {{net::SchemefulSite(GURL("https://example.com")),
           net::FirstPartySetEntry(
               net::SchemefulSite(GURL("https://example.com")),
-              net::SiteType::kPrimary)},
+              net::SiteType::kPrimary, absl::nullopt)},
          {net::SchemefulSite(GURL("https://member1.com")),
           net::FirstPartySetEntry(
               net::SchemefulSite(GURL("https://example.com")),
-              net::SiteType::kAssociated)}});
+              net::SiteType::kAssociated, 0)}});
     first_party_sets_access_delegate_remote_->NotifyReady(
         mojom::FirstPartySetsReadyEvent::New());
     auto cookie_access_delegate = std::make_unique<CookieAccessDelegateImpl>(
@@ -2056,10 +2056,10 @@
   auto cookie_access_delegate =
       std::make_unique<net::TestCookieAccessDelegate>();
   cookie_access_delegate->SetFirstPartySets({
-      {kOwnerSite,
-       net::FirstPartySetEntry(kOwnerSite, net::SiteType::kPrimary)},
+      {kOwnerSite, net::FirstPartySetEntry(kOwnerSite, net::SiteType::kPrimary,
+                                           absl::nullopt)},
       {kMemberSite,
-       net::FirstPartySetEntry(kOwnerSite, net::SiteType::kAssociated)},
+       net::FirstPartySetEntry(kOwnerSite, net::SiteType::kAssociated, 0)},
   });
   cookie_monster_.SetCookieAccessDelegate(std::move(cookie_access_delegate));
 
diff --git a/storage/browser/file_system/obfuscated_file_util.cc b/storage/browser/file_system/obfuscated_file_util.cc
index 2c692cf..b238431 100644
--- a/storage/browser/file_system/obfuscated_file_util.cc
+++ b/storage/browser/file_system/obfuscated_file_util.cc
@@ -306,15 +306,17 @@
   base::WeakPtr<ObfuscatedFileUtilMemoryDelegate> memory_file_util_;
 };
 
+const base::FilePath::CharType ObfuscatedFileUtil::kFileSystemDirectory[] =
+    FILE_PATH_LITERAL("File System");
+
 ObfuscatedFileUtil::ObfuscatedFileUtil(
     scoped_refptr<SpecialStoragePolicy> special_storage_policy,
-    const base::FilePath& file_system_directory,
+    const base::FilePath& profile_path,
     leveldb::Env* env_override,
     const std::set<std::string>& known_type_strings,
     SandboxFileSystemBackendDelegate* sandbox_delegate,
     bool is_incognito)
     : special_storage_policy_(std::move(special_storage_policy)),
-      file_system_directory_(file_system_directory),
       env_override_(env_override),
       is_incognito_(is_incognito),
       db_flush_delay_seconds_(10 * 60),  // 10 mins.
@@ -323,10 +325,15 @@
   DETACH_FROM_SEQUENCE(sequence_checker_);
   DCHECK(!is_incognito_ ||
          (env_override && leveldb_chrome::IsMemEnv(env_override)));
+  file_system_directory_ = profile_path.Append(kFileSystemDirectory);
 
   if (is_incognito_) {
-    delegate_ = std::make_unique<ObfuscatedFileUtilMemoryDelegate>(
-        file_system_directory_);
+    // profile_path is passed here, so that the delegate is able to accommodate
+    // both codepaths of {{profile_path}}/File System (first-party) and
+    // {{profile_path}}/WebStorage (buckets-based).
+    // See https://crrev.com/c/3817542 for more context.
+    delegate_ =
+        std::make_unique<ObfuscatedFileUtilMemoryDelegate>(profile_path);
   } else {
     delegate_ = std::make_unique<ObfuscatedFileUtilDiskDelegate>();
   }
diff --git a/storage/browser/file_system/obfuscated_file_util.h b/storage/browser/file_system/obfuscated_file_util.h
index 4b13568..8118736 100644
--- a/storage/browser/file_system/obfuscated_file_util.h
+++ b/storage/browser/file_system/obfuscated_file_util.h
@@ -136,6 +136,9 @@
     virtual bool HasTypeDirectory(const std::string& type_string) const = 0;
   };
 
+  // The FileSystem directory component.
+  static const base::FilePath::CharType kFileSystemDirectory[];
+
   // The type string is used to provide per-type isolation in the sandboxed
   // filesystem directory. `known_type_strings` are known type string names that
   // this file system should care about. This info is used to determine whether
@@ -146,7 +149,7 @@
   // and as a result, directories should only be directly compared using type
   // string values.
   ObfuscatedFileUtil(scoped_refptr<SpecialStoragePolicy> special_storage_policy,
-                     const base::FilePath& file_system_directory,
+                     const base::FilePath& profile_path,
                      leveldb::Env* env_override,
                      const std::set<std::string>& known_type_strings,
                      SandboxFileSystemBackendDelegate* sandbox_delegate,
diff --git a/storage/browser/file_system/obfuscated_file_util_unittest.cc b/storage/browser/file_system/obfuscated_file_util_unittest.cc
index acb3b0a1..09174f73c 100644
--- a/storage/browser/file_system/obfuscated_file_util_unittest.cc
+++ b/storage/browser/file_system/obfuscated_file_util_unittest.cc
@@ -65,14 +65,15 @@
 
 namespace {
 
-// TODO(https://crbug.com/1322223): create an additional parameterized
-// test mode for kIncognitoThirdParty.
 enum TestMode {
   kRegularFirstParty,
   kRegularFirstPartyNonDefaultBucket,
   kRegularThirdParty,
   kRegularThirdPartyNonDefaultBucket,
-  kIncognitoFirstParty
+  kIncognitoFirstParty,
+  kIncognitoFirstPartyNonDefaultBucket,
+  kIncognitoThirdParty,
+  kIncognitoThirdPartyNonDefaultBucket
 };
 
 bool FileExists(const base::FilePath& path) {
@@ -203,14 +204,23 @@
 
   ~ObfuscatedFileUtilTest() override = default;
 
-  bool is_incognito() { return GetParam() == TestMode::kIncognitoFirstParty; }
+  bool is_incognito() {
+    return GetParam() == TestMode::kIncognitoFirstParty ||
+           (GetParam() == TestMode::kIncognitoFirstPartyNonDefaultBucket) ||
+           (GetParam() == TestMode::kIncognitoThirdParty) ||
+           (GetParam() == TestMode::kIncognitoThirdPartyNonDefaultBucket);
+  }
   bool is_third_party_context() {
     return GetParam() == TestMode::kRegularThirdParty ||
-           (GetParam() == TestMode::kRegularThirdPartyNonDefaultBucket);
+           (GetParam() == TestMode::kRegularThirdPartyNonDefaultBucket) ||
+           (GetParam() == TestMode::kIncognitoThirdParty) ||
+           (GetParam() == TestMode::kIncognitoThirdPartyNonDefaultBucket);
   }
   bool is_non_default_bucket() {
     return GetParam() == TestMode::kRegularFirstPartyNonDefaultBucket ||
-           (GetParam() == TestMode::kRegularThirdPartyNonDefaultBucket);
+           (GetParam() == TestMode::kRegularThirdPartyNonDefaultBucket) ||
+           (GetParam() == TestMode::kIncognitoFirstPartyNonDefaultBucket) ||
+           (GetParam() == TestMode::kIncognitoThirdPartyNonDefaultBucket);
   }
 
   void SetUp() override {
@@ -941,7 +951,10 @@
                     TestMode::kRegularFirstPartyNonDefaultBucket,
                     TestMode::kRegularThirdParty,
                     TestMode::kRegularThirdPartyNonDefaultBucket,
-                    TestMode::kIncognitoFirstParty));
+                    TestMode::kIncognitoFirstParty,
+                    TestMode::kIncognitoFirstPartyNonDefaultBucket,
+                    TestMode::kIncognitoThirdParty,
+                    TestMode::kIncognitoThirdPartyNonDefaultBucket));
 
 TEST_P(ObfuscatedFileUtilTest, TestCreateAndDeleteFile) {
   FileSystemURL url = CreateURLFromUTF8("fake/file");
@@ -2541,10 +2554,6 @@
 }
 
 TEST_P(ObfuscatedFileUtilTest, DeleteDirectoryForBucketAndType) {
-  // TODO(https://crbug.com/1322223): Remove this early return once we resolve
-  // failures involving non-default buckets in incognito mode.
-  if (is_incognito())
-    return;
   // Create directories.
   std::unique_ptr<SandboxFileSystemTestHelper> fs1 =
       NewFileSystem(default_bucket_, kFileSystemTypeTemporary);
@@ -2715,10 +2724,6 @@
 }
 
 TEST_P(ObfuscatedFileUtilTest, DeleteDirectoryForBucketAndType_DeleteAll) {
-  // TODO(https://crbug.com/1322223): Remove this early return once we resolve
-  // failures involving non-default buckets in incognito mode.
-  if (is_incognito())
-    return;
   // Create origin directories.
   std::unique_ptr<SandboxFileSystemTestHelper> fs1 =
       NewFileSystem(default_bucket_, kFileSystemTypeTemporary);
diff --git a/storage/browser/file_system/sandbox_file_system_backend_delegate.cc b/storage/browser/file_system/sandbox_file_system_backend_delegate.cc
index 60e192f..ed0d7a68 100644
--- a/storage/browser/file_system/sandbox_file_system_backend_delegate.cc
+++ b/storage/browser/file_system/sandbox_file_system_backend_delegate.cc
@@ -162,10 +162,6 @@
 
 }  // namespace
 
-const base::FilePath::CharType
-    SandboxFileSystemBackendDelegate::kFileSystemDirectory[] =
-        FILE_PATH_LITERAL("File System");
-
 // static
 std::string SandboxFileSystemBackendDelegate::GetTypeString(
     FileSystemType type) {
@@ -196,7 +192,7 @@
       sandbox_file_util_(std::make_unique<AsyncFileUtilAdapter>(
           std::make_unique<ObfuscatedFileUtil>(
               special_storage_policy,
-              profile_path.Append(kFileSystemDirectory),
+              profile_path,
               env_override,
               GetKnownTypeStrings(),
               this,
@@ -795,11 +791,11 @@
 // static
 std::unique_ptr<ObfuscatedFileUtil> ObfuscatedFileUtil::CreateForTesting(
     scoped_refptr<SpecialStoragePolicy> special_storage_policy,
-    const base::FilePath& file_system_directory,
+    const base::FilePath& profile_path,
     leveldb::Env* env_override,
     bool is_incognito) {
   return std::make_unique<ObfuscatedFileUtil>(
-      std::move(special_storage_policy), file_system_directory, env_override,
+      std::move(special_storage_policy), profile_path, env_override,
       GetKnownTypeStrings(), /*sandbox_delegate=*/nullptr, is_incognito);
 }
 
diff --git a/storage/browser/file_system/sandbox_file_system_backend_delegate.h b/storage/browser/file_system/sandbox_file_system_backend_delegate.h
index 5655c3adf..30fef5c 100644
--- a/storage/browser/file_system/sandbox_file_system_backend_delegate.h
+++ b/storage/browser/file_system/sandbox_file_system_backend_delegate.h
@@ -73,9 +73,6 @@
   using OpenFileSystemCallback = FileSystemBackend::OpenFileSystemCallback;
   using ResolveURLCallback = FileSystemBackend::ResolveURLCallback;
 
-  // The FileSystem directory name.
-  static const base::FilePath::CharType kFileSystemDirectory[];
-
   // StorageKey enumerator interface.
   // An instance of this interface is assumed to be called on the file thread.
   class StorageKeyEnumerator {
diff --git a/storage/browser/file_system/sandbox_file_system_backend_unittest.cc b/storage/browser/file_system/sandbox_file_system_backend_unittest.cc
index 2290b355..fd43d57 100644
--- a/storage/browser/file_system/sandbox_file_system_backend_unittest.cc
+++ b/storage/browser/file_system/sandbox_file_system_backend_unittest.cc
@@ -23,6 +23,7 @@
 #include "storage/browser/file_system/file_system_backend.h"
 #include "storage/browser/file_system/file_system_features.h"
 #include "storage/browser/file_system/file_system_url.h"
+#include "storage/browser/file_system/obfuscated_file_util.h"
 #include "storage/browser/file_system/sandbox_file_system_backend_delegate.h"
 #include "storage/browser/quota/quota_manager.h"
 #include "storage/browser/quota/quota_manager_proxy.h"
@@ -203,8 +204,7 @@
   }
 
   base::FilePath file_system_path() const {
-    return data_dir_.GetPath().Append(
-        SandboxFileSystemBackendDelegate::kFileSystemDirectory);
+    return data_dir_.GetPath().Append(ObfuscatedFileUtil::kFileSystemDirectory);
   }
 
   base::FilePath file_system_path_for_buckets() const {
diff --git a/testing/buildbot/filters/android.emulator_11_12.media_unittests.filter b/testing/buildbot/filters/android.emulator_11_12.media_unittests.filter
index 33612d2..576eae8 100644
--- a/testing/buildbot/filters/android.emulator_11_12.media_unittests.filter
+++ b/testing/buildbot/filters/android.emulator_11_12.media_unittests.filter
@@ -3,3 +3,13 @@
 -PaintCanvasVideoRendererWithGLTest.CopyVideoFrameTexturesToGLTexture*
 -PaintCanvasVideoRendererWithGLTest.CopyVideoFrameYUVDataToGLTexture*
 -PaintCanvasVideoRendererWithGLTest.Paint*
+
+# Tests are flaky.
+# https://crbug.com/1352098
+-PaintCanvasVideoRendererWithGLTest.CopyVideoFrameToWebGLTextureRGBA
+-PaintCanvasVideoRendererWithGLTest.CopyVideoFrameToWebGLTexture_FlipY
+-PaintCanvasVideoRendererWithGLTest.CopyVideoFrameToWebGLTextureRGBA_ReadLockFence
+-PaintCanvasVideoRendererWithGLTest.CopyVideoFrameToWebGLTextureI420
+-PaintCanvasVideoRendererWithGLTest.CopyVideoFrameToWebGLTexture
+-PaintCanvasVideoRendererWithGLTest.CopyVideoFrameToWebGLTextureNV12
+
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 9d34f08a..c1ccd562 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -3739,6 +3739,9 @@
             "experiments": [
                 {
                     "name": "Enabled",
+                    "params": {
+                        "StackCapacity": "16"
+                    },
                     "enable_features": [
                         "DifferentWorkQueueCapacities"
                     ]
@@ -3857,12 +3860,11 @@
             ],
             "experiments": [
                 {
-                    "name": "enabled_ntp_native_dialog_20220713",
+                    "name": "enabled_ntp_native_dialog_with_approved_strings_M104_20220809",
                     "params": {
                         "discount-consent-ntp-variation": "4",
-                        "step-one-one-cart-content": "Get discount codes for $1",
-                        "step-one-three-carts-content": "Get discount codes for $1, $2, and more",
-                        "step-one-two-carts-content": "Get discount codes for $1 and $2"
+                        "step-one-static-content": "Let Google help you find discounts for your carts",
+                        "step-one-use-static-content": "true"
                     },
                     "enable_features": [
                         "DiscountConsentV2"
@@ -5178,6 +5180,9 @@
                 },
                 {
                     "name": "EnabledWithTextClassifier",
+                    "params": {
+                        "confidence_score_threshold": "0.0"
+                    },
                     "enable_features": [
                         "CalendarExperienceKit",
                         "EnableExpKitCalendarTextClassifier"
diff --git a/third_party/bidimapper/.gitignore b/third_party/bidimapper/.gitignore
new file mode 100644
index 0000000..fcfb397
--- /dev/null
+++ b/third_party/bidimapper/.gitignore
@@ -0,0 +1,3 @@
+chromium-bidi-main/
+src/
+revision.info
diff --git a/third_party/bidimapper/LICENSE b/third_party/bidimapper/LICENSE
new file mode 100644
index 0000000..f49a4e1
--- /dev/null
+++ b/third_party/bidimapper/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
\ No newline at end of file
diff --git a/third_party/bidimapper/OWNERS b/third_party/bidimapper/OWNERS
new file mode 100644
index 0000000..c0113f0
--- /dev/null
+++ b/third_party/bidimapper/OWNERS
@@ -0,0 +1,6 @@
+# primary reviewer
+nechaev@chromium.org
+sadym@chromium.org
+
+# secondary reviewer
+mathias@chromium.org
diff --git a/third_party/bidimapper/README.chromium b/third_party/bidimapper/README.chromium
new file mode 100644
index 0000000..70c159bb
--- /dev/null
+++ b/third_party/bidimapper/README.chromium
@@ -0,0 +1,32 @@
+Name: Implementation of WebDriver BiDi standard
+Short Name: chromium-bidi
+URL: https://github.com/GoogleChromeLabs/chromium-bidi/archive/4062a90162c96bf96a69a27998719f947d6f7297.zip
+Version: 0
+Date: 2022-08-11
+Revision: 4062a90162c96bf96a69a27998719f947d6f7297
+SHA-512: 23f384d3d94b5a5c6b90419127ffda9acbe1f860ce14a80b817586013e8b29a24ea18856848e10f996fe6e02721a9ac84288320b1d1f8496ef6e2237d7e89f9a
+License: Apache 2.0
+License File: LICENSE
+Security Critical: yes
+CPEPrefix: unknown
+
+Description:
+WebDriver BiDi implementation for ChromeDriver.
+The software is compiled into mapper.js script that is uploaded by ChromeDriver into the Chrome browser.
+
+Steps to build:
+Assume the git-revision-full-sha1 is the wanted revision.
+It can be found in the upstream repo: https://github.com/GoogleChromeLabs/chromium-bidi/commits/main.
+
+```bash
+cd directory-of-this-README.chromium
+./pull.sh git-revision-full-sha1
+./build.sh
+```
+
+The file README.chromium will be updated by build.sh
+
+Local Modifications:
+Local modifications are not allowed.
+The upstream is maintained by the Chromium developers.
+All the changes must be done there.
diff --git a/third_party/bidimapper/README.chromium.in b/third_party/bidimapper/README.chromium.in
new file mode 100644
index 0000000..1779d44
--- /dev/null
+++ b/third_party/bidimapper/README.chromium.in
@@ -0,0 +1,32 @@
+Name: Implementation of WebDriver BiDi standard
+Short Name: chromium-bidi
+URL: https://github.com/GoogleChromeLabs/chromium-bidi/archive/${REVISION}.zip
+Version: 0
+Date: ${DATE}
+Revision: ${REVISION}
+SHA-512: ${TAR-SHA512}
+License: Apache 2.0
+License File: LICENSE
+Security Critical: yes
+CPEPrefix: unknown
+
+Description:
+WebDriver BiDi implementation for ChromeDriver.
+The software is compiled into mapper.js script that is uploaded by ChromeDriver into the Chrome browser.
+
+Steps to build:
+Assume the git-revision-full-sha1 is the wanted revision.
+It can be found in the upstream repo: https://github.com/GoogleChromeLabs/chromium-bidi/commits/main.
+
+```bash
+cd directory-of-this-README.chromium
+./pull.sh git-revision-full-sha1
+./build.sh
+```
+
+The file README.chromium will be updated by build.sh
+
+Local Modifications:
+Local modifications are not allowed.
+The upstream is maintained by the Chromium developers.
+All the changes must be done there.
diff --git a/third_party/bidimapper/build.sh b/third_party/bidimapper/build.sh
new file mode 100755
index 0000000..90b13f15
--- /dev/null
+++ b/third_party/bidimapper/build.sh
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+
+# Copyright 2022 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+if [ ! -f 'revision.info' ]
+then
+  echo "File not found: revision.info" >&2
+  echo "Execute pull.sh first" >&2
+  exit 1
+fi
+
+dt=$(cut --fields=1 --delimiter=',' "revision.info")
+revision=$(cut --fields=2 --delimiter=',' "revision.info")
+sha512=$(cut --fields=3 --delimiter=',' "revision.info")
+
+if [ -z "$dt" -o -z "$revision" -o -z "$sha512" ]
+then
+  echo "Incorrect format of revision.info" >&2
+  echo "Execute pull.sh first" >&2
+  exit 1
+fi
+
+
+npm --prefix src install
+npm --prefix src run build-mapper
+
+cp src/src/.build/bidiMapper/mapper.js .
+
+cp README.chromium.in README.chromium
+sed --in-place --regexp-extended "s/\\$\\{DATE\\}/$dt/gi" README.chromium
+sed --in-place --regexp-extended "s/\\$\\{REVISION\\}/$revision/gi" README.chromium
+sed --in-place --regexp-extended "s/\\$\\{TAR-SHA512\\}/$sha512/gi" README.chromium
diff --git a/third_party/bidimapper/mapper.js b/third_party/bidimapper/mapper.js
new file mode 100644
index 0000000..e3fa067d
--- /dev/null
+++ b/third_party/bidimapper/mapper.js
@@ -0,0 +1,20 @@
+!function(){"use strict";function e(e){return(...t)=>{if(globalThis.document?.documentElement){console.log(e,...t);const n=function(e){const t=e+"_log",n=document.getElementById(t);if(n)return n;const s=document.createElement("div");return s.id=t,s.innerHTML=`<h3>${e}:</h3>`,document.body.appendChild(s),s}(e),s=document.createElement("pre");s.textContent=t.join(", "),n.appendChild(s)}}}class t{method;params;constructor(e,t){this.method=e,this.params=t}}var n;!function(e){e.assertEqual=e=>e,e.assertIs=function(e){},e.assertNever=function(e){throw new Error},e.arrayToEnum=e=>{const t={};for(const n of e)t[n]=n;return t},e.getValidEnumValues=t=>{const n=e.objectKeys(t).filter((e=>"number"!=typeof t[t[e]])),s={};for(const e of n)s[e]=t[e];return e.objectValues(s)},e.objectValues=t=>e.objectKeys(t).map((function(e){return t[e]})),e.objectKeys="function"==typeof Object.keys?e=>Object.keys(e):e=>{const t=[];for(const n in e)Object.prototype.hasOwnProperty.call(e,n)&&t.push(n);return t},e.find=(e,t)=>{for(const n of e)if(t(n))return n},e.isInteger="function"==typeof Number.isInteger?e=>Number.isInteger(e):e=>"number"==typeof e&&isFinite(e)&&Math.floor(e)===e,e.joinValues=function(e,t=" | "){return e.map((e=>"string"==typeof e?`'${e}'`:e)).join(t)}}(n||(n={}));const s=n.arrayToEnum(["string","nan","number","integer","float","boolean","date","bigint","symbol","function","undefined","null","array","object","unknown","promise","void","never","map","set"]),r=e=>{switch(typeof e){case"undefined":return s.undefined;case"string":return s.string;case"number":return isNaN(e)?s.nan:s.number;case"boolean":return s.boolean;case"function":return s.function;case"bigint":return s.bigint;case"object":return Array.isArray(e)?s.array:null===e?s.null:e.then&&"function"==typeof e.then&&e.catch&&"function"==typeof e.catch?s.promise:"undefined"!=typeof Map&&e instanceof Map?s.map:"undefined"!=typeof Set&&e instanceof Set?s.set:"undefined"!=typeof Date&&e instanceof Date?s.date:s.object;default:return s.unknown}},a=n.arrayToEnum(["invalid_type","invalid_literal","custom","invalid_union","invalid_union_discriminator","invalid_enum_value","unrecognized_keys","invalid_arguments","invalid_return_type","invalid_date","invalid_string","too_small","too_big","invalid_intersection_types","not_multiple_of"]);class i extends Error{constructor(e){super(),this.issues=[],this.addIssue=e=>{this.issues=[...this.issues,e]},this.addIssues=(e=[])=>{this.issues=[...this.issues,...e]};const t=new.target.prototype;Object.setPrototypeOf?Object.setPrototypeOf(this,t):this.__proto__=t,this.name="ZodError",this.issues=e}get errors(){return this.issues}format(e){const t=e||function(e){return e.message},n={_errors:[]},s=e=>{for(const r of e.issues)if("invalid_union"===r.code)r.unionErrors.map(s);else if("invalid_return_type"===r.code)s(r.returnTypeError);else if("invalid_arguments"===r.code)s(r.argumentsError);else if(0===r.path.length)n._errors.push(t(r));else{let e=n,s=0;for(;s<r.path.length;){const n=r.path[s];s===r.path.length-1?(e[n]=e[n]||{_errors:[]},e[n]._errors.push(t(r))):e[n]=e[n]||{_errors:[]},e=e[n],s++}}};return s(this),n}toString(){return this.message}get message(){return JSON.stringify(this.issues,b,2)}get isEmpty(){return 0===this.issues.length}flatten(e=(e=>e.message)){const t={},n=[];for(const s of this.issues)s.path.length>0?(t[s.path[0]]=t[s.path[0]]||[],t[s.path[0]].push(e(s))):n.push(e(s));return{formErrors:n,fieldErrors:t}}get formErrors(){return this.flatten()}}i.create=e=>new i(e);const o=(e,t)=>{let r;switch(e.code){case a.invalid_type:r=e.received===s.undefined?"Required":`Expected ${e.expected}, received ${e.received}`;break;case a.invalid_literal:r=`Invalid literal value, expected ${JSON.stringify(e.expected,b)}`;break;case a.unrecognized_keys:r=`Unrecognized key(s) in object: ${n.joinValues(e.keys,", ")}`;break;case a.invalid_union:r="Invalid input";break;case a.invalid_union_discriminator:r=`Invalid discriminator value. Expected ${n.joinValues(e.options)}`;break;case a.invalid_enum_value:r=`Invalid enum value. Expected ${n.joinValues(e.options)}, received '${e.received}'`;break;case a.invalid_arguments:r="Invalid function arguments";break;case a.invalid_return_type:r="Invalid function return type";break;case a.invalid_date:r="Invalid date";break;case a.invalid_string:"object"==typeof e.validation?"startsWith"in e.validation?r=`Invalid input: must start with "${e.validation.startsWith}"`:"endsWith"in e.validation?r=`Invalid input: must end with "${e.validation.endsWith}"`:n.assertNever(e.validation):r="regex"!==e.validation?`Invalid ${e.validation}`:"Invalid";break;case a.too_small:r="array"===e.type?`Array must contain ${e.inclusive?"at least":"more than"} ${e.minimum} element(s)`:"string"===e.type?`String must contain ${e.inclusive?"at least":"over"} ${e.minimum} character(s)`:"number"===e.type?`Number must be greater than ${e.inclusive?"or equal to ":""}${e.minimum}`:"date"===e.type?`Date must be greater than ${e.inclusive?"or equal to ":""}${new Date(e.minimum)}`:"Invalid input";break;case a.too_big:r="array"===e.type?`Array must contain ${e.inclusive?"at most":"less than"} ${e.maximum} element(s)`:"string"===e.type?`String must contain ${e.inclusive?"at most":"under"} ${e.maximum} character(s)`:"number"===e.type?`Number must be less than ${e.inclusive?"or equal to ":""}${e.maximum}`:"date"===e.type?`Date must be smaller than ${e.inclusive?"or equal to ":""}${new Date(e.maximum)}`:"Invalid input";break;case a.custom:r="Invalid input";break;case a.invalid_intersection_types:r="Intersection results could not be merged";break;case a.not_multiple_of:r=`Number must be a multiple of ${e.multipleOf}`;break;default:r=t.defaultError,n.assertNever(e)}return{message:r}};let c=o;function d(){return c}const l=e=>{const{data:t,path:n,errorMaps:s,issueData:r}=e,a=[...n,...r.path||[]],i={...r,path:a};let o="";const c=s.filter((e=>!!e)).slice().reverse();for(const e of c)o=e(i,{data:t,defaultError:o}).message;return{...r,path:a,message:r.message||o}};function u(e,t){const n=l({issueData:t,data:e.data,path:e.path,errorMaps:[e.common.contextualErrorMap,e.schemaErrorMap,d(),o].filter((e=>!!e))});e.common.issues.push(n)}class h{constructor(){this.value="valid"}dirty(){"valid"===this.value&&(this.value="dirty")}abort(){"aborted"!==this.value&&(this.value="aborted")}static mergeArray(e,t){const n=[];for(const s of t){if("aborted"===s.status)return p;"dirty"===s.status&&e.dirty(),n.push(s.value)}return{status:e.value,value:n}}static async mergeObjectAsync(e,t){const n=[];for(const e of t)n.push({key:await e.key,value:await e.value});return h.mergeObjectSync(e,n)}static mergeObjectSync(e,t){const n={};for(const s of t){const{key:t,value:r}=s;if("aborted"===t.status)return p;if("aborted"===r.status)return p;"dirty"===t.status&&e.dirty(),"dirty"===r.status&&e.dirty(),(void 0!==r.value||s.alwaysSet)&&(n[t.value]=r.value)}return{status:e.value,value:n}}}const p=Object.freeze({status:"aborted"}),m=e=>({status:"valid",value:e}),g=e=>"aborted"===e.status,f=e=>"dirty"===e.status,v=e=>"valid"===e.status,y=e=>void 0!==typeof Promise&&e instanceof Promise,b=(e,t)=>"bigint"==typeof t?t.toString():t;var _;!function(e){e.errToObj=e=>"string"==typeof e?{message:e}:e||{},e.toString=e=>"string"==typeof e?e:null==e?void 0:e.message}(_||(_={}));class w{constructor(e,t,n,s){this.parent=e,this.data=t,this._path=n,this._key=s}get path(){return this._path.concat(this._key)}}const x=(e,t)=>{if(v(t))return{success:!0,data:t.value};if(!e.common.issues.length)throw new Error("Validation failed but no issues detected.");return{success:!1,error:new i(e.common.issues)}};function C(e){if(!e)return{};const{errorMap:t,invalid_type_error:n,required_error:s,description:r}=e;if(t&&(n||s))throw new Error('Can\'t use "invalid" or "required" in conjunction with custom error map.');if(t)return{errorMap:t,description:r};return{errorMap:(e,t)=>"invalid_type"!==e.code?{message:t.defaultError}:void 0===t.data?{message:null!=s?s:t.defaultError}:{message:null!=n?n:t.defaultError},description:r}}class T{constructor(e){this.spa=this.safeParseAsync,this.superRefine=this._refinement,this._def=e,this.parse=this.parse.bind(this),this.safeParse=this.safeParse.bind(this),this.parseAsync=this.parseAsync.bind(this),this.safeParseAsync=this.safeParseAsync.bind(this),this.spa=this.spa.bind(this),this.refine=this.refine.bind(this),this.refinement=this.refinement.bind(this),this.superRefine=this.superRefine.bind(this),this.optional=this.optional.bind(this),this.nullable=this.nullable.bind(this),this.nullish=this.nullish.bind(this),this.array=this.array.bind(this),this.promise=this.promise.bind(this),this.or=this.or.bind(this),this.and=this.and.bind(this),this.transform=this.transform.bind(this),this.default=this.default.bind(this),this.describe=this.describe.bind(this),this.isNullable=this.isNullable.bind(this),this.isOptional=this.isOptional.bind(this)}get description(){return this._def.description}_getType(e){return r(e.data)}_getOrReturnCtx(e,t){return t||{common:e.parent.common,data:e.data,parsedType:r(e.data),schemaErrorMap:this._def.errorMap,path:e.path,parent:e.parent}}_processInputParams(e){return{status:new h,ctx:{common:e.parent.common,data:e.data,parsedType:r(e.data),schemaErrorMap:this._def.errorMap,path:e.path,parent:e.parent}}}_parseSync(e){const t=this._parse(e);if(y(t))throw new Error("Synchronous parse encountered promise.");return t}_parseAsync(e){const t=this._parse(e);return Promise.resolve(t)}parse(e,t){const n=this.safeParse(e,t);if(n.success)return n.data;throw n.error}safeParse(e,t){var n;const s={common:{issues:[],async:null!==(n=null==t?void 0:t.async)&&void 0!==n&&n,contextualErrorMap:null==t?void 0:t.errorMap},path:(null==t?void 0:t.path)||[],schemaErrorMap:this._def.errorMap,parent:null,data:e,parsedType:r(e)},a=this._parseSync({data:e,path:s.path,parent:s});return x(s,a)}async parseAsync(e,t){const n=await this.safeParseAsync(e,t);if(n.success)return n.data;throw n.error}async safeParseAsync(e,t){const n={common:{issues:[],contextualErrorMap:null==t?void 0:t.errorMap,async:!0},path:(null==t?void 0:t.path)||[],schemaErrorMap:this._def.errorMap,parent:null,data:e,parsedType:r(e)},s=this._parse({data:e,path:[],parent:n}),a=await(y(s)?s:Promise.resolve(s));return x(n,a)}refine(e,t){const n=e=>"string"==typeof t||void 0===t?{message:t}:"function"==typeof t?t(e):t;return this._refinement(((t,s)=>{const r=e(t),i=()=>s.addIssue({code:a.custom,...n(t)});return"undefined"!=typeof Promise&&r instanceof Promise?r.then((e=>!!e||(i(),!1))):!!r||(i(),!1)}))}refinement(e,t){return this._refinement(((n,s)=>!!e(n)||(s.addIssue("function"==typeof t?t(n,s):t),!1)))}_refinement(e){return new ie({schema:this,typeName:ge.ZodEffects,effect:{type:"refinement",refinement:e}})}optional(){return oe.create(this)}nullable(){return ce.create(this)}nullish(){return this.optional().nullable()}array(){return B.create(this)}promise(){return ae.create(this)}or(e){return W.create([this,e])}and(e){return q.create(this,e)}transform(e){return new ie({schema:this,typeName:ge.ZodEffects,effect:{type:"transform",transform:e}})}default(e){return new de({innerType:this,defaultValue:"function"==typeof e?e:()=>e,typeName:ge.ZodDefault})}brand(){return new he({typeName:ge.ZodBranded,type:this,...C(void 0)})}describe(e){return new(0,this.constructor)({...this._def,description:e})}isOptional(){return this.safeParse(void 0).success}isNullable(){return this.safeParse(null).success}}const S=/^c[^\s-]{8,}$/i,k=/^([a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[a-f0-9]{4}-[a-f0-9]{12}|00000000-0000-0000-0000-000000000000)$/i,O=/^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i;class E extends T{constructor(){super(...arguments),this._regex=(e,t,n)=>this.refinement((t=>e.test(t)),{validation:t,code:a.invalid_string,..._.errToObj(n)}),this.nonempty=e=>this.min(1,_.errToObj(e)),this.trim=()=>new E({...this._def,checks:[...this._def.checks,{kind:"trim"}]})}_parse(e){if(this._getType(e)!==s.string){const t=this._getOrReturnCtx(e);return u(t,{code:a.invalid_type,expected:s.string,received:t.parsedType}),p}const t=new h;let r;for(const s of this._def.checks)if("min"===s.kind)e.data.length<s.value&&(r=this._getOrReturnCtx(e,r),u(r,{code:a.too_small,minimum:s.value,type:"string",inclusive:!0,message:s.message}),t.dirty());else if("max"===s.kind)e.data.length>s.value&&(r=this._getOrReturnCtx(e,r),u(r,{code:a.too_big,maximum:s.value,type:"string",inclusive:!0,message:s.message}),t.dirty());else if("email"===s.kind)O.test(e.data)||(r=this._getOrReturnCtx(e,r),u(r,{validation:"email",code:a.invalid_string,message:s.message}),t.dirty());else if("uuid"===s.kind)k.test(e.data)||(r=this._getOrReturnCtx(e,r),u(r,{validation:"uuid",code:a.invalid_string,message:s.message}),t.dirty());else if("cuid"===s.kind)S.test(e.data)||(r=this._getOrReturnCtx(e,r),u(r,{validation:"cuid",code:a.invalid_string,message:s.message}),t.dirty());else if("url"===s.kind)try{new URL(e.data)}catch(n){r=this._getOrReturnCtx(e,r),u(r,{validation:"url",code:a.invalid_string,message:s.message}),t.dirty()}else if("regex"===s.kind){s.regex.lastIndex=0;s.regex.test(e.data)||(r=this._getOrReturnCtx(e,r),u(r,{validation:"regex",code:a.invalid_string,message:s.message}),t.dirty())}else"trim"===s.kind?e.data=e.data.trim():"startsWith"===s.kind?e.data.startsWith(s.value)||(r=this._getOrReturnCtx(e,r),u(r,{code:a.invalid_string,validation:{startsWith:s.value},message:s.message}),t.dirty()):"endsWith"===s.kind?e.data.endsWith(s.value)||(r=this._getOrReturnCtx(e,r),u(r,{code:a.invalid_string,validation:{endsWith:s.value},message:s.message}),t.dirty()):n.assertNever(s);return{status:t.value,value:e.data}}_addCheck(e){return new E({...this._def,checks:[...this._def.checks,e]})}email(e){return this._addCheck({kind:"email",..._.errToObj(e)})}url(e){return this._addCheck({kind:"url",..._.errToObj(e)})}uuid(e){return this._addCheck({kind:"uuid",..._.errToObj(e)})}cuid(e){return this._addCheck({kind:"cuid",..._.errToObj(e)})}regex(e,t){return this._addCheck({kind:"regex",regex:e,..._.errToObj(t)})}startsWith(e,t){return this._addCheck({kind:"startsWith",value:e,..._.errToObj(t)})}endsWith(e,t){return this._addCheck({kind:"endsWith",value:e,..._.errToObj(t)})}min(e,t){return this._addCheck({kind:"min",value:e,..._.errToObj(t)})}max(e,t){return this._addCheck({kind:"max",value:e,..._.errToObj(t)})}length(e,t){return this.min(e,t).max(e,t)}get isEmail(){return!!this._def.checks.find((e=>"email"===e.kind))}get isURL(){return!!this._def.checks.find((e=>"url"===e.kind))}get isUUID(){return!!this._def.checks.find((e=>"uuid"===e.kind))}get isCUID(){return!!this._def.checks.find((e=>"cuid"===e.kind))}get minLength(){let e=null;for(const t of this._def.checks)"min"===t.kind&&(null===e||t.value>e)&&(e=t.value);return e}get maxLength(){let e=null;for(const t of this._def.checks)"max"===t.kind&&(null===e||t.value<e)&&(e=t.value);return e}}function I(e,t){const n=(e.toString().split(".")[1]||"").length,s=(t.toString().split(".")[1]||"").length,r=n>s?n:s;return parseInt(e.toFixed(r).replace(".",""))%parseInt(t.toFixed(r).replace(".",""))/Math.pow(10,r)}E.create=e=>new E({checks:[],typeName:ge.ZodString,...C(e)});class D extends T{constructor(){super(...arguments),this.min=this.gte,this.max=this.lte,this.step=this.multipleOf}_parse(e){if(this._getType(e)!==s.number){const t=this._getOrReturnCtx(e);return u(t,{code:a.invalid_type,expected:s.number,received:t.parsedType}),p}let t;const r=new h;for(const s of this._def.checks)if("int"===s.kind)n.isInteger(e.data)||(t=this._getOrReturnCtx(e,t),u(t,{code:a.invalid_type,expected:"integer",received:"float",message:s.message}),r.dirty());else if("min"===s.kind){(s.inclusive?e.data<s.value:e.data<=s.value)&&(t=this._getOrReturnCtx(e,t),u(t,{code:a.too_small,minimum:s.value,type:"number",inclusive:s.inclusive,message:s.message}),r.dirty())}else if("max"===s.kind){(s.inclusive?e.data>s.value:e.data>=s.value)&&(t=this._getOrReturnCtx(e,t),u(t,{code:a.too_big,maximum:s.value,type:"number",inclusive:s.inclusive,message:s.message}),r.dirty())}else"multipleOf"===s.kind?0!==I(e.data,s.value)&&(t=this._getOrReturnCtx(e,t),u(t,{code:a.not_multiple_of,multipleOf:s.value,message:s.message}),r.dirty()):n.assertNever(s);return{status:r.value,value:e.data}}gte(e,t){return this.setLimit("min",e,!0,_.toString(t))}gt(e,t){return this.setLimit("min",e,!1,_.toString(t))}lte(e,t){return this.setLimit("max",e,!0,_.toString(t))}lt(e,t){return this.setLimit("max",e,!1,_.toString(t))}setLimit(e,t,n,s){return new D({...this._def,checks:[...this._def.checks,{kind:e,value:t,inclusive:n,message:_.toString(s)}]})}_addCheck(e){return new D({...this._def,checks:[...this._def.checks,e]})}int(e){return this._addCheck({kind:"int",message:_.toString(e)})}positive(e){return this._addCheck({kind:"min",value:0,inclusive:!1,message:_.toString(e)})}negative(e){return this._addCheck({kind:"max",value:0,inclusive:!1,message:_.toString(e)})}nonpositive(e){return this._addCheck({kind:"max",value:0,inclusive:!0,message:_.toString(e)})}nonnegative(e){return this._addCheck({kind:"min",value:0,inclusive:!0,message:_.toString(e)})}multipleOf(e,t){return this._addCheck({kind:"multipleOf",value:e,message:_.toString(t)})}get minValue(){let e=null;for(const t of this._def.checks)"min"===t.kind&&(null===e||t.value>e)&&(e=t.value);return e}get maxValue(){let e=null;for(const t of this._def.checks)"max"===t.kind&&(null===e||t.value<e)&&(e=t.value);return e}get isInt(){return!!this._def.checks.find((e=>"int"===e.kind))}}D.create=e=>new D({checks:[],typeName:ge.ZodNumber,...C(e)});class P extends T{_parse(e){if(this._getType(e)!==s.bigint){const t=this._getOrReturnCtx(e);return u(t,{code:a.invalid_type,expected:s.bigint,received:t.parsedType}),p}return m(e.data)}}P.create=e=>new P({typeName:ge.ZodBigInt,...C(e)});class N extends T{_parse(e){if(this._getType(e)!==s.boolean){const t=this._getOrReturnCtx(e);return u(t,{code:a.invalid_type,expected:s.boolean,received:t.parsedType}),p}return m(e.data)}}N.create=e=>new N({typeName:ge.ZodBoolean,...C(e)});class j extends T{_parse(e){if(this._getType(e)!==s.date){const t=this._getOrReturnCtx(e);return u(t,{code:a.invalid_type,expected:s.date,received:t.parsedType}),p}if(isNaN(e.data.getTime())){return u(this._getOrReturnCtx(e),{code:a.invalid_date}),p}const t=new h;let r;for(const s of this._def.checks)"min"===s.kind?e.data.getTime()<s.value&&(r=this._getOrReturnCtx(e,r),u(r,{code:a.too_small,message:s.message,inclusive:!0,minimum:s.value,type:"date"}),t.dirty()):"max"===s.kind?e.data.getTime()>s.value&&(r=this._getOrReturnCtx(e,r),u(r,{code:a.too_big,message:s.message,inclusive:!0,maximum:s.value,type:"date"}),t.dirty()):n.assertNever(s);return{status:t.value,value:new Date(e.data.getTime())}}_addCheck(e){return new j({...this._def,checks:[...this._def.checks,e]})}min(e,t){return this._addCheck({kind:"min",value:e.getTime(),message:_.toString(t)})}max(e,t){return this._addCheck({kind:"max",value:e.getTime(),message:_.toString(t)})}get minDate(){let e=null;for(const t of this._def.checks)"min"===t.kind&&(null===e||t.value>e)&&(e=t.value);return null!=e?new Date(e):null}get maxDate(){let e=null;for(const t of this._def.checks)"max"===t.kind&&(null===e||t.value<e)&&(e=t.value);return null!=e?new Date(e):null}}j.create=e=>new j({checks:[],typeName:ge.ZodDate,...C(e)});class M extends T{_parse(e){if(this._getType(e)!==s.undefined){const t=this._getOrReturnCtx(e);return u(t,{code:a.invalid_type,expected:s.undefined,received:t.parsedType}),p}return m(e.data)}}M.create=e=>new M({typeName:ge.ZodUndefined,...C(e)});class R extends T{_parse(e){if(this._getType(e)!==s.null){const t=this._getOrReturnCtx(e);return u(t,{code:a.invalid_type,expected:s.null,received:t.parsedType}),p}return m(e.data)}}R.create=e=>new R({typeName:ge.ZodNull,...C(e)});class A extends T{constructor(){super(...arguments),this._any=!0}_parse(e){return m(e.data)}}A.create=e=>new A({typeName:ge.ZodAny,...C(e)});class L extends T{constructor(){super(...arguments),this._unknown=!0}_parse(e){return m(e.data)}}L.create=e=>new L({typeName:ge.ZodUnknown,...C(e)});class Z extends T{_parse(e){const t=this._getOrReturnCtx(e);return u(t,{code:a.invalid_type,expected:s.never,received:t.parsedType}),p}}Z.create=e=>new Z({typeName:ge.ZodNever,...C(e)});class F extends T{_parse(e){if(this._getType(e)!==s.undefined){const t=this._getOrReturnCtx(e);return u(t,{code:a.invalid_type,expected:s.void,received:t.parsedType}),p}return m(e.data)}}F.create=e=>new F({typeName:ge.ZodVoid,...C(e)});class B extends T{_parse(e){const{ctx:t,status:n}=this._processInputParams(e),r=this._def;if(t.parsedType!==s.array)return u(t,{code:a.invalid_type,expected:s.array,received:t.parsedType}),p;if(null!==r.minLength&&t.data.length<r.minLength.value&&(u(t,{code:a.too_small,minimum:r.minLength.value,type:"array",inclusive:!0,message:r.minLength.message}),n.dirty()),null!==r.maxLength&&t.data.length>r.maxLength.value&&(u(t,{code:a.too_big,maximum:r.maxLength.value,type:"array",inclusive:!0,message:r.maxLength.message}),n.dirty()),t.common.async)return Promise.all(t.data.map(((e,n)=>r.type._parseAsync(new w(t,e,t.path,n))))).then((e=>h.mergeArray(n,e)));const i=t.data.map(((e,n)=>r.type._parseSync(new w(t,e,t.path,n))));return h.mergeArray(n,i)}get element(){return this._def.type}min(e,t){return new B({...this._def,minLength:{value:e,message:_.toString(t)}})}max(e,t){return new B({...this._def,maxLength:{value:e,message:_.toString(t)}})}length(e,t){return this.min(e,t).max(e,t)}nonempty(e){return this.min(1,e)}}var z;B.create=(e,t)=>new B({type:e,minLength:null,maxLength:null,typeName:ge.ZodArray,...C(t)}),function(e){e.mergeShapes=(e,t)=>({...e,...t})}(z||(z={}));const V=e=>t=>new $({...e,shape:()=>({...e.shape(),...t})});function U(e){if(e instanceof $){const t={};for(const n in e.shape){const s=e.shape[n];t[n]=oe.create(U(s))}return new $({...e._def,shape:()=>t})}return e instanceof B?B.create(U(e.element)):e instanceof oe?oe.create(U(e.unwrap())):e instanceof ce?ce.create(U(e.unwrap())):e instanceof J?J.create(e.items.map((e=>U(e)))):e}class $ extends T{constructor(){super(...arguments),this._cached=null,this.nonstrict=this.passthrough,this.augment=V(this._def),this.extend=V(this._def)}_getCached(){if(null!==this._cached)return this._cached;const e=this._def.shape(),t=n.objectKeys(e);return this._cached={shape:e,keys:t}}_parse(e){if(this._getType(e)!==s.object){const t=this._getOrReturnCtx(e);return u(t,{code:a.invalid_type,expected:s.object,received:t.parsedType}),p}const{status:t,ctx:n}=this._processInputParams(e),{shape:r,keys:i}=this._getCached(),o=[];for(const e in n.data)i.includes(e)||o.push(e);const c=[];for(const e of i){const t=r[e],s=n.data[e];c.push({key:{status:"valid",value:e},value:t._parse(new w(n,s,n.path,e)),alwaysSet:e in n.data})}if(this._def.catchall instanceof Z){const e=this._def.unknownKeys;if("passthrough"===e)for(const e of o)c.push({key:{status:"valid",value:e},value:{status:"valid",value:n.data[e]}});else if("strict"===e)o.length>0&&(u(n,{code:a.unrecognized_keys,keys:o}),t.dirty());else if("strip"!==e)throw new Error("Internal ZodObject error: invalid unknownKeys value.")}else{const e=this._def.catchall;for(const t of o){const s=n.data[t];c.push({key:{status:"valid",value:t},value:e._parse(new w(n,s,n.path,t)),alwaysSet:t in n.data})}}return n.common.async?Promise.resolve().then((async()=>{const e=[];for(const t of c){const n=await t.key;e.push({key:n,value:await t.value,alwaysSet:t.alwaysSet})}return e})).then((e=>h.mergeObjectSync(t,e))):h.mergeObjectSync(t,c)}get shape(){return this._def.shape()}strict(e){return _.errToObj,new $({...this._def,unknownKeys:"strict",...void 0!==e?{errorMap:(t,n)=>{var s,r,a,i;const o=null!==(a=null===(r=(s=this._def).errorMap)||void 0===r?void 0:r.call(s,t,n).message)&&void 0!==a?a:n.defaultError;return"unrecognized_keys"===t.code?{message:null!==(i=_.errToObj(e).message)&&void 0!==i?i:o}:{message:o}}}:{}})}strip(){return new $({...this._def,unknownKeys:"strip"})}passthrough(){return new $({...this._def,unknownKeys:"passthrough"})}setKey(e,t){return this.augment({[e]:t})}merge(e){return new $({unknownKeys:e._def.unknownKeys,catchall:e._def.catchall,shape:()=>z.mergeShapes(this._def.shape(),e._def.shape()),typeName:ge.ZodObject})}catchall(e){return new $({...this._def,catchall:e})}pick(e){const t={};return n.objectKeys(e).map((e=>{this.shape[e]&&(t[e]=this.shape[e])})),new $({...this._def,shape:()=>t})}omit(e){const t={};return n.objectKeys(this.shape).map((s=>{-1===n.objectKeys(e).indexOf(s)&&(t[s]=this.shape[s])})),new $({...this._def,shape:()=>t})}deepPartial(){return U(this)}partial(e){const t={};if(e)return n.objectKeys(this.shape).map((s=>{-1===n.objectKeys(e).indexOf(s)?t[s]=this.shape[s]:t[s]=this.shape[s].optional()})),new $({...this._def,shape:()=>t});for(const e in this.shape){const n=this.shape[e];t[e]=n.optional()}return new $({...this._def,shape:()=>t})}required(){const e={};for(const t in this.shape){let n=this.shape[t];for(;n instanceof oe;)n=n._def.innerType;e[t]=n}return new $({...this._def,shape:()=>e})}keyof(){return ne(n.objectKeys(this.shape))}}$.create=(e,t)=>new $({shape:()=>e,unknownKeys:"strip",catchall:Z.create(),typeName:ge.ZodObject,...C(t)}),$.strictCreate=(e,t)=>new $({shape:()=>e,unknownKeys:"strict",catchall:Z.create(),typeName:ge.ZodObject,...C(t)}),$.lazycreate=(e,t)=>new $({shape:e,unknownKeys:"strip",catchall:Z.create(),typeName:ge.ZodObject,...C(t)});class W extends T{_parse(e){const{ctx:t}=this._processInputParams(e),n=this._def.options;if(t.common.async)return Promise.all(n.map((async e=>{const n={...t,common:{...t.common,issues:[]},parent:null};return{result:await e._parseAsync({data:t.data,path:t.path,parent:n}),ctx:n}}))).then((function(e){for(const t of e)if("valid"===t.result.status)return t.result;for(const n of e)if("dirty"===n.result.status)return t.common.issues.push(...n.ctx.common.issues),n.result;const n=e.map((e=>new i(e.ctx.common.issues)));return u(t,{code:a.invalid_union,unionErrors:n}),p}));{let e;const s=[];for(const r of n){const n={...t,common:{...t.common,issues:[]},parent:null},a=r._parseSync({data:t.data,path:t.path,parent:n});if("valid"===a.status)return a;"dirty"!==a.status||e||(e={result:a,ctx:n}),n.common.issues.length&&s.push(n.common.issues)}if(e)return t.common.issues.push(...e.ctx.common.issues),e.result;const r=s.map((e=>new i(e)));return u(t,{code:a.invalid_union,unionErrors:r}),p}}get options(){return this._def.options}}W.create=(e,t)=>new W({options:e,typeName:ge.ZodUnion,...C(t)});class K extends T{_parse(e){const{ctx:t}=this._processInputParams(e);if(t.parsedType!==s.object)return u(t,{code:a.invalid_type,expected:s.object,received:t.parsedType}),p;const n=this.discriminator,r=t.data[n],i=this.options.get(r);return i?t.common.async?i._parseAsync({data:t.data,path:t.path,parent:t}):i._parseSync({data:t.data,path:t.path,parent:t}):(u(t,{code:a.invalid_union_discriminator,options:this.validDiscriminatorValues,path:[n]}),p)}get discriminator(){return this._def.discriminator}get validDiscriminatorValues(){return Array.from(this.options.keys())}get options(){return this._def.options}static create(e,t,n){const s=new Map;try{t.forEach((t=>{const n=t.shape[e].value;s.set(n,t)}))}catch(e){throw new Error("The discriminator value could not be extracted from all the provided schemas")}if(s.size!==t.length)throw new Error("Some of the discriminator values are not unique");return new K({typeName:ge.ZodDiscriminatedUnion,discriminator:e,options:s,...C(n)})}}function H(e,t){const a=r(e),i=r(t);if(e===t)return{valid:!0,data:e};if(a===s.object&&i===s.object){const s=n.objectKeys(t),r=n.objectKeys(e).filter((e=>-1!==s.indexOf(e))),a={...e,...t};for(const n of r){const s=H(e[n],t[n]);if(!s.valid)return{valid:!1};a[n]=s.data}return{valid:!0,data:a}}if(a===s.array&&i===s.array){if(e.length!==t.length)return{valid:!1};const n=[];for(let s=0;s<e.length;s++){const r=H(e[s],t[s]);if(!r.valid)return{valid:!1};n.push(r.data)}return{valid:!0,data:n}}return a===s.date&&i===s.date&&+e==+t?{valid:!0,data:e}:{valid:!1}}class q extends T{_parse(e){const{status:t,ctx:n}=this._processInputParams(e),s=(e,s)=>{if(g(e)||g(s))return p;const r=H(e.value,s.value);return r.valid?((f(e)||f(s))&&t.dirty(),{status:t.value,value:r.data}):(u(n,{code:a.invalid_intersection_types}),p)};return n.common.async?Promise.all([this._def.left._parseAsync({data:n.data,path:n.path,parent:n}),this._def.right._parseAsync({data:n.data,path:n.path,parent:n})]).then((([e,t])=>s(e,t))):s(this._def.left._parseSync({data:n.data,path:n.path,parent:n}),this._def.right._parseSync({data:n.data,path:n.path,parent:n}))}}q.create=(e,t,n)=>new q({left:e,right:t,typeName:ge.ZodIntersection,...C(n)});class J extends T{_parse(e){const{status:t,ctx:n}=this._processInputParams(e);if(n.parsedType!==s.array)return u(n,{code:a.invalid_type,expected:s.array,received:n.parsedType}),p;if(n.data.length<this._def.items.length)return u(n,{code:a.too_small,minimum:this._def.items.length,inclusive:!0,type:"array"}),p;!this._def.rest&&n.data.length>this._def.items.length&&(u(n,{code:a.too_big,maximum:this._def.items.length,inclusive:!0,type:"array"}),t.dirty());const r=n.data.map(((e,t)=>{const s=this._def.items[t]||this._def.rest;return s?s._parse(new w(n,e,n.path,t)):null})).filter((e=>!!e));return n.common.async?Promise.all(r).then((e=>h.mergeArray(t,e))):h.mergeArray(t,r)}get items(){return this._def.items}rest(e){return new J({...this._def,rest:e})}}J.create=(e,t)=>new J({items:e,typeName:ge.ZodTuple,rest:null,...C(t)});class G extends T{get keySchema(){return this._def.keyType}get valueSchema(){return this._def.valueType}_parse(e){const{status:t,ctx:n}=this._processInputParams(e);if(n.parsedType!==s.object)return u(n,{code:a.invalid_type,expected:s.object,received:n.parsedType}),p;const r=[],i=this._def.keyType,o=this._def.valueType;for(const e in n.data)r.push({key:i._parse(new w(n,e,n.path,e)),value:o._parse(new w(n,n.data[e],n.path,e))});return n.common.async?h.mergeObjectAsync(t,r):h.mergeObjectSync(t,r)}get element(){return this._def.valueType}static create(e,t,n){return new G(t instanceof T?{keyType:e,valueType:t,typeName:ge.ZodRecord,...C(n)}:{keyType:E.create(),valueType:e,typeName:ge.ZodRecord,...C(t)})}}class X extends T{_parse(e){const{status:t,ctx:n}=this._processInputParams(e);if(n.parsedType!==s.map)return u(n,{code:a.invalid_type,expected:s.map,received:n.parsedType}),p;const r=this._def.keyType,i=this._def.valueType,o=[...n.data.entries()].map((([e,t],s)=>({key:r._parse(new w(n,e,n.path,[s,"key"])),value:i._parse(new w(n,t,n.path,[s,"value"]))})));if(n.common.async){const e=new Map;return Promise.resolve().then((async()=>{for(const n of o){const s=await n.key,r=await n.value;if("aborted"===s.status||"aborted"===r.status)return p;"dirty"!==s.status&&"dirty"!==r.status||t.dirty(),e.set(s.value,r.value)}return{status:t.value,value:e}}))}{const e=new Map;for(const n of o){const s=n.key,r=n.value;if("aborted"===s.status||"aborted"===r.status)return p;"dirty"!==s.status&&"dirty"!==r.status||t.dirty(),e.set(s.value,r.value)}return{status:t.value,value:e}}}}X.create=(e,t,n)=>new X({valueType:t,keyType:e,typeName:ge.ZodMap,...C(n)});class Q extends T{_parse(e){const{status:t,ctx:n}=this._processInputParams(e);if(n.parsedType!==s.set)return u(n,{code:a.invalid_type,expected:s.set,received:n.parsedType}),p;const r=this._def;null!==r.minSize&&n.data.size<r.minSize.value&&(u(n,{code:a.too_small,minimum:r.minSize.value,type:"set",inclusive:!0,message:r.minSize.message}),t.dirty()),null!==r.maxSize&&n.data.size>r.maxSize.value&&(u(n,{code:a.too_big,maximum:r.maxSize.value,type:"set",inclusive:!0,message:r.maxSize.message}),t.dirty());const i=this._def.valueType;function o(e){const n=new Set;for(const s of e){if("aborted"===s.status)return p;"dirty"===s.status&&t.dirty(),n.add(s.value)}return{status:t.value,value:n}}const c=[...n.data.values()].map(((e,t)=>i._parse(new w(n,e,n.path,t))));return n.common.async?Promise.all(c).then((e=>o(e))):o(c)}min(e,t){return new Q({...this._def,minSize:{value:e,message:_.toString(t)}})}max(e,t){return new Q({...this._def,maxSize:{value:e,message:_.toString(t)}})}size(e,t){return this.min(e,t).max(e,t)}nonempty(e){return this.min(1,e)}}Q.create=(e,t)=>new Q({valueType:e,minSize:null,maxSize:null,typeName:ge.ZodSet,...C(t)});class Y extends T{constructor(){super(...arguments),this.validate=this.implement}_parse(e){const{ctx:t}=this._processInputParams(e);if(t.parsedType!==s.function)return u(t,{code:a.invalid_type,expected:s.function,received:t.parsedType}),p;function n(e,n){return l({data:e,path:t.path,errorMaps:[t.common.contextualErrorMap,t.schemaErrorMap,d(),o].filter((e=>!!e)),issueData:{code:a.invalid_arguments,argumentsError:n}})}function r(e,n){return l({data:e,path:t.path,errorMaps:[t.common.contextualErrorMap,t.schemaErrorMap,d(),o].filter((e=>!!e)),issueData:{code:a.invalid_return_type,returnTypeError:n}})}const c={errorMap:t.common.contextualErrorMap},h=t.data;return this._def.returns instanceof ae?m((async(...e)=>{const t=new i([]),s=await this._def.args.parseAsync(e,c).catch((s=>{throw t.addIssue(n(e,s)),t})),a=await h(...s);return await this._def.returns._def.type.parseAsync(a,c).catch((e=>{throw t.addIssue(r(a,e)),t}))})):m(((...e)=>{const t=this._def.args.safeParse(e,c);if(!t.success)throw new i([n(e,t.error)]);const s=h(...t.data),a=this._def.returns.safeParse(s,c);if(!a.success)throw new i([r(s,a.error)]);return a.data}))}parameters(){return this._def.args}returnType(){return this._def.returns}args(...e){return new Y({...this._def,args:J.create(e).rest(L.create())})}returns(e){return new Y({...this._def,returns:e})}implement(e){return this.parse(e)}strictImplement(e){return this.parse(e)}}Y.create=(e,t,n)=>new Y({args:e?e.rest(L.create()):J.create([]).rest(L.create()),returns:t||L.create(),typeName:ge.ZodFunction,...C(n)});class ee extends T{get schema(){return this._def.getter()}_parse(e){const{ctx:t}=this._processInputParams(e);return this._def.getter()._parse({data:t.data,path:t.path,parent:t})}}ee.create=(e,t)=>new ee({getter:e,typeName:ge.ZodLazy,...C(t)});class te extends T{_parse(e){if(e.data!==this._def.value){return u(this._getOrReturnCtx(e),{code:a.invalid_literal,expected:this._def.value}),p}return{status:"valid",value:e.data}}get value(){return this._def.value}}function ne(e,t){return new se({values:e,typeName:ge.ZodEnum,...C(t)})}te.create=(e,t)=>new te({value:e,typeName:ge.ZodLiteral,...C(t)});class se extends T{_parse(e){if("string"!=typeof e.data){const t=this._getOrReturnCtx(e),s=this._def.values;return u(t,{expected:n.joinValues(s),received:t.parsedType,code:a.invalid_type}),p}if(-1===this._def.values.indexOf(e.data)){const t=this._getOrReturnCtx(e),n=this._def.values;return u(t,{received:t.data,code:a.invalid_enum_value,options:n}),p}return m(e.data)}get options(){return this._def.values}get enum(){const e={};for(const t of this._def.values)e[t]=t;return e}get Values(){const e={};for(const t of this._def.values)e[t]=t;return e}get Enum(){const e={};for(const t of this._def.values)e[t]=t;return e}}se.create=ne;class re extends T{_parse(e){const t=n.getValidEnumValues(this._def.values),r=this._getOrReturnCtx(e);if(r.parsedType!==s.string&&r.parsedType!==s.number){const e=n.objectValues(t);return u(r,{expected:n.joinValues(e),received:r.parsedType,code:a.invalid_type}),p}if(-1===t.indexOf(e.data)){const e=n.objectValues(t);return u(r,{received:r.data,code:a.invalid_enum_value,options:e}),p}return m(e.data)}get enum(){return this._def.values}}re.create=(e,t)=>new re({values:e,typeName:ge.ZodNativeEnum,...C(t)});class ae extends T{_parse(e){const{ctx:t}=this._processInputParams(e);if(t.parsedType!==s.promise&&!1===t.common.async)return u(t,{code:a.invalid_type,expected:s.promise,received:t.parsedType}),p;const n=t.parsedType===s.promise?t.data:Promise.resolve(t.data);return m(n.then((e=>this._def.type.parseAsync(e,{path:t.path,errorMap:t.common.contextualErrorMap}))))}}ae.create=(e,t)=>new ae({type:e,typeName:ge.ZodPromise,...C(t)});class ie extends T{innerType(){return this._def.schema}_parse(e){const{status:t,ctx:s}=this._processInputParams(e),r=this._def.effect||null;if("preprocess"===r.type){const e=r.transform(s.data);return s.common.async?Promise.resolve(e).then((e=>this._def.schema._parseAsync({data:e,path:s.path,parent:s}))):this._def.schema._parseSync({data:e,path:s.path,parent:s})}const a={addIssue:e=>{u(s,e),e.fatal?t.abort():t.dirty()},get path(){return s.path}};if(a.addIssue=a.addIssue.bind(a),"refinement"===r.type){const e=e=>{const t=r.refinement(e,a);if(s.common.async)return Promise.resolve(t);if(t instanceof Promise)throw new Error("Async refinement encountered during synchronous parse operation. Use .parseAsync instead.");return e};if(!1===s.common.async){const n=this._def.schema._parseSync({data:s.data,path:s.path,parent:s});return"aborted"===n.status?p:("dirty"===n.status&&t.dirty(),e(n.value),{status:t.value,value:n.value})}return this._def.schema._parseAsync({data:s.data,path:s.path,parent:s}).then((n=>"aborted"===n.status?p:("dirty"===n.status&&t.dirty(),e(n.value).then((()=>({status:t.value,value:n.value}))))))}if("transform"===r.type){if(!1===s.common.async){const e=this._def.schema._parseSync({data:s.data,path:s.path,parent:s});if(!v(e))return e;const n=r.transform(e.value,a);if(n instanceof Promise)throw new Error("Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead.");return{status:t.value,value:n}}return this._def.schema._parseAsync({data:s.data,path:s.path,parent:s}).then((e=>v(e)?Promise.resolve(r.transform(e.value,a)).then((e=>({status:t.value,value:e}))):e))}n.assertNever(r)}}ie.create=(e,t,n)=>new ie({schema:e,typeName:ge.ZodEffects,effect:t,...C(n)}),ie.createWithPreprocess=(e,t,n)=>new ie({schema:t,effect:{type:"preprocess",transform:e},typeName:ge.ZodEffects,...C(n)});class oe extends T{_parse(e){return this._getType(e)===s.undefined?m(void 0):this._def.innerType._parse(e)}unwrap(){return this._def.innerType}}oe.create=(e,t)=>new oe({innerType:e,typeName:ge.ZodOptional,...C(t)});class ce extends T{_parse(e){return this._getType(e)===s.null?m(null):this._def.innerType._parse(e)}unwrap(){return this._def.innerType}}ce.create=(e,t)=>new ce({innerType:e,typeName:ge.ZodNullable,...C(t)});class de extends T{_parse(e){const{ctx:t}=this._processInputParams(e);let n=t.data;return t.parsedType===s.undefined&&(n=this._def.defaultValue()),this._def.innerType._parse({data:n,path:t.path,parent:t})}removeDefault(){return this._def.innerType}}de.create=(e,t)=>new oe({innerType:e,typeName:ge.ZodOptional,...C(t)});class le extends T{_parse(e){if(this._getType(e)!==s.nan){const t=this._getOrReturnCtx(e);return u(t,{code:a.invalid_type,expected:s.nan,received:t.parsedType}),p}return{status:"valid",value:e.data}}}le.create=e=>new le({typeName:ge.ZodNaN,...C(e)});const ue=Symbol("zod_brand");class he extends T{_parse(e){const{ctx:t}=this._processInputParams(e),n=t.data;return this._def.type._parse({data:n,path:t.path,parent:t})}unwrap(){return this._def.type}}const pe=(e,t={},n)=>e?A.create().superRefine(((s,r)=>{if(!e(s)){const e="function"==typeof t?t(s):t,a="string"==typeof e?{message:e}:e;r.addIssue({code:"custom",...a,fatal:n})}})):A.create(),me={object:$.lazycreate};var ge;!function(e){e.ZodString="ZodString",e.ZodNumber="ZodNumber",e.ZodNaN="ZodNaN",e.ZodBigInt="ZodBigInt",e.ZodBoolean="ZodBoolean",e.ZodDate="ZodDate",e.ZodUndefined="ZodUndefined",e.ZodNull="ZodNull",e.ZodAny="ZodAny",e.ZodUnknown="ZodUnknown",e.ZodNever="ZodNever",e.ZodVoid="ZodVoid",e.ZodArray="ZodArray",e.ZodObject="ZodObject",e.ZodUnion="ZodUnion",e.ZodDiscriminatedUnion="ZodDiscriminatedUnion",e.ZodIntersection="ZodIntersection",e.ZodTuple="ZodTuple",e.ZodRecord="ZodRecord",e.ZodMap="ZodMap",e.ZodSet="ZodSet",e.ZodFunction="ZodFunction",e.ZodLazy="ZodLazy",e.ZodLiteral="ZodLiteral",e.ZodEnum="ZodEnum",e.ZodEffects="ZodEffects",e.ZodNativeEnum="ZodNativeEnum",e.ZodOptional="ZodOptional",e.ZodNullable="ZodNullable",e.ZodDefault="ZodDefault",e.ZodPromise="ZodPromise",e.ZodBranded="ZodBranded"}(ge||(ge={}));const fe=E.create,ve=D.create,ye=le.create,be=P.create,_e=N.create,we=j.create,xe=M.create,Ce=R.create,Te=A.create,Se=L.create,ke=Z.create,Oe=F.create,Ee=B.create,Ie=$.create,De=$.strictCreate,Pe=W.create,Ne=K.create,je=q.create,Me=J.create,Re=G.create,Ae=X.create,Le=Q.create,Ze=Y.create,Fe=ee.create,Be=te.create,ze=se.create,Ve=re.create,Ue=ae.create,$e=ie.create,We=oe.create,Ke=ce.create,He=ie.createWithPreprocess;var qe=Object.freeze({__proto__:null,getParsedType:r,ZodParsedType:s,makeIssue:l,EMPTY_PATH:[],addIssueToContext:u,ParseStatus:h,INVALID:p,DIRTY:e=>({status:"dirty",value:e}),OK:m,isAborted:g,isDirty:f,isValid:v,isAsync:y,jsonStringifyReplacer:b,ZodType:T,ZodString:E,ZodNumber:D,ZodBigInt:P,ZodBoolean:N,ZodDate:j,ZodUndefined:M,ZodNull:R,ZodAny:A,ZodUnknown:L,ZodNever:Z,ZodVoid:F,ZodArray:B,get objectUtil(){return z},ZodObject:$,ZodUnion:W,ZodDiscriminatedUnion:K,ZodIntersection:q,ZodTuple:J,ZodRecord:G,ZodMap:X,ZodSet:Q,ZodFunction:Y,ZodLazy:ee,ZodLiteral:te,ZodEnum:se,ZodNativeEnum:re,ZodPromise:ae,ZodEffects:ie,ZodTransformer:ie,ZodOptional:oe,ZodNullable:ce,ZodDefault:de,ZodNaN:le,BRAND:ue,ZodBranded:he,custom:pe,Schema:T,ZodSchema:T,late:me,get ZodFirstPartyTypeKind(){return ge},any:Te,array:Ee,bigint:be,boolean:_e,date:we,discriminatedUnion:Ne,effect:$e,enum:ze,function:Ze,instanceof:(e,t={message:`Input not instance of ${e.name}`})=>pe((t=>t instanceof e),t,!0),intersection:je,lazy:Fe,literal:Be,map:Ae,nan:ye,nativeEnum:Ve,never:ke,null:Ce,nullable:Ke,number:ve,object:Ie,oboolean:()=>_e().optional(),onumber:()=>ve().optional(),optional:We,ostring:()=>fe().optional(),preprocess:He,promise:Ue,record:Re,set:Le,strictObject:De,string:fe,transformer:$e,tuple:Me,undefined:xe,union:Pe,unknown:Se,void:Oe,ZodIssueCode:a,quotelessJson:e=>JSON.stringify(e,null,2).replace(/"([^"]+)":/g,"$1:"),ZodError:i,defaultErrorMap:o,setErrorMap:function(e){c=e},getErrorMap:d});class Je{constructor(e,t,n){this.error=e,this.message=t,this.stacktrace=n}error;message;stacktrace;toErrorResponse(e){return{id:e,error:this.error,message:this.message,stacktrace:this.stacktrace}}}class Ge extends Je{constructor(e,t){super("unknown error",e,t)}}class Xe extends Je{constructor(e,t){super("unknown command",e,t)}}class Qe extends Je{constructor(e,t){super("invalid argument",e,t)}}class Ye extends Je{constructor(e){super("no such frame",e)}}const et=e("command parser");function tt(e,t){const n=t.safeParse(e);if(n.success)return n.data;et(`Command ${JSON.stringify(e)} parse failed: ${JSON.stringify(n)}.`);const s=n.error.errors.map((e=>`${e.message} in ${e.path.map((e=>JSON.stringify(e))).join("/")}.`)).join(" ");throw new Qe(s)}var nt,st,rt,at,it,ot;!function(e){e.RemoteReferenceSchema=qe.object({handle:qe.string().min(1)});const t=qe.object({type:qe.literal("undefined")}),n=qe.object({type:qe.literal("null")}),s=qe.object({type:qe.literal("string"),value:qe.string()}),r=qe.enum(["NaN","-0","Infinity","+Infinity","-Infinity"]),a=qe.object({type:qe.literal("number"),value:qe.union([r,qe.number()])}),i=qe.object({type:qe.literal("boolean"),value:qe.boolean()}),o=qe.object({type:qe.literal("bigint"),value:qe.string()}),c=qe.union([t,n,s,a,i,o]);e.LocalValueSchema=qe.lazy((()=>qe.union([c,l,u,p,m,g,f])));const d=qe.array(e.LocalValueSchema),l=qe.lazy((()=>qe.object({type:qe.literal("array"),value:d}))),u=qe.object({type:qe.literal("date"),value:qe.string().min(1)}),h=qe.lazy((()=>qe.tuple([qe.union([qe.string(),e.LocalValueSchema]),e.LocalValueSchema]))),p=qe.object({type:qe.literal("map"),value:qe.array(h)}),m=qe.object({type:qe.literal("object"),value:qe.array(h)}),g=qe.lazy((()=>qe.object({type:qe.literal("regexp"),value:qe.object({pattern:qe.string(),flags:qe.string().optional()})}))),f=qe.lazy((()=>qe.object({type:qe.literal("set"),value:d})));e.BrowsingContextSchema=qe.string()}(nt||(nt={})),function(e){const t=qe.object({context:nt.BrowsingContextSchema,sandbox:qe.string().optional()}),n=qe.object({realm:qe.string().min(1)}),s=qe.union([t,n]),r=qe.enum(["root","none"]),a=qe.object({expression:qe.string(),awaitPromise:qe.boolean(),target:s,resultOwnership:r.optional()});e.parseEvaluateParams=function(e){return tt(e,a)};const i=qe.union([nt.RemoteReferenceSchema,nt.LocalValueSchema]),o=qe.object({functionDeclaration:qe.string(),target:s,arguments:qe.array(i).optional(),this:i.optional(),awaitPromise:qe.boolean(),resultOwnership:r.optional()});e.parseCallFunctionParams=function(e){return tt(e,o)}}(st||(st={})),function(e){const n=qe.object({maxDepth:qe.number().int().nonnegative().max(9007199254740991).optional(),root:nt.BrowsingContextSchema.optional()});e.parseGetTreeParams=function(e){return tt(e,n)};const s=qe.enum(["none","interactive","complete"]),r=qe.object({context:nt.BrowsingContextSchema,url:qe.string().url(),wait:s.optional()});e.parseNavigateParams=function(e){return tt(e,r)};const a=qe.object({type:qe.enum(["tab","window"])});e.parseCreateParams=function(e){return tt(e,a)};const i=qe.object({context:nt.BrowsingContextSchema});e.parseCloseParams=function(e){return tt(e,i)};class o extends t{static method="browsingContext.load";constructor(e){super(o.method,e)}}e.LoadEvent=o;class c extends t{static method="browsingContext.domContentLoaded";constructor(e){super(c.method,e)}}e.DomContentLoadedEvent=c;class d extends t{static method="browsingContext.contextCreated";constructor(e){super(d.method,e)}}e.ContextCreatedEvent=d;class l extends t{static method="browsingContext.contextDestroyed";constructor(e){super(l.method,e)}}e.ContextDestroyedEvent=l,function(e){const t=qe.object({context:nt.BrowsingContextSchema,selector:qe.string()});e.parseFindElementParams=function(e){return tt(e,t)}}(e.PROTO||(e.PROTO={})),e.EventNames=[o.method,c.method,d.method,l.method]}(rt||(rt={})),function(e){class n extends t{static method="log.entryAdded";constructor(e){super(n.method,e)}}e.LogEntryAddedEvent=n,e.EventNames=[n.method]}(at||(at={})),function(e){let n;!function(e){const n=qe.object({cdpMethod:qe.string(),cdpParams:qe.object({}).passthrough(),cdpSession:qe.string().optional()});e.parseSendCommandParams=function(e){return tt(e,n)};const s=qe.object({context:nt.BrowsingContextSchema});e.parseGetSessionParams=function(e){return tt(e,s)};class r extends t{static method="PROTO.cdp.eventReceived";constructor(e){super(r.method,e)}}e.EventReceivedEvent=r}(n=e.PROTO||(e.PROTO={})),e.EventNames=[n.EventReceivedEvent.method]}(it||(it={})),function(e){const t=qe.enum([...rt.EventNames,...at.EventNames,...it.EventNames]),n=qe.object({events:qe.array(t),contexts:qe.array(nt.BrowsingContextSchema).optional()});e.parseSubscribeParams=function(e){return tt(e,n)}}(ot||(ot={}));class ct{#e=()=>{};#t=()=>{};#n;#s=!1;get isFinished(){return this.#s}constructor(){this.#n=new Promise(((e,t)=>{this.#e=e,this.#t=t}))}then(e,t){return this.#n.then(e,t)}catch(e){return this.#n.catch(e)}resolve(e){this.#s=!0,this.#e(e)}reject(e){this.#s=!0,this.#t(e)}finally(e){return this.#n.finally(e)}[Symbol.toStringTag]="Promise"}const dt=["%s","%d","%i","%f","%o","%O","%c"];function lt(e){return dt.some((t=>e.includes(t)))}function ut(e){if(!["number","string","object"].includes(e.type))throw Error("Invalid value type: "+e.toString());if("number"===e.type&&(e.value,1))return e.value.toString();if("string"===e.type&&(e.value,1))return'"'+e.value.toString()+'"';if("object"===e.type&&(e.value,1))return'{"'+e.value[0][0]+'": '+ut(e.value[0][1])+"}";throw Error("Invalid value type: "+e.toString())}function ht(e,t){return 0==e.length?"":"string"===e[0].type&&lt(e[0].value.toString())&&t?function(e){let t="";const n=e[0].value.toString(),s=e.slice(1,void 0),r=n.split(new RegExp(dt.map((e=>"("+e+")")).join("|"),"g"));for(const n of r)if(void 0!==n&&""!=n)if(lt(n)){const r=s.shift();if(void 0===r)throw new Error('Less value is provided: "'+ht(e,!1)+'"');t+="%s"===n?r.value.toString():"%d"===n||"%i"===n?parseInt(r.value.toString(),10):"%f"===n?parseFloat(r.value.toString()):ut(r)}else t+=n;if(s.length>0)throw new Error('More value is provided: "'+ht(e,!1)+'"');return t}(e):e.map((e=>e.hasOwnProperty("value")?e.value.toString():e.type)).join(" ")}class pt{#r;#a;#i;#o;constructor(e,t,n,s){this.#o=s,this.#i=n,this.#a=t,this.#r=e}static create(e,t,n,s){const r=new pt(e,t,n,s);return r.#c(),r}#c(){this.#d()}#d(){this.#l()}#l(){this.#a.Runtime.on("consoleAPICalled",(async e=>{const t=await Promise.all(e.args.map((async t=>this.#o?.serializeCdpObject(t,"none",e.executionContextId))));await this.#i.sendMessage(new at.LogEntryAddedEvent({level:pt.#u(e.type),text:ht(t,!0),timestamp:e.timestamp,stackTrace:pt.#h(e.stackTrace),type:"console",method:e.type,realm:this.#r,args:t}))})),this.#a.Runtime.on("exceptionThrown",(async e=>{let t=e.exceptionDetails.text;if(e.exceptionDetails.exception)if(void 0===e.exceptionDetails.executionContextId)t=JSON.stringify(e.exceptionDetails.exception);else{const n=await this.#o.stringifyObject(e.exceptionDetails.exception,e.exceptionDetails.executionContextId);n&&(t=n)}await this.#i.sendMessage(new at.LogEntryAddedEvent({level:"error",text:t,timestamp:e.timestamp,stackTrace:pt.#h(e.exceptionDetails.stackTrace),type:"javascript",realm:this.#r}))}))}static#u(e){return e in["error","assert"]?"error":e in["debug","trace"]?"debug":e in["warning","warn"]?"warning":"info"}static#h(e){const t=e?.callFrames.map((e=>({columnNumber:e.columnNumber,functionName:e.functionName,lineNumber:e.lineNumber,url:e.url})));return t?{callFrames:t}:void 0}}class mt{#a;#p=0;#m=1;constructor(e){this.#a=e}static create(e){return new mt(e)}async serializeCdpObject(e,t,n){const s=await this.#a.Runtime.callFunctionOn({functionDeclaration:String((e=>e)),awaitPromise:!1,arguments:[e],generateWebDriverValue:!0,objectId:await this.#g(n)});return await this.#f(s,t)}async stringifyObject(e,t){return(await this.#a.Runtime.callFunctionOn({functionDeclaration:String((function(e){return String(e)})),awaitPromise:!1,arguments:[e],returnByValue:!0,objectId:await this.#g(t)})).result.value}async#g(e){return(await this.#a.Runtime.evaluate({expression:"(()=>{return {}})()",contextId:e})).result.objectId}async callFunction(e,t,n,s,r,a){const i=`(...args)=>{ return _callFunction((\n${t}\n), args);\n      function _callFunction(f, args) {\n        const deserializedThis = args.shift();\n        const deserializedArgs = args;\n        return f.apply(deserializedThis, deserializedArgs);\n      }}`,o=[await this.#v(n,e)];o.push(...await Promise.all(s.map((async t=>await this.#v(t,e)))));const c=await this.#a.Runtime.callFunctionOn({functionDeclaration:i,awaitPromise:r,arguments:o,generateWebDriverValue:!0,objectId:await this.#g(e)});return c.exceptionDetails?{exceptionDetails:await this.#y(c.exceptionDetails,this.#m,a,e),realm:"TODO: ADD"}:{result:await this.#f(c,a),realm:"TODO: ADD"}}async#y(e,t,n,s){const r=e.stackTrace?.callFrames.map((e=>({url:e.url,functionName:e.functionName,lineNumber:e.lineNumber-t,columnNumber:e.columnNumber}))),a=await this.serializeCdpObject(e.exception,n,s),i=await this.stringifyObject(e.exception,s);return{exception:a,columnNumber:e.columnNumber,lineNumber:e.lineNumber-t,stackTrace:{callFrames:r||[]},text:i||e.text}}async#f(e,t){const n=e.result.webDriverValue;if(!e.result.objectId)return n;const s=e.result.objectId,r=n;return"root"===t?r.handle=s:await this.#a.Runtime.releaseObject({objectId:s}),r}async scriptEvaluate(e,t,n,s){let r=await this.#a.Runtime.evaluate({contextId:e,expression:t,awaitPromise:n,generateWebDriverValue:!0});return r.exceptionDetails?{result:{exceptionDetails:await this.#y(r.exceptionDetails,this.#p,s,e),realm:"TODO: ADD"}}:{result:{result:await this.#f(r,s),realm:"TODO: ADD"}}}async#v(e,t){if("handle"in e)return{objectId:e.handle};switch(e.type){case"undefined":return{unserializableValue:"undefined"};case"null":return{unserializableValue:"null"};case"string":return{value:e.value};case"number":return"NaN"===e.value?{unserializableValue:"NaN"}:"-0"===e.value?{unserializableValue:"-0"}:"+Infinity"===e.value?{unserializableValue:"+Infinity"}:"Infinity"===e.value?{unserializableValue:"Infinity"}:"-Infinity"===e.value?{unserializableValue:"-Infinity"}:{value:e.value};case"boolean":return{value:!!e.value};case"bigint":return{unserializableValue:`BigInt(${JSON.stringify(e.value)})`};case"date":return{unserializableValue:`new Date(Date.parse(${JSON.stringify(e.value)}))`};case"regexp":return{unserializableValue:`new RegExp(${JSON.stringify(e.value.pattern)}, ${JSON.stringify(e.value.flags)})`};case"map":{const n=await this.#b(e.value,t);return{objectId:(await this.#a.Runtime.callFunctionOn({functionDeclaration:String((function(...e){const t=new Map;for(let n=0;n<e.length;n+=2)t.set(e[n],e[n+1]);return t})),awaitPromise:!1,arguments:n,returnByValue:!1,objectId:await this.#g(t)})).result.objectId}}case"object":{const n=await this.#b(e.value,t);return{objectId:(await this.#a.Runtime.callFunctionOn({functionDeclaration:String((function(...e){const t={};for(let n=0;n<e.length;n+=2){t[e[n]]=e[n+1]}return t})),awaitPromise:!1,arguments:n,returnByValue:!1,objectId:await this.#g(t)})).result.objectId}}case"array":{const n=await this.#_(e.value,t);return{objectId:(await this.#a.Runtime.callFunctionOn({functionDeclaration:String((function(...e){return e})),awaitPromise:!1,arguments:n,returnByValue:!1,objectId:await this.#g(t)})).result.objectId}}case"set":{const n=await this.#_(e.value,t);return{objectId:(await this.#a.Runtime.callFunctionOn({functionDeclaration:String((function(...e){return new Set(e)})),awaitPromise:!1,arguments:n,returnByValue:!1,objectId:await this.#g(t)})).result.objectId}}default:throw new Error(`Value ${JSON.stringify(e)} is not deserializable.`)}}async#b(e,t){const n=[];for(let s of e){const e=s[0],r=s[1];let a,i;a="string"==typeof e?{value:e}:await this.#v(e,t),i=await this.#v(r,t),n.push(a),n.push(i)}return n}async#_(e,t){const n=[];for(let s of e)n.push(await this.#v(s,t));return n}}var gt=rt.LoadEvent;class ft{#w={documentInitialized:new ct,targetUnblocked:new ct,Page:{navigatedWithinDocument:new ct,lifecycleEvent:{DOMContentLoaded:new ct,load:new ct}}};#r;#x;#C="about:blank";#T=null;#S;#a;#i;#k;#O=[];#E;#I=null;constructor(e,t,n,s,r,a){this.#r=e,this.#x=t,this.#a=n,this.#k=a,this.#S=r,this.#i=s,this.#E=mt.create(n),this.#D()}static createFrameContext(e,t,n,s,r,a){const i=new ft(e,t,n,s,r,a);return i.#w.targetUnblocked.resolve(),i}static createTargetContext(e,t,n,s,r,a){const i=new ft(e,t,n,s,r,a);return i.#P(),i}static convertFrameToTargetContext(e,t,n){return e.#N(t,n),e.#P(),e}#N(e,t){this.#w.targetUnblocked.isFinished||this.#w.targetUnblocked.reject("OOPiF"),this.#w.targetUnblocked=new ct,this.#a=e,this.#S=t,this.#E=mt.create(e),this.#D()}async#P(){pt.create(this.#r,this.#a,this.#i,this.#E),await this.#a.Runtime.enable(),await this.#a.Page.enable(),await this.#a.Page.setLifecycleEventsEnabled({enabled:!0}),await this.#a.Target.setAutoAttach({autoAttach:!0,waitForDebuggerOnStart:!0,flatten:!0}),await this.#a.Runtime.runIfWaitingForDebugger(),this.#w.targetUnblocked.resolve()}get contextId(){return this.#r}get parentId(){return this.#x}get cdpSessionId(){return this.#S}get children(){return this.#O}get url(){return this.#C}addChild(e){this.#O.push(e)}serializeToBidiValue(e,t){return{context:this.#r,url:this.url,children:e>0?this.children.map((t=>t.serializeToBidiValue(e-1,!1))):null,...t?{parent:this.#x}:{}}}#D(){this.#a.Target.on("targetInfoChanged",(e=>{this.contextId===e.targetInfo.targetId&&(this.#C=e.targetInfo.url)})),this.#a.Page.on("frameNavigated",(e=>{this.contextId===e.frame.id&&(this.#C=e.frame.url+(e.frame.urlFragment??""))})),this.#a.Page.on("navigatedWithinDocument",(e=>{this.contextId===e.frameId&&(this.#C=e.url,this.#w.Page.navigatedWithinDocument.resolve(e))})),this.#a.Page.on("lifecycleEvent",(async e=>{if(this.contextId===e.frameId&&("init"===e.name&&(this.#j(e.loaderId),this.#w.documentInitialized.resolve()),e.loaderId===this.#T))switch(e.name){case"DOMContentLoaded":this.#w.Page.lifecycleEvent.DOMContentLoaded.resolve(e),await this.#k.sendEvent(new rt.DomContentLoadedEvent({context:this.contextId,navigation:this.#T}),this.contextId);break;case"load":this.#w.Page.lifecycleEvent.load.resolve(e),await this.#k.sendEvent(new gt({context:this.contextId,navigation:this.#T}),this.contextId)}})),this.#a.Runtime.on("executionContextCreated",(e=>{e.context.auxData.frameId===this.contextId&&e.context.auxData.isDefault&&(this.#I=e.context.id)}))}#j(e){this.#T!==e&&(this.#w.documentInitialized.isFinished||this.#w.documentInitialized.reject("Document changed"),this.#w.documentInitialized=new ct,this.#w.Page.navigatedWithinDocument.isFinished||this.#w.Page.navigatedWithinDocument.reject("Document changed"),this.#w.Page.navigatedWithinDocument=new ct,this.#w.Page.lifecycleEvent.DOMContentLoaded.isFinished||this.#w.Page.lifecycleEvent.DOMContentLoaded.reject("Document changed"),this.#w.Page.lifecycleEvent.DOMContentLoaded=new ct,this.#w.Page.lifecycleEvent.load.isFinished||this.#w.Page.lifecycleEvent.load.reject("Document changed"),this.#w.Page.lifecycleEvent.load=new ct,this.#T=e)}async navigate(e,t){await this.#w.targetUnblocked;const n=await this.#a.Page.navigate({url:e,frameId:this.contextId});if(n.errorText)throw new Ge(n.errorText);switch(void 0!==n.loaderId&&n.loaderId!==this.#T&&this.#j(n.loaderId),t){case"none":break;case"interactive":void 0===n.loaderId?await this.#w.Page.navigatedWithinDocument:await this.#w.Page.lifecycleEvent.DOMContentLoaded;break;case"complete":void 0===n.loaderId?await this.#w.Page.navigatedWithinDocument:await this.#w.Page.lifecycleEvent.load;break;default:throw new Error(`Not implemented wait '${t}'`)}return{result:{navigation:n.loaderId||null,url:e}}}async callFunction(e,t,n,s,r){if(await this.#w.targetUnblocked,null===this.#I)throw Error("No execution context");return{result:await this.#E.callFunction(this.#I,e,t,n,s,r)}}async scriptEvaluate(e,t,n){if(await this.#w.targetUnblocked,null===this.#I)throw Error("No execution context");return this.#E.scriptEvaluate(this.#I,e,t,n)}async findElement(e){await this.#w.targetUnblocked;const t=String((e=>document.querySelector(e))),n=[{type:"string",value:e}];return await this.callFunction(t,{type:"undefined"},n,!0,"root")}}const vt=e("context");class yt{static#M=new Map;static#R(){return Array.from(yt.#M.values()).filter((e=>null===e.parentId))}static#A(e){yt.#M.delete(e)}static#L(e){yt.#M.set(e.contextId,e),null!==e.parentId&&yt.#Z(e.parentId).addChild(e)}static#F(e){return yt.#M.has(e)}static#Z(e){if(!yt.#F(e))throw new Ye(`Context ${e} not found`);return yt.#M.get(e)}sessions=new Set;#B;#z;#i;#k;constructor(e,t,n,s){this.#B=e,this.#z=t,this.#i=n,this.#k=s,this.#V(this.#B.browserClient())}#V(e){this.#U(e)}#U(e){e.Target.on("attachedToTarget",(async t=>{await this.#$(t,e)})),e.Target.on("detachedFromTarget",(async e=>{await this.#W(e)}))}#K(e){if(this.sessions.has(e))return;this.sessions.add(e);const t=this.#B.getCdpClient(e);this.#U(t),t.on("event",(async(t,n)=>{await this.#k.sendEvent({method:"PROTO.cdp.eventReceived",params:{cdpMethod:t,cdpParams:n,session:e}},null)})),t.Page.on("frameAttached",(async n=>{const s=ft.createFrameContext(n.frameId,n.parentFrameId,t,this.#i,e,this.#k);yt.#L(s),await this.#k.sendEvent(new rt.ContextCreatedEvent(s.serializeToBidiValue(0,!0)),s.contextId)}))}async#$(e,t){vt("AttachedToTarget event received: "+JSON.stringify(e));const{sessionId:n,targetInfo:s}=e;let r=this.#B.getCdpClient(n);if(!this.#H(s))return await r.Runtime.runIfWaitingForDebugger(),void await t.Target.detachFromTarget(e);if(this.#K(n),yt.#F(s.targetId))ft.convertFrameToTargetContext(yt.#Z(s.targetId),r,n);else{const e=ft.createTargetContext(s.targetId,null,r,this.#i,n,this.#k);yt.#L(e),await this.#k.sendEvent(new rt.ContextCreatedEvent(e.serializeToBidiValue(0,!0)),e.contextId)}}async#W(e){vt("detachedFromTarget event received: "+JSON.stringify(e));const t=e.targetId;if(!yt.#F(t))return;const n=yt.#Z(t);yt.#A(t),await this.#k.sendEvent(new rt.ContextDestroyedEvent(n.serializeToBidiValue(0,!0)),t)}async process_browsingContext_getTree(e){return{result:{contexts:(void 0===e.root?yt.#R():[yt.#Z(e.root)]).map((t=>t.serializeToBidiValue(e.maxDepth??Number.MAX_VALUE,!0)))}}}async process_browsingContext_create(e){const t=this.#B.browserClient();return{result:{context:(await t.Target.createTarget({url:"about:blank",newWindow:"window"===e.type})).targetId,parent:null,url:"about:blank",children:[]}}}async process_browsingContext_navigate(e){const t=yt.#Z(e.context);return await t.navigate(e.url,void 0!==e.wait?e.wait:"none")}async process_script_evaluate(e){const t=yt.#Z(e.target.context);return await t.scriptEvaluate(e.expression,e.awaitPromise,e.resultOwnership??"none")}async process_script_callFunction(e){const t=yt.#Z(e.target.context);return await t.callFunction(e.functionDeclaration,e.this||{type:"undefined"},e.arguments||[],e.awaitPromise,e.resultOwnership??"none")}async process_PROTO_browsingContext_findElement(e){const t=yt.#Z(e.context);return await t.findElement(e.selector)}async process_browsingContext_close(e){const t=this.#B.browserClient();if(null!==yt.#Z(e.context).parentId)throw new Qe("Not a top-level browsing context cannot be closed.");const n=new Promise((async n=>{const s=r=>{r.targetId===e.context&&(t.Target.removeListener("detachedFromTarget",s),n())};t.Target.on("detachedFromTarget",s)}));return await this.#B.browserClient().Target.closeTarget({targetId:e.context}),await n,{result:{}}}#H(e){return e.targetId!==this.#z&&["page","iframe"].includes(e.type)}async process_PROTO_cdp_sendCommand(e){return{result:await this.#B.sendCommand(e.cdpMethod,e.cdpParams,e.cdpSession??null)}}async process_PROTO_cdp_getSession(e){const t=e.context,n=yt.#Z(t).cdpSessionId;return void 0===n?{result:{session:null}}:{result:{session:n}}}}class bt{#q;#i;#k;static run(e,t,n,s){new bt(e,t,n,s).#J()}constructor(e,t,n,s){this.#k=n,this.#i=t,this.#q=new yt(e,s,t,n)}#J(){this.#i.on("message",(e=>this.#G(e)))}async#X(){return{result:{ready:!0,message:"ready"}}}async#Q(e){return await this.#k.subscribe(e.events,e.contexts??null),{result:{}}}async#Y(e){return await this.#k.unsubscribe(e.events,e.contexts??null),{result:{}}}async#ee(e){switch(e.method){case"session.status":return await this.#X();case"session.subscribe":return await this.#Q(ot.parseSubscribeParams(e.params));case"session.unsubscribe":return await this.#Y(ot.parseSubscribeParams(e.params));case"browsingContext.create":return await this.#q.process_browsingContext_create(rt.parseCreateParams(e.params));case"browsingContext.close":return await this.#q.process_browsingContext_close(rt.parseCloseParams(e.params));case"browsingContext.getTree":return await this.#q.process_browsingContext_getTree(rt.parseGetTreeParams(e.params));case"browsingContext.navigate":return await this.#q.process_browsingContext_navigate(rt.parseNavigateParams(e.params));case"script.callFunction":return await this.#q.process_script_callFunction(st.parseCallFunctionParams(e.params));case"script.evaluate":return await this.#q.process_script_evaluate(st.parseEvaluateParams(e.params));case"PROTO.browsingContext.findElement":return await this.#q.process_PROTO_browsingContext_findElement(rt.PROTO.parseFindElementParams(e.params));case"PROTO.cdp.sendCommand":return await this.#q.process_PROTO_cdp_sendCommand(it.PROTO.parseSendCommandParams(e.params));case"PROTO.cdp.getSession":return await this.#q.process_PROTO_cdp_getSession(it.PROTO.parseGetSessionParams(e.params));default:throw new Xe(`Unknown command '${e.method}'.`)}}#G=async e=>{try{const t=await this.#ee(e),n={id:e.id,...t};await this.#i.sendMessage(n)}catch(t){if(t instanceof Je){const n=t;await this.#i.sendMessage(n.toErrorResponse(e.id))}else{const n=t;console.error(n),await this.#i.sendMessage(new Ge(n.message).toErrorResponse(e.id))}}}}function _t(){}function wt(){wt.init.call(this)}function xt(e){return void 0===e._maxListeners?wt.defaultMaxListeners:e._maxListeners}function Ct(e,t,n){if(t)e.call(n);else for(var s=e.length,r=Pt(e,s),a=0;a<s;++a)r[a].call(n)}function Tt(e,t,n,s){if(t)e.call(n,s);else for(var r=e.length,a=Pt(e,r),i=0;i<r;++i)a[i].call(n,s)}function St(e,t,n,s,r){if(t)e.call(n,s,r);else for(var a=e.length,i=Pt(e,a),o=0;o<a;++o)i[o].call(n,s,r)}function kt(e,t,n,s,r,a){if(t)e.call(n,s,r,a);else for(var i=e.length,o=Pt(e,i),c=0;c<i;++c)o[c].call(n,s,r,a)}function Ot(e,t,n,s){if(t)e.apply(n,s);else for(var r=e.length,a=Pt(e,r),i=0;i<r;++i)a[i].apply(n,s)}function Et(e,t,n,s){var r,a,i,o;if("function"!=typeof n)throw new TypeError('"listener" argument must be a function');if((a=e._events)?(a.newListener&&(e.emit("newListener",t,n.listener?n.listener:n),a=e._events),i=a[t]):(a=e._events=new _t,e._eventsCount=0),i){if("function"==typeof i?i=a[t]=s?[n,i]:[i,n]:s?i.unshift(n):i.push(n),!i.warned&&(r=xt(e))&&r>0&&i.length>r){i.warned=!0;var c=new Error("Possible EventEmitter memory leak detected. "+i.length+" "+t+" listeners added. Use emitter.setMaxListeners() to increase limit");c.name="MaxListenersExceededWarning",c.emitter=e,c.type=t,c.count=i.length,o=c,"function"==typeof console.warn?console.warn(o):console.log(o)}}else i=a[t]=n,++e._eventsCount;return e}function It(e,t,n){var s=!1;function r(){e.removeListener(t,r),s||(s=!0,n.apply(e,arguments))}return r.listener=n,r}function Dt(e){var t=this._events;if(t){var n=t[e];if("function"==typeof n)return 1;if(n)return n.length}return 0}function Pt(e,t){for(var n=new Array(t);t--;)n[t]=e[t];return n}_t.prototype=Object.create(null),wt.EventEmitter=wt,wt.usingDomains=!1,wt.prototype.domain=void 0,wt.prototype._events=void 0,wt.prototype._maxListeners=void 0,wt.defaultMaxListeners=10,wt.init=function(){this.domain=null,wt.usingDomains&&undefined.active,this._events&&this._events!==Object.getPrototypeOf(this)._events||(this._events=new _t,this._eventsCount=0),this._maxListeners=this._maxListeners||void 0},wt.prototype.setMaxListeners=function(e){if("number"!=typeof e||e<0||isNaN(e))throw new TypeError('"n" argument must be a positive number');return this._maxListeners=e,this},wt.prototype.getMaxListeners=function(){return xt(this)},wt.prototype.emit=function(e){var t,n,s,r,a,i,o,c="error"===e;if(i=this._events)c=c&&null==i.error;else if(!c)return!1;if(o=this.domain,c){if(t=arguments[1],!o){if(t instanceof Error)throw t;var d=new Error('Uncaught, unspecified "error" event. ('+t+")");throw d.context=t,d}return t||(t=new Error('Uncaught, unspecified "error" event')),t.domainEmitter=this,t.domain=o,t.domainThrown=!1,o.emit("error",t),!1}if(!(n=i[e]))return!1;var l="function"==typeof n;switch(s=arguments.length){case 1:Ct(n,l,this);break;case 2:Tt(n,l,this,arguments[1]);break;case 3:St(n,l,this,arguments[1],arguments[2]);break;case 4:kt(n,l,this,arguments[1],arguments[2],arguments[3]);break;default:for(r=new Array(s-1),a=1;a<s;a++)r[a-1]=arguments[a];Ot(n,l,this,r)}return!0},wt.prototype.addListener=function(e,t){return Et(this,e,t,!1)},wt.prototype.on=wt.prototype.addListener,wt.prototype.prependListener=function(e,t){return Et(this,e,t,!0)},wt.prototype.once=function(e,t){if("function"!=typeof t)throw new TypeError('"listener" argument must be a function');return this.on(e,It(this,e,t)),this},wt.prototype.prependOnceListener=function(e,t){if("function"!=typeof t)throw new TypeError('"listener" argument must be a function');return this.prependListener(e,It(this,e,t)),this},wt.prototype.removeListener=function(e,t){var n,s,r,a,i;if("function"!=typeof t)throw new TypeError('"listener" argument must be a function');if(!(s=this._events))return this;if(!(n=s[e]))return this;if(n===t||n.listener&&n.listener===t)0==--this._eventsCount?this._events=new _t:(delete s[e],s.removeListener&&this.emit("removeListener",e,n.listener||t));else if("function"!=typeof n){for(r=-1,a=n.length;a-- >0;)if(n[a]===t||n[a].listener&&n[a].listener===t){i=n[a].listener,r=a;break}if(r<0)return this;if(1===n.length){if(n[0]=void 0,0==--this._eventsCount)return this._events=new _t,this;delete s[e]}else!function(e,t){for(var n=t,s=n+1,r=e.length;s<r;n+=1,s+=1)e[n]=e[s];e.pop()}(n,r);s.removeListener&&this.emit("removeListener",e,i||t)}return this},wt.prototype.removeAllListeners=function(e){var t,n;if(!(n=this._events))return this;if(!n.removeListener)return 0===arguments.length?(this._events=new _t,this._eventsCount=0):n[e]&&(0==--this._eventsCount?this._events=new _t:delete n[e]),this;if(0===arguments.length){for(var s,r=Object.keys(n),a=0;a<r.length;++a)"removeListener"!==(s=r[a])&&this.removeAllListeners(s);return this.removeAllListeners("removeListener"),this._events=new _t,this._eventsCount=0,this}if("function"==typeof(t=n[e]))this.removeListener(e,t);else if(t)do{this.removeListener(e,t[t.length-1])}while(t[0]);return this},wt.prototype.listeners=function(e){var t,n=this._events;return n&&(t=n[e])?"function"==typeof t?[t.listener||t]:function(e){for(var t=new Array(e.length),n=0;n<t.length;++n)t[n]=e[n].listener||e[n];return t}(t):[]},wt.listenerCount=function(e,t){return"function"==typeof e.listenerCount?e.listenerCount(t):Dt.call(e,t)},wt.prototype.listenerCount=Dt,wt.prototype.eventNames=function(){return this._eventsCount>0?Reflect.ownKeys(this._events):[]};const Nt=[{domain:"Accessibility",commands:["disable","enable","getPartialAXTree","getFullAXTree","getRootAXNode","getAXNodeAndAncestors","getChildAXNodes","queryAXTree"]},{domain:"Animation",commands:["disable","enable","getCurrentTime","getPlaybackRate","releaseAnimations","resolveAnimation","seekAnimations","setPaused","setPlaybackRate","setTiming"]},{domain:"Audits",commands:["getEncodedResponse","disable","enable","checkContrast"]},{domain:"BackgroundService",commands:["startObserving","stopObserving","setRecording","clearEvents"]},{domain:"Browser",commands:["setPermission","grantPermissions","resetPermissions","setDownloadBehavior","cancelDownload","close","crash","crashGpuProcess","getVersion","getBrowserCommandLine","getHistograms","getHistogram","getWindowBounds","getWindowForTarget","setWindowBounds","setDockTile","executeBrowserCommand"]},{domain:"CSS",commands:["addRule","collectClassNames","createStyleSheet","disable","enable","forcePseudoState","getBackgroundColors","getComputedStyleForNode","getInlineStylesForNode","getMatchedStylesForNode","getMediaQueries","getPlatformFontsForNode","getStyleSheetText","getLayersForNode","trackComputedStyleUpdates","takeComputedStyleUpdates","setEffectivePropertyValueForNode","setKeyframeKey","setMediaText","setContainerQueryText","setSupportsText","setRuleSelector","setStyleSheetText","setStyleTexts","startRuleUsageTracking","stopRuleUsageTracking","takeCoverageDelta","setLocalFontsEnabled"]},{domain:"CacheStorage",commands:["deleteCache","deleteEntry","requestCacheNames","requestCachedResponse","requestEntries"]},{domain:"Cast",commands:["enable","disable","setSinkToUse","startDesktopMirroring","startTabMirroring","stopCasting"]},{domain:"DOM",commands:["collectClassNamesFromSubtree","copyTo","describeNode","scrollIntoViewIfNeeded","disable","discardSearchResults","enable","focus","getAttributes","getBoxModel","getContentQuads","getDocument","getFlattenedDocument","getNodesForSubtreeByStyle","getNodeForLocation","getOuterHTML","getRelayoutBoundary","getSearchResults","hideHighlight","highlightNode","highlightRect","markUndoableState","moveTo","performSearch","pushNodeByPathToFrontend","pushNodesByBackendIdsToFrontend","querySelector","querySelectorAll","redo","removeAttribute","removeNode","requestChildNodes","requestNode","resolveNode","setAttributeValue","setAttributesAsText","setFileInputFiles","setNodeStackTracesEnabled","getNodeStackTraces","getFileInfo","setInspectedNode","setNodeName","setNodeValue","setOuterHTML","undo","getFrameOwner","getContainerForNode","getQueryingDescendantsForContainer"]},{domain:"DOMDebugger",commands:["getEventListeners","removeDOMBreakpoint","removeEventListenerBreakpoint","removeInstrumentationBreakpoint","removeXHRBreakpoint","setBreakOnCSPViolation","setDOMBreakpoint","setEventListenerBreakpoint","setInstrumentationBreakpoint","setXHRBreakpoint"]},{domain:"EventBreakpoints",commands:["setInstrumentationBreakpoint","removeInstrumentationBreakpoint"]},{domain:"DOMSnapshot",commands:["disable","enable","getSnapshot","captureSnapshot"]},{domain:"DOMStorage",commands:["clear","disable","enable","getDOMStorageItems","removeDOMStorageItem","setDOMStorageItem"]},{domain:"Database",commands:["disable","enable","executeSQL","getDatabaseTableNames"]},{domain:"DeviceOrientation",commands:["clearDeviceOrientationOverride","setDeviceOrientationOverride"]},{domain:"Emulation",commands:["canEmulate","clearDeviceMetricsOverride","clearGeolocationOverride","resetPageScaleFactor","setFocusEmulationEnabled","setAutoDarkModeOverride","setCPUThrottlingRate","setDefaultBackgroundColorOverride","setDeviceMetricsOverride","setScrollbarsHidden","setDocumentCookieDisabled","setEmitTouchEventsForMouse","setEmulatedMedia","setEmulatedVisionDeficiency","setGeolocationOverride","setIdleOverride","clearIdleOverride","setNavigatorOverrides","setPageScaleFactor","setScriptExecutionDisabled","setTouchEmulationEnabled","setVirtualTimePolicy","setLocaleOverride","setTimezoneOverride","setVisibleSize","setDisabledImageTypes","setHardwareConcurrencyOverride","setUserAgentOverride","setAutomationOverride"]},{domain:"HeadlessExperimental",commands:["beginFrame","disable","enable"]},{domain:"IO",commands:["close","read","resolveBlob"]},{domain:"IndexedDB",commands:["clearObjectStore","deleteDatabase","deleteObjectStoreEntries","disable","enable","requestData","getMetadata","requestDatabase","requestDatabaseNames"]},{domain:"Input",commands:["dispatchDragEvent","dispatchKeyEvent","insertText","imeSetComposition","dispatchMouseEvent","dispatchTouchEvent","emulateTouchFromMouseEvent","setIgnoreInputEvents","setInterceptDrags","synthesizePinchGesture","synthesizeScrollGesture","synthesizeTapGesture"]},{domain:"Inspector",commands:["disable","enable"]},{domain:"LayerTree",commands:["compositingReasons","disable","enable","loadSnapshot","makeSnapshot","profileSnapshot","releaseSnapshot","replaySnapshot","snapshotCommandLog"]},{domain:"Log",commands:["clear","disable","enable","startViolationsReport","stopViolationsReport"]},{domain:"Memory",commands:["getDOMCounters","prepareForLeakDetection","forciblyPurgeJavaScriptMemory","setPressureNotificationsSuppressed","simulatePressureNotification","startSampling","stopSampling","getAllTimeSamplingProfile","getBrowserSamplingProfile","getSamplingProfile"]},{domain:"Network",commands:["setAcceptedEncodings","clearAcceptedEncodingsOverride","canClearBrowserCache","canClearBrowserCookies","canEmulateNetworkConditions","clearBrowserCache","clearBrowserCookies","continueInterceptedRequest","deleteCookies","disable","emulateNetworkConditions","enable","getAllCookies","getCertificate","getCookies","getResponseBody","getRequestPostData","getResponseBodyForInterception","takeResponseBodyForInterceptionAsStream","replayXHR","searchInResponseBody","setBlockedURLs","setBypassServiceWorker","setCacheDisabled","setCookie","setCookies","setExtraHTTPHeaders","setAttachDebugStack","setRequestInterception","setUserAgentOverride","getSecurityIsolationStatus","enableReportingApi","loadNetworkResource"]},{domain:"Overlay",commands:["disable","enable","getHighlightObjectForTest","getGridHighlightObjectsForTest","getSourceOrderHighlightObjectForTest","hideHighlight","highlightFrame","highlightNode","highlightQuad","highlightRect","highlightSourceOrder","setInspectMode","setShowAdHighlights","setPausedInDebuggerMessage","setShowDebugBorders","setShowFPSCounter","setShowGridOverlays","setShowFlexOverlays","setShowScrollSnapOverlays","setShowContainerQueryOverlays","setShowPaintRects","setShowLayoutShiftRegions","setShowScrollBottleneckRects","setShowHitTestBorders","setShowWebVitals","setShowViewportSizeOnResize","setShowHinge","setShowIsolatedElements"]},{domain:"Page",commands:["addScriptToEvaluateOnLoad","addScriptToEvaluateOnNewDocument","bringToFront","captureScreenshot","captureSnapshot","clearDeviceMetricsOverride","clearDeviceOrientationOverride","clearGeolocationOverride","createIsolatedWorld","deleteCookie","disable","enable","getAppManifest","getInstallabilityErrors","getManifestIcons","getAppId","getCookies","getFrameTree","getLayoutMetrics","getNavigationHistory","resetNavigationHistory","getResourceContent","getResourceTree","handleJavaScriptDialog","navigate","navigateToHistoryEntry","printToPDF","reload","removeScriptToEvaluateOnLoad","removeScriptToEvaluateOnNewDocument","screencastFrameAck","searchInResource","setAdBlockingEnabled","setBypassCSP","getPermissionsPolicyState","getOriginTrials","setDeviceMetricsOverride","setDeviceOrientationOverride","setFontFamilies","setFontSizes","setDocumentContent","setDownloadBehavior","setGeolocationOverride","setLifecycleEventsEnabled","setTouchEmulationEnabled","startScreencast","stopLoading","crash","close","setWebLifecycleState","stopScreencast","produceCompilationCache","addCompilationCache","clearCompilationCache","setSPCTransactionMode","generateTestReport","waitForDebugger","setInterceptFileChooserDialog"]},{domain:"Performance",commands:["disable","enable","setTimeDomain","getMetrics"]},{domain:"PerformanceTimeline",commands:["enable"]},{domain:"Security",commands:["disable","enable","setIgnoreCertificateErrors","handleCertificateError","setOverrideCertificateErrors"]},{domain:"ServiceWorker",commands:["deliverPushMessage","disable","dispatchSyncEvent","dispatchPeriodicSyncEvent","enable","inspectWorker","setForceUpdateOnPageLoad","skipWaiting","startWorker","stopAllWorkers","stopWorker","unregister","updateRegistration"]},{domain:"Storage",commands:["getStorageKeyForFrame","clearDataForOrigin","getCookies","setCookies","clearCookies","getUsageAndQuota","overrideQuotaForOrigin","trackCacheStorageForOrigin","trackIndexedDBForOrigin","untrackCacheStorageForOrigin","untrackIndexedDBForOrigin","getTrustTokens","clearTrustTokens","getInterestGroupDetails","setInterestGroupTracking"]},{domain:"SystemInfo",commands:["getInfo","getProcessInfo"]},{domain:"Target",commands:["activateTarget","attachToTarget","attachToBrowserTarget","closeTarget","exposeDevToolsProtocol","createBrowserContext","getBrowserContexts","createTarget","detachFromTarget","disposeBrowserContext","getTargetInfo","getTargets","sendMessageToTarget","setAutoAttach","autoAttachRelated","setDiscoverTargets","setRemoteLocations"]},{domain:"Tethering",commands:["bind","unbind"]},{domain:"Tracing",commands:["end","getCategories","recordClockSyncMarker","requestMemoryDump","start"]},{domain:"Fetch",commands:["disable","enable","failRequest","fulfillRequest","continueRequest","continueWithAuth","continueResponse","getResponseBody","takeResponseBodyAsStream"]},{domain:"WebAudio",commands:["enable","disable","getRealtimeData"]},{domain:"WebAuthn",commands:["enable","disable","addVirtualAuthenticator","removeVirtualAuthenticator","addCredential","getCredential","getCredentials","removeCredential","clearCredentials","setUserVerified","setAutomaticPresenceSimulation"]},{domain:"Media",commands:["enable","disable"]},{domain:"Console",commands:["clearMessages","disable","enable"]},{domain:"Debugger",commands:["continueToLocation","disable","enable","evaluateOnCallFrame","getPossibleBreakpoints","getScriptSource","getWasmBytecode","getStackTrace","pause","pauseOnAsyncCall","removeBreakpoint","restartFrame","resume","searchInContent","setAsyncCallStackDepth","setBlackboxPatterns","setBlackboxedRanges","setBreakpoint","setInstrumentationBreakpoint","setBreakpointByUrl","setBreakpointOnFunctionCall","setBreakpointsActive","setPauseOnExceptions","setReturnValue","setScriptSource","setSkipAllPauses","setVariableValue","stepInto","stepOut","stepOver"]},{domain:"HeapProfiler",commands:["addInspectedHeapObject","collectGarbage","disable","enable","getHeapObjectId","getObjectByHeapObjectId","getSamplingProfile","startSampling","startTrackingHeapObjects","stopSampling","stopTrackingHeapObjects","takeHeapSnapshot"]},{domain:"Profiler",commands:["disable","enable","getBestEffortCoverage","setSamplingInterval","start","startPreciseCoverage","startTypeProfile","stop","stopPreciseCoverage","stopTypeProfile","takePreciseCoverage","takeTypeProfile"]},{domain:"Runtime",commands:["awaitPromise","callFunctionOn","compileScript","disable","discardConsoleEntries","enable","evaluate","getIsolateId","getHeapUsage","getProperties","globalLexicalScopeNames","queryObjects","releaseObject","releaseObjectGroup","runIfWaitingForDebugger","runScript","setAsyncCallStackDepth","setCustomObjectFormatterEnabled","setMaxCallStackSizeToCapture","terminateExecution","addBinding","removeBinding","getExceptionDetails"]},{domain:"Schema",commands:["getDomains"]}],jt=new Map;class Mt extends wt{_client;constructor(e){super(),this._client=e}}for(let e of Nt){class t extends Mt{constructor(e){super(e)}}for(let n of e.commands)Object.defineProperty(t.prototype,n,{value:async function(t){return await this._client.sendCommand(`${e.domain}.${n}`,t)}});jt.set(e.domain,t)}class Rt extends wt{_cdpConnection;_sessionId;_domains;constructor(e,t){super(),this._cdpConnection=e,this._sessionId=t,this._domains=new Map;for(const[e,t]of jt.entries())this._domains.set(e,new t(this)),Object.defineProperty(this,e,{get(){return this._domains.get(e)}})}sendCommand(e,t){return this._cdpConnection.sendCommand(e,t,this._sessionId)}_onCdpEvent(e,t){this.emit("event",e,t);const[n,s]=e.split("."),r=this._domains.get(n);r&&r.emit(s,t)}}function At(e,t){return new Rt(e,t)}const Lt=e("cdp");class Zt{_transport;_browserCdpClient;_sessionCdpClients=new Map;_commandCallbacks=new Map;_nextId;constructor(e){this._transport=e,this._nextId=0,this._transport.setOnMessage(this._onMessage),this._browserCdpClient=At(this,null)}close(){this._transport.close();for(const[e,{reject:t}]of this._commandCallbacks)t(new Error("Disconnected"));this._commandCallbacks.clear(),this._sessionCdpClients.clear()}browserClient(){return this._browserCdpClient}getCdpClient(e){const t=this._sessionCdpClients.get(e);if(!t)throw new Error("Unknown CDP session ID");return t}sendCommand(e,t,n){return new Promise(((s,r)=>{const a=this._nextId++;this._commandCallbacks.set(a,{resolve:s,reject:r});let i={id:a,method:e,params:t};n&&(i.sessionId=n);const o=JSON.stringify(i);this._transport.sendMessage(o),Lt("sent > "+o)}))}_onMessage=async e=>{Lt("received < "+e);const t=JSON.parse(e);if("Target.attachedToTarget"===t.method){const{sessionId:e}=t.params;this._sessionCdpClients.set(e,At(this,e))}else if("Target.detachedFromTarget"===t.method){const{sessionId:e}=t.params;this._sessionCdpClients.get(e)&&this._sessionCdpClients.delete(e)}if(void 0!==t.id){const e=this._commandCallbacks.get(t.id);e&&(t.result?e.resolve(t.result):t.error&&e.reject(t.error))}else if(t.method){const e=t.sessionId?this._sessionCdpClients.get(t.sessionId):this._browserCdpClient;e&&e._onCdpEvent(t.method,t.params||{})}}}const Ft=e("bidi");class Bt extends wt{_transport;constructor(e){super(),this._transport=e,this._transport.setOnMessage(this._onBidiMessage)}async sendMessage(e){const t=JSON.stringify(e);Ft("sent > "+t),this._transport.sendMessage(t)}close(){this._transport.close()}_onBidiMessage=async e=>{let t;Ft("received < "+e);try{t=this._parseBidiMessage(e)}catch(t){return void this._respondWithError(e,"invalid argument",t.message)}this.emit("message",t)};_respondWithError(e,t,n){const s=this._getErrorResponse(e,t,n);this.sendMessage(s)}_getJsonType(e){return null===e?"null":Array.isArray(e)?"array":typeof e}_getErrorResponse(e,t,n){let s;try{const t=JSON.parse(e);"object"===this._getJsonType(t)&&"id"in t&&(s=t.id)}catch{}return{id:s,error:t,message:n}}_parseBidiMessage(e){let t;try{t=JSON.parse(e)}catch{throw new Error("Cannot parse data as JSON")}const n=this._getJsonType(t);if("object"!==n)throw new Error(`Expected JSON object but got ${n}`);const{id:s,method:r,params:a}=t,i=this._getJsonType(s);if("number"!==i||!Number.isInteger(s)||s<0)throw new Error(`Expected unsigned integer but got ${i}`);const o=this._getJsonType(r);if("string"!==o)throw new Error(`Expected string method but got ${o}`);const c=this._getJsonType(a);if("object"!==c)throw new Error(`Expected object params but got ${c}`);return{id:s,method:r,params:a}}}class zt{#te=new Map;#i;constructor(e){this.#i=e}async sendEvent(e,t){(this.#ne(e.method,null)||null!==t&&this.#ne(e.method,t))&&await this.#i.sendMessage(e)}#ne(e,t){return this.#te.has(t)&&this.#te.get(t).has(e)}async subscribe(e,t){for(let n of e)if(null===t)this.#se(n,null);else for(let e of t)this.#se(n,e)}#se(e,t){this.#te.has(t)||this.#te.set(t,new Set),this.#te.get(t).add(e)}async unsubscribe(e,t){for(let n of e)if(null===t)this.#re(n,null);else for(let e of t)this.#re(n,e)}#re(e,t){const n=this.#te.get(t);n?.delete(e),0===n?.size&&this.#te.delete(t)}}
+/**
+     * Copyright 2021 Google LLC.
+     * Copyright (c) Microsoft Corporation.
+     *
+     * Licensed under the Apache License, Version 2.0 (the "License");
+     * you may not use this file except in compliance with the License.
+     * You may obtain a copy of the License at
+     *
+     *     http://www.apache.org/licenses/LICENSE-2.0
+     *
+     * Unless required by applicable law or agreed to in writing, software
+     * distributed under the License is distributed on an "AS IS" BASIS,
+     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     * See the License for the specific language governing permissions and
+     * limitations under the License.
+     *
+     * @license
+     */const Vt=e("system"),Ut=async function(){return await new Promise((e=>{window.setSelfTargetId=function(t){Vt("current target ID: "+t),e(t)}}))}();(async()=>{window.document.documentElement.innerHTML="<h1>Bidi mapper runs here!</h1><h2>Don't close.</h2>",window.document.title="BiDi Mapper";const e=function(){class e{_onMessage=null;constructor(){window.cdp.onmessage=e=>{this._onMessage&&this._onMessage.call(null,e)}}setOnMessage(e){this._onMessage=e}async sendMessage(e){window.cdp.send(e)}close(){this._onMessage=null,window.cdp.onmessage=null}}return new Zt(new e)}(),t=e.browserClient(),n=function(){class e{_onMessage=null;constructor(){window.onBidiMessage=e=>{this._onMessage&&this._onMessage.call(null,e)}}setOnMessage(e){this._onMessage=e}async sendMessage(e){window.sendBidiResponse(e)}close(){this._onMessage=null,window.onBidiMessage=null}}return new Bt(new e)}(),s=new zt(n),r=await Ut;bt.run(e,n,s,r),await async function(e){await e.Target.setDiscoverTargets({discover:!0}),await e.Target.setAutoAttach({autoAttach:!0,waitForDebuggerOnStart:!0,flatten:!0})}(t),Vt("launched"),n.sendMessage({launched:!0})})()}();
+//# sourceMappingURL=mapper.js.map
diff --git a/third_party/bidimapper/pull.sh b/third_party/bidimapper/pull.sh
new file mode 100755
index 0000000..4a92605
--- /dev/null
+++ b/third_party/bidimapper/pull.sh
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+
+# Copyright 2022 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Exit if any command fails
+set -e
+
+revision="$1"
+
+if [ -z "$revision" ]
+then
+  echo "Usage: $0 git-revision-full-sha1" >&2
+  exit 1
+fi
+
+[ -f "chromium-bidi.zip" ] && rm "chromium-bidi.zip"
+[ -d "src" ] && rm --recursive --force "src"
+
+
+wget --output-document="chromium-bidi.zip" "https://github.com/GoogleChromeLabs/chromium-bidi/archive/$revision.zip"
+unzip "chromium-bidi.zip"
+mv "chromium-bidi-$revision" src
+
+date=$(date "+%Y-%m-%d")
+sha512=$(sha512sum --binary "chromium-bidi.zip" | cut --fields=1 --delimiter=' ')
+rm "chromium-bidi.zip"
+echo "$date,$revision,$sha512" > "revision.info"
+
diff --git a/third_party/blink/public/mojom/BUILD.gn b/third_party/blink/public/mojom/BUILD.gn
index 050ae2c..2dc05ed 100644
--- a/third_party/blink/public/mojom/BUILD.gn
+++ b/third_party/blink/public/mojom/BUILD.gn
@@ -1442,7 +1442,10 @@
 
 mojom_component("script_type_mojo_bindings") {
   generate_java = true
-  sources = [ "script/script_type.mojom" ]
+  sources = [
+    "script/script_evaluation_params.mojom",
+    "script/script_type.mojom",
+  ]
 
   macro_prefix = "SCRIPT_TYPE_MOJOM"
   output_prefix = "script_type_mojom"
diff --git a/third_party/blink/public/mojom/devtools/inspector_issue.mojom b/third_party/blink/public/mojom/devtools/inspector_issue.mojom
index 95e547d..3506928f 100644
--- a/third_party/blink/public/mojom/devtools/inspector_issue.mojom
+++ b/third_party/blink/public/mojom/devtools/inspector_issue.mojom
@@ -208,7 +208,6 @@
   kErrorFetchingIdTokenHttpNotFound,
   kErrorFetchingIdTokenNoResponse,
   kErrorFetchingIdTokenInvalidResponse,
-  kErrorFetchingIdTokenInvalidRequest,
   kErrorCanceled,
   kError,
 };
diff --git a/third_party/blink/public/mojom/script/script_evaluation_params.mojom b/third_party/blink/public/mojom/script/script_evaluation_params.mojom
new file mode 100644
index 0000000..014cba2
--- /dev/null
+++ b/third_party/blink/public/mojom/script/script_evaluation_params.mojom
@@ -0,0 +1,49 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be found
+// in the LICENSE file.
+
+module blink.mojom;
+
+// Parameters to evaluate scripts, mainly from outside Blink.
+
+// Whether the script evaluation should be associated with a user gesture.
+enum UserActivationOption {
+  kDoNotActivate,
+  kActivate,
+};
+
+enum WantResultOption {
+  kNoResult,
+
+  // Evaluation result (or promise resolution result if
+  // `PromiseResultOption::kAwait` is used) is passed to callback.
+  kWantResult,
+};
+
+enum PromiseResultOption {
+  // If the result of the executed script is a promise or other then-able,
+  // wait for it to settle and pass the result of the promise to the caller.
+  // If the promise (and any subsequent thenables) resolves, this passes the
+  // value. If the promise rejects, the corresponding value will be empty.
+  kAwait,
+
+  // Don't wait for any promise to settle.
+  kDoNotWait,
+};
+
+enum EvaluationTiming {
+  // Execute scripts asynchronously.
+  kAsynchronous,
+
+  // Execute scripts synchronously, unless the page is suspended.
+  // Even in this case, completion can be asynchronous, e.g. when
+  // `PromiseResultOption::kAwait` is used.
+  // If the page is suspended, execute scripts asynchronously.
+  kSynchronous,
+};
+
+// Whether to block window load event until the scripts are evaluated.
+enum LoadEventBlockingOption {
+  kDoNotBlock,
+  kBlock,
+};
diff --git a/third_party/blink/public/mojom/webid/federated_auth_request.mojom b/third_party/blink/public/mojom/webid/federated_auth_request.mojom
index 6f2e2de..675d957 100644
--- a/third_party/blink/public/mojom/webid/federated_auth_request.mojom
+++ b/third_party/blink/public/mojom/webid/federated_auth_request.mojom
@@ -41,17 +41,24 @@
   string account_id;
 };
 
+// The details of an identity provider.
+struct IdentityProvider {
+  url.mojom.Url config_url;
+
+  // Can be an empty string to be omitted in the request sent to the provider.
+  string client_id;
+
+  // Can be an empty string to be omitted in the request sent to the provider.
+  string nonce;
+};
+
 // Create a federated sign-in request using the specified provider.
 // This interface is called from a renderer process and implemented in the
 // browser process.
 interface FederatedAuthRequest {
-  // Requests a token to be generated, given an IDP URL.
-  // |client_id| and |nonce| can be empty strings to omit the fields in the
-  // request sent to the provider.
+  // Requests a token to be generated, given an IdentityProvider.
   // Returns the raw content of the token.
-  RequestToken(url.mojom.Url provider,
-                 string client_id,
-                 string nonce,
+  RequestToken(IdentityProvider identity_provider_ptr,
                  bool prefer_auto_sign_in) =>
       (RequestTokenStatus status, string? token);
 
diff --git a/third_party/blink/public/platform/web_runtime_features.h b/third_party/blink/public/platform/web_runtime_features.h
index 9229e433..787171d 100644
--- a/third_party/blink/public/platform/web_runtime_features.h
+++ b/third_party/blink/public/platform/web_runtime_features.h
@@ -112,6 +112,7 @@
   BLINK_PLATFORM_EXPORT static void EnableFedCm(bool);
   BLINK_PLATFORM_EXPORT static void EnableFedCmIdpSignout(bool);
   BLINK_PLATFORM_EXPORT static void EnableFedCmIframeSupport(bool);
+  BLINK_PLATFORM_EXPORT static void EnableFedCmMultipleIdentityProviders(bool);
   BLINK_PLATFORM_EXPORT static void EnableFencedFrames(bool);
   BLINK_PLATFORM_EXPORT static bool IsFencedFramesEnabled();
   BLINK_PLATFORM_EXPORT static void EnableFileSystem(bool);
diff --git a/third_party/blink/public/web/web_local_frame.h b/third_party/blink/public/web/web_local_frame.h
index 8c82414..fb11ec3 100644
--- a/third_party/blink/public/web/web_local_frame.h
+++ b/third_party/blink/public/web/web_local_frame.h
@@ -36,6 +36,7 @@
 #include "third_party/blink/public/mojom/page/widget.mojom-shared.h"
 #include "third_party/blink/public/mojom/permissions_policy/permissions_policy.mojom-shared.h"
 #include "third_party/blink/public/mojom/portal/portal.mojom-shared.h"
+#include "third_party/blink/public/mojom/script/script_evaluation_params.mojom-shared.h"
 #include "third_party/blink/public/mojom/selection_menu/selection_menu_behavior.mojom-shared.h"
 #include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom-shared.h"
 #include "third_party/blink/public/platform/cross_variant_mojo_util.h"
@@ -414,35 +415,17 @@
                                         v8::Local<v8::Value> argv[],
                                         WebScriptExecutionCallback*) = 0;
 
-  enum ScriptExecutionType {
-    // Execute script synchronously, unless the page is suspended.
-    kSynchronous,
-    // Execute script asynchronously.
-    kAsynchronous,
-    // Execute script asynchronously, blocking the window.onload event.
-    kAsynchronousBlockingOnload
-  };
-
-  enum class PromiseBehavior {
-    // If the result of the executed script is a promise or other then-able,
-    // wait for it to settle and pass the result of the promise to the caller.
-    // If the promise (and any subsequent thenables) resolves, this passes the
-    // value. If the promise rejects, the corresponding value will be empty.
-    kAwait,
-    // Don't wait for any promise to settle.
-    kDontWait,
-  };
-
   // Executes the script in the main world of the page.
   // Use kMainDOMWorldId to execute in the main world; otherwise,
   // `world_id` must be a positive integer and less than kEmbedderWorldIdLimit.
   virtual void RequestExecuteScript(int32_t world_id,
                                     base::span<const WebScriptSource> sources,
-                                    bool user_gesture,
-                                    ScriptExecutionType,
+                                    mojom::UserActivationOption,
+                                    mojom::EvaluationTiming,
+                                    mojom::LoadEventBlockingOption,
                                     WebScriptExecutionCallback*,
                                     BackForwardCacheAware,
-                                    PromiseBehavior) = 0;
+                                    mojom::PromiseResultOption) = 0;
 
   // Logs to the console associated with this frame. If |discard_duplicates| is
   // set, the message will only be added if it is unique (i.e. has not been
@@ -475,8 +458,8 @@
   virtual WebRange MarkedRange() const = 0;
 
   // Returns the text range rectangle in the viepwort coordinate space.
-  virtual bool FirstRectForCharacterRange(unsigned location,
-                                          unsigned length,
+  virtual bool FirstRectForCharacterRange(uint32_t location,
+                                          uint32_t length,
                                           gfx::Rect&) const = 0;
 
   // Supports commands like Undo, Redo, Cut, Copy, Paste, SelectAll,
diff --git a/third_party/blink/renderer/core/animation/css_color_interpolation_type.cc b/third_party/blink/renderer/core/animation/css_color_interpolation_type.cc
index 5734f71..a9865c4 100644
--- a/third_party/blink/renderer/core/animation/css_color_interpolation_type.cc
+++ b/third_party/blink/renderer/core/animation/css_color_interpolation_type.cc
@@ -343,7 +343,7 @@
     const StyleResolverState& state) const {
   const auto& color_pair = To<InterpolableList>(interpolable_value);
   Color color = ResolveInterpolableColor(*color_pair.Get(kUnvisited), state);
-  return cssvalue::CSSColor::Create(color.Rgb());
+  return cssvalue::CSSColor::Create(color);
 }
 
 void CSSColorInterpolationType::Composite(
diff --git a/third_party/blink/renderer/core/css/binary_data_font_face_source.cc b/third_party/blink/renderer/core/css/binary_data_font_face_source.cc
index 203696a..ba38d46 100644
--- a/third_party/blink/renderer/core/css/binary_data_font_face_source.cc
+++ b/third_party/blink/renderer/core/css/binary_data_font_face_source.cc
@@ -40,6 +40,7 @@
   return SimpleFontData::Create(
       custom_platform_data_->GetFontPlatformData(
           font_description.EffectiveFontSize(),
+          font_description.AdjustedSpecifiedSize(),
           font_description.IsSyntheticBold() &&
               font_description.SyntheticBoldAllowed(),
           font_description.IsSyntheticItalic() &&
diff --git a/third_party/blink/renderer/core/css/css_color.cc b/third_party/blink/renderer/core/css/css_color.cc
index bf1b0e7..80509508 100644
--- a/third_party/blink/renderer/core/css/css_color.cc
+++ b/third_party/blink/renderer/core/css/css_color.cc
@@ -10,21 +10,21 @@
 namespace blink {
 namespace cssvalue {
 
-CSSColor* CSSColor::Create(RGBA32 color) {
+CSSColor* CSSColor::Create(const Color& color) {
   // These are the empty and deleted values of the hash table.
-  if (Color::FromRGBA32(color) == Color::kTransparent)
+  if (color == Color::kTransparent)
     return CssValuePool().TransparentColor();
-  if (Color::FromRGBA32(color) == Color::kWhite)
+  if (color == Color::kWhite)
     return CssValuePool().WhiteColor();
   // Just because it is common.
-  if (Color::FromRGBA32(color) == Color::kBlack)
+  if (color == Color::kBlack)
     return CssValuePool().BlackColor();
 
+  // TODO(https://crbug.com/1351544): Use blink::Color as the cache key.
   CSSValuePool::ColorValueCache::AddResult entry =
-      CssValuePool().GetColorCacheEntry(color);
+      CssValuePool().GetColorCacheEntry(color.Rgb());
   if (entry.is_new_entry) {
-    entry.stored_value->value =
-        MakeGarbageCollected<CSSColor>(Color::FromRGBA32(color));
+    entry.stored_value->value = MakeGarbageCollected<CSSColor>(color);
   }
   return entry.stored_value->value;
 }
diff --git a/third_party/blink/renderer/core/css/css_color.h b/third_party/blink/renderer/core/css/css_color.h
index ec0a383..13ef2eb3 100644
--- a/third_party/blink/renderer/core/css/css_color.h
+++ b/third_party/blink/renderer/core/css/css_color.h
@@ -20,8 +20,7 @@
 // Represents the non-keyword subset of <color>.
 class CORE_EXPORT CSSColor : public CSSValue {
  public:
-  // TODO(sashab): Make this create() method take a Color instead.
-  static CSSColor* Create(RGBA32 color);
+  static CSSColor* Create(const Color& color);
 
   CSSColor(Color color) : CSSValue(kColorClass), color_(color) {}
 
diff --git a/third_party/blink/renderer/core/css/css_gradient_value.cc b/third_party/blink/renderer/core/css/css_gradient_value.cc
index 72fbe5e..5528bd8 100644
--- a/third_party/blink/renderer/core/css/css_gradient_value.cc
+++ b/third_party/blink/renderer/core/css/css_gradient_value.cc
@@ -359,7 +359,7 @@
       case CSSValueID::kCurrentcolor:
         if (allow_visited_style) {
           stop.color_ = CSSColor::Create(
-              style.VisitedDependentColor(GetCSSPropertyColor()).Rgb());
+              style.VisitedDependentColor(GetCSSPropertyColor()));
         } else {
           stop.color_ = ComputedStyleUtils::CurrentColorOrValidColor(
               style, StyleColor(), CSSValuePhase::kComputedValue);
@@ -367,10 +367,8 @@
         break;
       default:
         // TODO(crbug.com/929098) Need to pass an appropriate color scheme here.
-        stop.color_ =
-            CSSColor::Create(StyleColor::ColorFromKeyword(
-                                 value_id, mojom::blink::ColorScheme::kLight)
-                                 .Rgb());
+        stop.color_ = CSSColor::Create(StyleColor::ColorFromKeyword(
+            value_id, mojom::blink::ColorScheme::kLight));
     }
     AddStop(stop);
   }
diff --git a/third_party/blink/renderer/core/css/cssom/css_color_value.cc b/third_party/blink/renderer/core/css/cssom/css_color_value.cc
index 843b43c8..59ccee3 100644
--- a/third_party/blink/renderer/core/css/cssom/css_color_value.cc
+++ b/third_party/blink/renderer/core/css/cssom/css_color_value.cc
@@ -43,7 +43,7 @@
 }
 
 const CSSValue* CSSColorValue::ToCSSValue() const {
-  return cssvalue::CSSColor::Create(ToColor().Rgb());
+  return cssvalue::CSSColor::Create(ToColor());
 }
 
 CSSNumericValue* CSSColorValue::ToNumberOrPercentage(
diff --git a/third_party/blink/renderer/core/css/cssom/css_unsupported_color.cc b/third_party/blink/renderer/core/css/cssom/css_unsupported_color.cc
index a66dd45..06c9f4c 100644
--- a/third_party/blink/renderer/core/css/cssom/css_unsupported_color.cc
+++ b/third_party/blink/renderer/core/css/cssom/css_unsupported_color.cc
@@ -22,9 +22,7 @@
 }
 
 const CSSValue* CSSUnsupportedColor::ToCSSValue() const {
-  return cssvalue::CSSColor::Create(
-      MakeRGBA(color_value_.Red(), color_value_.Green(), color_value_.Blue(),
-               color_value_.Alpha()));
+  return cssvalue::CSSColor::Create(color_value_);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/parser/css_parser_fast_paths.cc b/third_party/blink/renderer/core/css/parser/css_parser_fast_paths.cc
index c555bf038..dc102386 100644
--- a/third_party/blink/renderer/core/css/parser/css_parser_fast_paths.cc
+++ b/third_party/blink/renderer/core/css/parser/css_parser_fast_paths.cc
@@ -812,7 +812,7 @@
       });
   if (!parse_result)
     return nullptr;
-  return cssvalue::CSSColor::Create(color);
+  return cssvalue::CSSColor::Create(Color::FromRGBA32(color));
 }
 
 CSSValue* CSSParserFastPaths::ParseColor(const String& string,
diff --git a/third_party/blink/renderer/core/css/properties/computed_style_utils.cc b/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
index 85644bb..8c1d0ff 100644
--- a/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
+++ b/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
@@ -119,7 +119,7 @@
     const StyleColor& color,
     CSSValuePhase value_phase) {
   return cssvalue::CSSColor::Create(
-      color.Resolve(style.GetCurrentColor(), style.UsedColorScheme()).Rgb());
+      color.Resolve(style.GetCurrentColor(), style.UsedColorScheme()));
 }
 
 const blink::Color ComputedStyleUtils::BorderSideColor(
@@ -3032,10 +3032,8 @@
     const StyleAutoColor& color,
     CSSValuePhase value_phase) {
   if (color.IsAutoColor()) {
-    return cssvalue::CSSColor::Create(
-        StyleColor::CurrentColor()
-            .Resolve(style.GetCurrentColor(), style.UsedColorScheme())
-            .Rgb());
+    return cssvalue::CSSColor::Create(StyleColor::CurrentColor().Resolve(
+        style.GetCurrentColor(), style.UsedColorScheme()));
   }
   return ComputedStyleUtils::CurrentColorOrValidColor(
       style, color.ToStyleColor(), value_phase);
diff --git a/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc b/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
index 85f40e1..8e51849c 100644
--- a/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
+++ b/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
@@ -1747,7 +1747,7 @@
     return ConsumeInternalLightDark(ConsumeColor, range, context,
                                     accept_quirky_colors, allowed_keywords);
   }
-  return cssvalue::CSSColor::Create(color);
+  return cssvalue::CSSColor::Create(Color::FromRGBA32(color));
 }
 
 CSSValue* ConsumeLineWidth(CSSParserTokenRange& range,
diff --git a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
index 6e15db32..27c1e95c 100644
--- a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
+++ b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
@@ -602,7 +602,7 @@
     const LayoutObject*,
     bool allow_visited_style) const {
   if (allow_visited_style) {
-    return cssvalue::CSSColor::Create(style.VisitedDependentColor(*this).Rgb());
+    return cssvalue::CSSColor::Create(style.VisitedDependentColor(*this));
   }
 
   StyleColor background_color = style.BackgroundColor();
@@ -844,8 +844,7 @@
   // https://drafts.csswg.org/cssom/#resolved-values
   // For this property, the resolved value is the used value.
   return allow_visited_style
-             ? cssvalue::CSSColor::Create(
-                   style.VisitedDependentColor(*this).Rgb())
+             ? cssvalue::CSSColor::Create(style.VisitedDependentColor(*this))
              : ComputedStyleUtils::CurrentColorOrValidColor(
                    style, border_bottom_color, CSSValuePhase::kUsedValue);
 }
@@ -1107,8 +1106,7 @@
   // https://drafts.csswg.org/cssom/#resolved-values
   // For this property, the resolved value is the used value.
   return allow_visited_style
-             ? cssvalue::CSSColor::Create(
-                   style.VisitedDependentColor(*this).Rgb())
+             ? cssvalue::CSSColor::Create(style.VisitedDependentColor(*this))
              : ComputedStyleUtils::CurrentColorOrValidColor(
                    style, border_left_color, CSSValuePhase::kUsedValue);
 }
@@ -1167,8 +1165,7 @@
   // https://drafts.csswg.org/cssom/#resolved-values
   // For this property, the resolved value is the used value.
   return allow_visited_style
-             ? cssvalue::CSSColor::Create(
-                   style.VisitedDependentColor(*this).Rgb())
+             ? cssvalue::CSSColor::Create(style.VisitedDependentColor(*this))
              : ComputedStyleUtils::CurrentColorOrValidColor(
                    style, border_right_color, CSSValuePhase::kUsedValue);
 }
@@ -1241,8 +1238,7 @@
   // https://drafts.csswg.org/cssom/#resolved-values
   // For this property, the resolved value is the used value.
   return allow_visited_style
-             ? cssvalue::CSSColor::Create(
-                   style.VisitedDependentColor(*this).Rgb())
+             ? cssvalue::CSSColor::Create(style.VisitedDependentColor(*this))
              : ComputedStyleUtils::ComputedStyleUtils::CurrentColorOrValidColor(
                    style, border_top_color, CSSValuePhase::kUsedValue);
 }
@@ -1410,7 +1406,7 @@
     const LayoutObject*,
     bool allow_visited_style) const {
   if (allow_visited_style) {
-    return cssvalue::CSSColor::Create(style.VisitedDependentColor(*this).Rgb());
+    return cssvalue::CSSColor::Create(style.VisitedDependentColor(*this));
   }
 
   StyleAutoColor auto_color = style.CaretColor();
@@ -1419,8 +1415,7 @@
   StyleColor result = auto_color.IsAutoColor() ? StyleColor::CurrentColor()
                                                : auto_color.ToStyleColor();
   if (style.ShouldForceColor(result)) {
-    return cssvalue::CSSColor::Create(
-        style.GetInternalForcedCurrentColor().Rgb());
+    return cssvalue::CSSColor::Create(style.GetInternalForcedCurrentColor());
   }
 
   // https://drafts.csswg.org/cssom/#resolved-values
@@ -1573,9 +1568,9 @@
     return GetCSSPropertyInternalForcedColor().CSSValueFromComputedStyle(
         style, nullptr, allow_visited_style);
   }
-  return cssvalue::CSSColor::Create(
-      allow_visited_style ? style.VisitedDependentColor(*this).Rgb()
-                          : style.GetCurrentColor().Rgb());
+  return cssvalue::CSSColor::Create(allow_visited_style
+                                        ? style.VisitedDependentColor(*this)
+                                        : style.GetCurrentColor());
 }
 
 void Color::ApplyInitial(StyleResolverState& state) const {
@@ -1814,11 +1809,11 @@
     const ComputedStyle& style,
     const LayoutObject*,
     bool allow_visited_style) const {
-  return allow_visited_style ? cssvalue::CSSColor::Create(
-                                   style.VisitedDependentColor(*this).Rgb())
-                             : ComputedStyleUtils::CurrentColorOrValidColor(
-                                   style, style.ColumnRuleColor(),
-                                   CSSValuePhase::kComputedValue);
+  return allow_visited_style
+             ? cssvalue::CSSColor::Create(style.VisitedDependentColor(*this))
+             : ComputedStyleUtils::CurrentColorOrValidColor(
+                   style, style.ColumnRuleColor(),
+                   CSSValuePhase::kComputedValue);
 }
 
 const CSSValue* ColumnRuleStyle::CSSValueFromComputedStyleInternal(
@@ -4121,7 +4116,7 @@
   bool visited_link = allow_visited_style &&
                       style.InsideLink() == EInsideLink::kInsideVisitedLink;
   return cssvalue::CSSColor::Create(
-      ColorIncludingFallback(visited_link, style).Rgb());
+      ColorIncludingFallback(visited_link, style));
 }
 
 const CSSValue* InternalForcedBackgroundColor::ParseSingleValue(
@@ -4150,7 +4145,7 @@
   bool visited_link = allow_visited_style &&
                       style.InsideLink() == EInsideLink::kInsideVisitedLink;
   return cssvalue::CSSColor::Create(
-      ColorIncludingFallback(visited_link, style).Rgb());
+      ColorIncludingFallback(visited_link, style));
 }
 
 const CSSValue* InternalForcedBorderColor::ParseSingleValue(
@@ -4201,8 +4196,8 @@
     const LayoutObject*,
     bool allow_visited_style) const {
   return cssvalue::CSSColor::Create(
-      allow_visited_style ? style.VisitedDependentColor(*this).Rgb()
-                          : style.GetInternalForcedCurrentColor().Rgb());
+      allow_visited_style ? style.VisitedDependentColor(*this)
+                          : style.GetInternalForcedCurrentColor());
 }
 
 const CSSValue* InternalForcedColor::ParseSingleValue(
@@ -4231,7 +4226,7 @@
   bool visited_link = allow_visited_style &&
                       style.InsideLink() == EInsideLink::kInsideVisitedLink;
   return cssvalue::CSSColor::Create(
-      ColorIncludingFallback(visited_link, style).Rgb());
+      ColorIncludingFallback(visited_link, style));
 }
 
 const CSSValue* InternalForcedOutlineColor::ParseSingleValue(
@@ -5233,8 +5228,7 @@
   // https://drafts.csswg.org/cssom/#resolved-values
   // For this property, the resolved value is the used value.
   return allow_visited_style
-             ? cssvalue::CSSColor::Create(
-                   style.VisitedDependentColor(*this).Rgb())
+             ? cssvalue::CSSColor::Create(style.VisitedDependentColor(*this))
              : ComputedStyleUtils::CurrentColorOrValidColor(
                    style, outline_color, CSSValuePhase::kUsedValue);
 }
diff --git a/third_party/blink/renderer/core/css/remote_font_face_source.cc b/third_party/blink/renderer/core/css/remote_font_face_source.cc
index cf944c61..465097b67 100644
--- a/third_party/blink/renderer/core/css/remote_font_face_source.cc
+++ b/third_party/blink/renderer/core/css/remote_font_face_source.cc
@@ -351,6 +351,7 @@
   return SimpleFontData::Create(
       custom_font_data_->GetFontPlatformData(
           font_description.EffectiveFontSize(),
+          font_description.AdjustedSpecifiedSize(),
           font_description.IsSyntheticBold() &&
               font_description.SyntheticBoldAllowed(),
           font_description.IsSyntheticItalic() &&
diff --git a/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc b/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc
index 662abec3..50cedb1 100644
--- a/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc
@@ -2110,7 +2110,7 @@
                 : mojom::blink::ColorScheme::kLight;
       Color color = document.GetTextLinkColors().ColorFromCSSValue(
           value, Color(), scheme, false);
-      return *cssvalue::CSSColor::Create(color.Rgb());
+      return *cssvalue::CSSColor::Create(color);
     }
   }
 
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
index 1cf9a9f5..2df1357 100644
--- a/third_party/blink/renderer/core/dom/element.cc
+++ b/third_party/blink/renderer/core/dom/element.cc
@@ -2626,8 +2626,7 @@
         document.PopupHintShowing()->HidePopUpInternal(focus_behavior,
                                                        forcing_level);
       }
-      while (!document.PopupStack().IsEmpty() &&
-             document.PopupStack().back() != endpoint) {
+      while (!document.PopupStack().IsEmpty()) {
         document.PopupStack().back()->HidePopUpInternal(focus_behavior,
                                                         forcing_level);
       }
diff --git a/third_party/blink/renderer/core/editing/ime/edit_context.cc b/third_party/blink/renderer/core/editing/ime/edit_context.cc
index 551c0a4..e50917f6 100644
--- a/third_party/blink/renderer/core/editing/ime/edit_context.cc
+++ b/third_party/blink/renderer/core/editing/ime/edit_context.cc
@@ -140,9 +140,10 @@
       static_cast<WTF::wtf_size_t>(ime_text_spans.size()));
 
   for (const auto& ime_text_span : ime_text_spans) {
-    const int range_start =
-        ime_text_span.start_offset + composition_range_start_;
-    const int range_end = ime_text_span.end_offset + composition_range_start_;
+    const auto range_start = base::checked_cast<wtf_size_t>(
+        ime_text_span.start_offset + composition_range_start_);
+    const auto range_end = base::checked_cast<wtf_size_t>(
+        ime_text_span.end_offset + composition_range_start_);
 
     String underline_thickness;
     String underline_style;
diff --git a/third_party/blink/renderer/core/editing/ime/ime_text_span.cc b/third_party/blink/renderer/core/editing/ime/ime_text_span.cc
index 5fc34de..c9fb0f76 100644
--- a/third_party/blink/renderer/core/editing/ime/ime_text_span.cc
+++ b/third_party/blink/renderer/core/editing/ime/ime_text_span.cc
@@ -5,6 +5,8 @@
 #include "third_party/blink/renderer/core/editing/ime/ime_text_span.h"
 
 #include <algorithm>
+
+#include "base/numerics/safe_conversions.h"
 #include "ui/base/ime/ime_text_span.h"
 #include "ui/base/ime/mojom/ime_types.mojom-blink.h"
 
@@ -29,8 +31,8 @@
 }
 
 ImeTextSpan::ImeTextSpan(Type type,
-                         unsigned start_offset,
-                         unsigned end_offset,
+                         wtf_size_t start_offset,
+                         wtf_size_t end_offset,
                          const Color& underline_color,
                          ui::mojom::ImeTextSpanThickness thickness,
                          ui::mojom::ImeTextSpanUnderlineStyle underline_style,
@@ -54,7 +56,7 @@
   // possible position.
   // TODO(wkorman): Consider replacing with DCHECK_LT(startOffset, endOffset).
   start_offset_ =
-      std::min(start_offset, std::numeric_limits<unsigned>::max() - 1u);
+      std::min(start_offset, std::numeric_limits<wtf_size_t>::max() - 1u);
   end_offset_ = std::max(start_offset_ + 1u, end_offset);
 }
 
@@ -133,8 +135,8 @@
 
 ImeTextSpan::ImeTextSpan(const ui::ImeTextSpan& ime_text_span)
     : ImeTextSpan(ConvertUiTypeToType(ime_text_span.type),
-                  ime_text_span.start_offset,
-                  ime_text_span.end_offset,
+                  base::checked_cast<wtf_size_t>(ime_text_span.start_offset),
+                  base::checked_cast<wtf_size_t>(ime_text_span.end_offset),
                   Color::FromSkColor(ime_text_span.underline_color),
                   ConvertUiThicknessToThickness(ime_text_span.thickness),
                   ConvertUiUnderlineToUnderline(ime_text_span.underline_style),
diff --git a/third_party/blink/renderer/core/editing/ime/ime_text_span.h b/third_party/blink/renderer/core/editing/ime/ime_text_span.h
index 57b4c2e..bb6d5fd 100644
--- a/third_party/blink/renderer/core/editing/ime/ime_text_span.h
+++ b/third_party/blink/renderer/core/editing/ime/ime_text_span.h
@@ -49,8 +49,8 @@
   };
 
   ImeTextSpan(Type,
-              unsigned start_offset,
-              unsigned end_offset,
+              wtf_size_t start_offset,
+              wtf_size_t end_offset,
               const Color& underline_color,
               ui::mojom::ImeTextSpanThickness,
               ui::mojom::ImeTextSpanUnderlineStyle,
@@ -64,8 +64,8 @@
   explicit ImeTextSpan(const ui::ImeTextSpan&);
 
   Type GetType() const { return type_; }
-  unsigned StartOffset() const { return start_offset_; }
-  unsigned EndOffset() const { return end_offset_; }
+  wtf_size_t StartOffset() const { return start_offset_; }
+  wtf_size_t EndOffset() const { return end_offset_; }
   const Color& UnderlineColor() const { return underline_color_; }
   ui::mojom::ImeTextSpanThickness Thickness() const { return thickness_; }
   ui::mojom::ImeTextSpanUnderlineStyle UnderlineStyle() const {
@@ -86,8 +86,8 @@
 
  private:
   Type type_;
-  unsigned start_offset_;
-  unsigned end_offset_;
+  wtf_size_t start_offset_;
+  wtf_size_t end_offset_;
   Color underline_color_;
   ui::mojom::ImeTextSpanThickness thickness_;
   ui::mojom::ImeTextSpanUnderlineStyle underline_style_;
diff --git a/third_party/blink/renderer/core/editing/ime/input_method_controller.cc b/third_party/blink/renderer/core/editing/ime/input_method_controller.cc
index c7262fc4..9fc1f5a5 100644
--- a/third_party/blink/renderer/core/editing/ime/input_method_controller.cc
+++ b/third_party/blink/renderer/core/editing/ime/input_method_controller.cc
@@ -736,9 +736,9 @@
     ContainerNode* base_element,
     unsigned offset_in_plain_chars) {
   for (const auto& ime_text_span : ime_text_spans) {
-    unsigned ime_text_span_start =
+    wtf_size_t ime_text_span_start =
         offset_in_plain_chars + ime_text_span.StartOffset();
-    unsigned ime_text_span_end =
+    wtf_size_t ime_text_span_end =
         offset_in_plain_chars + ime_text_span.EndOffset();
 
     EphemeralRange ephemeral_line_range =
diff --git a/third_party/blink/renderer/core/editing/ime/text_format.cc b/third_party/blink/renderer/core/editing/ime/text_format.cc
index a2a1862..d2019d0 100644
--- a/third_party/blink/renderer/core/editing/ime/text_format.cc
+++ b/third_party/blink/renderer/core/editing/ime/text_format.cc
@@ -8,8 +8,8 @@
 
 namespace blink {
 
-TextFormat::TextFormat(uint32_t range_start,
-                       uint32_t range_end,
+TextFormat::TextFormat(wtf_size_t range_start,
+                       wtf_size_t range_end,
                        const String& text_color,
                        const String& background_color,
                        const String& underline_color,
@@ -23,8 +23,8 @@
       underline_style_(underline_style),
       underline_thickness_(underline_thickness) {}
 
-TextFormat* TextFormat::Create(uint32_t range_start,
-                               uint32_t range_end,
+TextFormat* TextFormat::Create(wtf_size_t range_start,
+                               wtf_size_t range_end,
                                const String& text_color,
                                const String& background_color,
                                const String& underline_color,
@@ -62,11 +62,11 @@
   return MakeGarbageCollected<TextFormat>(dict);
 }
 
-uint32_t TextFormat::rangeStart() const {
+wtf_size_t TextFormat::rangeStart() const {
   return range_start_;
 }
 
-uint32_t TextFormat::rangeEnd() const {
+wtf_size_t TextFormat::rangeEnd() const {
   return range_end_;
 }
 
diff --git a/third_party/blink/renderer/core/editing/ime/text_format.h b/third_party/blink/renderer/core/editing/ime/text_format.h
index ef78028..2a2af5a5 100644
--- a/third_party/blink/renderer/core/editing/ime/text_format.h
+++ b/third_party/blink/renderer/core/editing/ime/text_format.h
@@ -23,24 +23,24 @@
 
  public:
   static TextFormat* Create(const TextFormatInit* dict);
-  static TextFormat* Create(uint32_t range_start,
-                            uint32_t range_end,
+  static TextFormat* Create(wtf_size_t range_start,
+                            wtf_size_t range_end,
                             const String& text_color,
                             const String& background_color,
                             const String& underline_color,
                             const String& underline_style,
                             const String& underline_thickness);
   explicit TextFormat(const TextFormatInit* dict);
-  TextFormat(uint32_t range_start,
-             uint32_t range_end,
+  TextFormat(wtf_size_t range_start,
+             wtf_size_t range_end,
              const String& text_color,
              const String& background_color,
              const String& underline_color,
              const String& underline_style,
              const String& underline_thickness);
 
-  uint32_t rangeStart() const;
-  uint32_t rangeEnd() const;
+  wtf_size_t rangeStart() const;
+  wtf_size_t rangeEnd() const;
   String textColor() const;
   String backgroundColor() const;
   String underlineColor() const;
@@ -48,8 +48,8 @@
   String underlineThickness() const;
 
  private:
-  uint32_t range_start_ = 0;
-  uint32_t range_end_ = 0;
+  wtf_size_t range_start_ = 0;
+  wtf_size_t range_end_ = 0;
   String text_color_;
   String background_color_;
   String underline_color_;
diff --git a/third_party/blink/renderer/core/frame/attribution_src_loader.cc b/third_party/blink/renderer/core/frame/attribution_src_loader.cc
index 57948b7a..dea509e 100644
--- a/third_party/blink/renderer/core/frame/attribution_src_loader.cc
+++ b/third_party/blink/renderer/core/frame/attribution_src_loader.cc
@@ -29,6 +29,7 @@
 #include "third_party/blink/renderer/core/frame/local_frame_client.h"
 #include "third_party/blink/renderer/core/html/html_element.h"
 #include "third_party/blink/renderer/core/inspector/identifiers_factory.h"
+#include "third_party/blink/renderer/core/inspector/inspector_audits_issue.h"
 #include "third_party/blink/renderer/platform/heap/persistent.h"
 #include "third_party/blink/renderer/platform/heap/self_keep_alive.h"
 #include "third_party/blink/renderer/platform/loader/attribution_header_constants.h"
@@ -111,43 +112,6 @@
 
 }  // namespace
 
-bool CanRegisterAttributionInContext(LocalFrame* frame,
-                                     HTMLElement* element,
-                                     absl::optional<uint64_t> request_id,
-                                     bool log_issues) {
-  DCHECK(frame);
-
-  LocalDOMWindow* window = frame->DomWindow();
-  DCHECK(window);
-
-  if (!RuntimeEnabledFeatures::AttributionReportingEnabled(window))
-    return false;
-
-  const bool feature_policy_enabled = window->IsFeatureEnabled(
-      mojom::blink::PermissionsPolicyFeature::kAttributionReporting);
-
-  if (!feature_policy_enabled) {
-    if (log_issues) {
-      LogAuditIssue(window,
-                    AttributionReportingIssueType::kPermissionPolicyDisabled,
-                    element, request_id, /*invalid_parameter=*/String());
-    }
-    return false;
-  }
-
-  if (!window->IsSecureContext()) {
-    if (log_issues) {
-      LogAuditIssue(
-          window, AttributionReportingIssueType::kInsecureContext, element,
-          request_id, /*invalid_parameter=*/
-          window->GetSecurityContext().GetSecurityOrigin()->ToString());
-    }
-    return false;
-  }
-
-  return true;
-}
-
 class AttributionSrcLoader::ResourceClient
     : public GarbageCollected<AttributionSrcLoader::ResourceClient>,
       public RawResourceClient {
@@ -295,10 +259,8 @@
     return nullptr;
   }
 
-  if (!ReportingOriginForUrlIfValid(src_url, element,
-                                    /*request_id=*/absl::nullopt)) {
+  if (!CanRegister(src_url, element, /*request_id=*/absl::nullopt))
     return nullptr;
-  }
 
   Document* document = window->document();
 
@@ -365,21 +327,45 @@
 AttributionSrcLoader::ReportingOriginForUrlIfValid(
     const KURL& url,
     HTMLElement* element,
-    absl::optional<uint64_t> request_id) {
+    absl::optional<uint64_t> request_id,
+    bool log_issues) {
   LocalDOMWindow* window = local_frame_->DomWindow();
   DCHECK(window);
 
-  if (!CanRegisterAttributionInContext(local_frame_, element, request_id))
+  auto maybe_log_audit_issue = [&](AttributionReportingIssueType issue_type,
+                                   const SecurityOrigin* invalid_origin =
+                                       nullptr) {
+    if (!log_issues)
+      return;
+
+    LogAuditIssue(window, issue_type, element, request_id,
+                  /*invalid_parameter=*/
+                  invalid_origin ? invalid_origin->ToString() : String());
+  };
+
+  if (!RuntimeEnabledFeatures::AttributionReportingEnabled(window))
     return nullptr;
 
+  if (!window->IsFeatureEnabled(
+          mojom::blink::PermissionsPolicyFeature::kAttributionReporting)) {
+    maybe_log_audit_issue(
+        AttributionReportingIssueType::kPermissionPolicyDisabled);
+    return nullptr;
+  }
+
+  if (!window->IsSecureContext()) {
+    maybe_log_audit_issue(AttributionReportingIssueType::kInsecureContext,
+                          window->GetSecurityContext().GetSecurityOrigin());
+    return nullptr;
+  }
+
   scoped_refptr<const SecurityOrigin> reporting_origin =
       SecurityOrigin::Create(url);
   if (!url.ProtocolIsInHTTPFamily() ||
       !reporting_origin->IsPotentiallyTrustworthy()) {
-    LogAuditIssue(window,
-                  AttributionReportingIssueType::kUntrustworthyReportingOrigin,
-                  element, request_id,
-                  /*invalid_parameter=*/reporting_origin->ToString());
+    maybe_log_audit_issue(
+        AttributionReportingIssueType::kUntrustworthyReportingOrigin,
+        reporting_origin.get());
     return nullptr;
   }
 
@@ -393,6 +379,13 @@
   return reporting_origin;
 }
 
+bool AttributionSrcLoader::CanRegister(const KURL& url,
+                                       HTMLElement* element,
+                                       absl::optional<uint64_t> request_id,
+                                       bool log_issues) {
+  return !!ReportingOriginForUrlIfValid(url, element, request_id, log_issues);
+}
+
 bool AttributionSrcLoader::MaybeRegisterAttributionHeaders(
     const ResourceRequest& request,
     const ResourceResponse& response,
diff --git a/third_party/blink/renderer/core/frame/attribution_src_loader.h b/third_party/blink/renderer/core/frame/attribution_src_loader.h
index 04cefcd..90f3e3ac 100644
--- a/third_party/blink/renderer/core/frame/attribution_src_loader.h
+++ b/third_party/blink/renderer/core/frame/attribution_src_loader.h
@@ -11,7 +11,6 @@
 #include "base/memory/scoped_refptr.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/renderer/core/core_export.h"
-#include "third_party/blink/renderer/core/inspector/inspector_audits_issue.h"
 #include "third_party/blink/renderer/platform/heap/forward.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/heap/member.h"
@@ -64,6 +63,18 @@
   absl::optional<Impression> RegisterNavigation(const KURL& attribution_src,
                                                 HTMLElement* element = nullptr);
 
+  // Returns true if `url` can be used as an attributionsrc: its scheme is HTTP
+  // or HTTPS, its origin is potentially trustworthy, the document's permission
+  // policy supports Attribution Reporting, the window's context is secure, and
+  // the Attribution Reporting runtime-enabled feature is enabled.
+  //
+  // Reports a DevTools issue using `element` and `request_id` otherwise, if
+  // `log_issues` is true.
+  [[nodiscard]] bool CanRegister(const KURL& url,
+                                 HTMLElement* element,
+                                 absl::optional<uint64_t> request_id,
+                                 bool log_issues = true);
+
   void Trace(Visitor* visitor) const;
 
   static constexpr size_t kMaxConcurrentRequests = 30;
@@ -80,11 +91,13 @@
 
   // Returns the reporting origin corresponding to `url` if its protocol is in
   // the HTTP family, its origin is potentially trustworthy, and attribution is
-  // allowed. Returns `nullptr` and reports a DevTools issue otherwise.
+  // allowed. Returns `nullptr` otherwise, and reports a DevTools issue using
+  // `element` and `request_id if `log_issues` is true.
   scoped_refptr<const SecurityOrigin> ReportingOriginForUrlIfValid(
       const KURL& url,
       HTMLElement* element,
-      absl::optional<uint64_t> request_id);
+      absl::optional<uint64_t> request_id,
+      bool log_issues = true);
 
   ResourceClient* CreateAndSendRequest(const KURL& src_url,
                                        HTMLElement* element,
@@ -95,15 +108,6 @@
   size_t num_resource_clients_ = 0;
 };
 
-// Returns whether attribution is allowed, and logs DevTools issues if
-// registration was attempted in a context that is not allowed.
-// `element` may be null.
-CORE_EXPORT bool CanRegisterAttributionInContext(
-    LocalFrame* frame,
-    HTMLElement* element,
-    absl::optional<uint64_t> request_id,
-    bool log_issues = true);
-
 }  // namespace blink
 
 #endif  // THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_ATTRIBUTION_SRC_LOADER_H_
diff --git a/third_party/blink/renderer/core/frame/local_dom_window.cc b/third_party/blink/renderer/core/frame/local_dom_window.cc
index cae474b..047a94f 100644
--- a/third_party/blink/renderer/core/frame/local_dom_window.cc
+++ b/third_party/blink/renderer/core/frame/local_dom_window.cc
@@ -2089,7 +2089,7 @@
   }
 
   WebWindowFeatures window_features =
-      GetWindowFeaturesFromString(features, entered_window);
+      GetWindowFeaturesFromString(features, entered_window, completed_url);
 
   // In fenced frames, we should always use `noopener`.
   if (GetFrame()->IsInFencedFrameTree()) {
diff --git a/third_party/blink/renderer/core/frame/local_frame_mojo_handler.cc b/third_party/blink/renderer/core/frame/local_frame_mojo_handler.cc
index 358ba6b..2ad89c64 100644
--- a/third_party/blink/renderer/core/frame/local_frame_mojo_handler.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_mojo_handler.cc
@@ -1161,8 +1161,8 @@
                            : GetCurrentCursorPositionInFrame(frame_);
 
       WebLocalFrameImpl::FromFrame(frame_)->FirstRectForCharacterRange(
-          base::checked_cast<unsigned>(start),
-          base::checked_cast<unsigned>(range.length()), rect);
+          base::checked_cast<uint32_t>(start),
+          base::checked_cast<uint32_t>(range.length()), rect);
     }
   }
 
diff --git a/third_party/blink/renderer/core/frame/pausable_script_executor.cc b/third_party/blink/renderer/core/frame/pausable_script_executor.cc
index 50802cd9d..34ba150b 100644
--- a/third_party/blink/renderer/core/frame/pausable_script_executor.cc
+++ b/third_party/blink/renderer/core/frame/pausable_script_executor.cc
@@ -144,26 +144,27 @@
  public:
   WebScriptExecutor(Vector<WebScriptSource>,
                     int32_t world_id,
-                    bool user_gesture);
+                    mojom::blink::UserActivationOption);
 
   Vector<v8::Local<v8::Value>> Execute(LocalDOMWindow*) override;
 
  private:
   Vector<WebScriptSource> sources_;
   int32_t world_id_;
-  bool user_gesture_;
+  mojom::blink::UserActivationOption user_gesture_;
 };
 
-WebScriptExecutor::WebScriptExecutor(Vector<WebScriptSource> sources,
-                                     int32_t world_id,
-                                     bool user_gesture)
+WebScriptExecutor::WebScriptExecutor(
+    Vector<WebScriptSource> sources,
+    int32_t world_id,
+    mojom::blink::UserActivationOption user_gesture)
     : sources_(std::move(sources)),
       world_id_(world_id),
       user_gesture_(user_gesture) {}
 
 Vector<v8::Local<v8::Value>> WebScriptExecutor::Execute(
     LocalDOMWindow* window) {
-  if (user_gesture_) {
+  if (user_gesture_ == mojom::blink::UserActivationOption::kActivate) {
     // TODO(mustaq): Need to make sure this is safe. https://crbug.com/1082273
     LocalFrame::NotifyUserActivation(
         window->GetFrame(),
@@ -284,7 +285,7 @@
     LocalDOMWindow* window,
     scoped_refptr<DOMWrapperWorld> world,
     Vector<WebScriptSource> sources,
-    bool user_gesture,
+    mojom::blink::UserActivationOption user_gesture,
     WebScriptExecutionCallback* callback)
     : PausableScriptExecutor(
           window,
@@ -302,7 +303,7 @@
     : ExecutionContextLifecycleObserver(window),
       script_state_(script_state),
       callback_(callback),
-      blocking_option_(kNonBlocking),
+      blocking_option_(mojom::blink::LoadEventBlockingOption::kDoNotBlock),
       executor_(executor) {
   CHECK(script_state_);
   CHECK(script_state_->ContextIsValid());
@@ -320,11 +321,12 @@
   PostExecuteAndDestroySelf(context);
 }
 
-void PausableScriptExecutor::RunAsync(BlockingOption blocking) {
+void PausableScriptExecutor::RunAsync(
+    mojom::blink::LoadEventBlockingOption blocking) {
   ExecutionContext* context = GetExecutionContext();
   DCHECK(context);
   blocking_option_ = blocking;
-  if (blocking_option_ == kOnloadBlocking)
+  if (blocking_option_ == mojom::blink::LoadEventBlockingOption::kBlock)
     To<LocalDOMWindow>(context)->document()->IncrementLoadEventDelayCount();
 
   PostExecuteAndDestroySelf(context);
@@ -353,21 +355,24 @@
   if (!script_state_->ContextIsValid())
     return;
 
-  if (wait_for_promise_) {
-    // Use a SelfKeepAlive to extend the lifetime of the PausableScriptExecutor
-    // while we wait for promises to settle. We don't just use a reference in
-    // the callback to PromiseAggregator to avoid a cycle with a GC root.
-    // Cleared in Dispose(), which is called when all promises settle or when
-    // the ExecutionContext is invalidated.
-    keep_alive_ = this;
-    MakeGarbageCollected<PromiseAggregator>(
-        script_state_, results,
-        WTF::Bind(&PausableScriptExecutor::HandleResults,
-                  WrapWeakPersistent(this)));
-    return;
-  }
+  switch (wait_for_promise_) {
+    case mojom::blink::PromiseResultOption::kAwait:
+      // Use a SelfKeepAlive to extend the lifetime of the
+      // PausableScriptExecutor while we wait for promises to settle. We don't
+      // just use a reference in the callback to PromiseAggregator to avoid a
+      // cycle with a GC root. Cleared in Dispose(), which is called when all
+      // promises settle or when the ExecutionContext is invalidated.
+      keep_alive_ = this;
+      MakeGarbageCollected<PromiseAggregator>(
+          script_state_, results,
+          WTF::Bind(&PausableScriptExecutor::HandleResults,
+                    WrapWeakPersistent(this)));
+      break;
 
-  HandleResults(results);
+    case mojom::blink::PromiseResultOption::kDoNotWait:
+      HandleResults(results);
+      break;
+  }
 }
 
 void PausableScriptExecutor::HandleResults(
@@ -379,7 +384,7 @@
 
   auto* window = To<LocalDOMWindow>(GetExecutionContext());
 
-  if (blocking_option_ == kOnloadBlocking)
+  if (blocking_option_ == mojom::blink::LoadEventBlockingOption::kBlock)
     window->document()->DecrementLoadEventDelayCount();
 
   if (callback_)
diff --git a/third_party/blink/renderer/core/frame/pausable_script_executor.h b/third_party/blink/renderer/core/frame/pausable_script_executor.h
index 0aaa752a..3c9b611 100644
--- a/third_party/blink/renderer/core/frame/pausable_script_executor.h
+++ b/third_party/blink/renderer/core/frame/pausable_script_executor.h
@@ -6,6 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_PAUSABLE_SCRIPT_EXECUTOR_H_
 
 #include "base/memory/scoped_refptr.h"
+#include "third_party/blink/public/mojom/script/script_evaluation_params.mojom-blink.h"
 #include "third_party/blink/public/web/web_script_source.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h"
@@ -26,8 +27,6 @@
     : public GarbageCollected<PausableScriptExecutor>,
       public ExecutionContextLifecycleObserver {
  public:
-  enum BlockingOption { kNonBlocking, kOnloadBlocking };
-
   static void CreateAndRun(LocalDOMWindow*,
                            v8::Local<v8::Context>,
                            v8::Local<v8::Function>,
@@ -48,7 +47,7 @@
   PausableScriptExecutor(LocalDOMWindow*,
                          scoped_refptr<DOMWrapperWorld>,
                          Vector<WebScriptSource>,
-                         bool,
+                         mojom::blink::UserActivationOption,
                          WebScriptExecutionCallback*);
   PausableScriptExecutor(LocalDOMWindow*,
                          ScriptState*,
@@ -57,12 +56,13 @@
   ~PausableScriptExecutor() override;
 
   void Run();
-  void RunAsync(BlockingOption);
+  void RunAsync(mojom::blink::LoadEventBlockingOption);
   void ContextDestroyed() override;
 
   void Trace(Visitor*) const override;
 
-  void set_wait_for_promise(bool wait_for_promise) {
+  void set_wait_for_promise(
+      mojom::blink::PromiseResultOption wait_for_promise) {
     wait_for_promise_ = wait_for_promise;
   }
 
@@ -75,14 +75,15 @@
 
   Member<ScriptState> script_state_;
   WebScriptExecutionCallback* callback_;
-  BlockingOption blocking_option_;
+  mojom::blink::LoadEventBlockingOption blocking_option_;
   TaskHandle task_handle_;
 
   Member<Executor> executor_;
 
   // Whether to wait for a promise to resolve, if the executed script evaluates
   // to a promise.
-  bool wait_for_promise_ = false;
+  mojom::blink::PromiseResultOption wait_for_promise_ =
+      mojom::blink::PromiseResultOption::kDoNotWait;
 
   // A keepalive used when waiting on promises to settle.
   SelfKeepAlive<PausableScriptExecutor> keep_alive_;
diff --git a/third_party/blink/renderer/core/frame/web_frame_test.cc b/third_party/blink/renderer/core/frame/web_frame_test.cc
index 487a8b84..731d6c5c 100644
--- a/third_party/blink/renderer/core/frame/web_frame_test.cc
+++ b/third_party/blink/renderer/core/frame/web_frame_test.cc
@@ -259,27 +259,33 @@
 
 // A helper function to execute the given `scripts` in the main world of the
 // specified `frame`.
-void ExecuteScriptsInMainWorld(WebLocalFrame* frame,
-                               base::span<const String> scripts,
-                               WebScriptExecutionCallback* callback,
-                               bool wait_for_promise = true,
-                               bool user_gesture = false) {
+void ExecuteScriptsInMainWorld(
+    WebLocalFrame* frame,
+    base::span<const String> scripts,
+    WebScriptExecutionCallback* callback,
+    mojom::blink::PromiseResultOption wait_for_promise =
+        mojom::blink::PromiseResultOption::kAwait,
+    mojom::blink::UserActivationOption user_gesture =
+        mojom::blink::UserActivationOption::kDoNotActivate) {
   Vector<WebScriptSource> sources;
   for (auto script : scripts)
     sources.push_back(WebScriptSource(script));
   frame->RequestExecuteScript(
       DOMWrapperWorld::kMainWorldId, sources, user_gesture,
-      WebLocalFrame::kSynchronous, callback, BackForwardCacheAware::kAllow,
-      wait_for_promise ? WebLocalFrame::PromiseBehavior::kAwait
-                       : WebLocalFrame::PromiseBehavior::kDontWait);
+      mojom::blink::EvaluationTiming::kSynchronous,
+      mojom::blink::LoadEventBlockingOption::kDoNotBlock, callback,
+      BackForwardCacheAware::kAllow, wait_for_promise);
 }
 
 // Same as above, but for a single script.
-void ExecuteScriptInMainWorld(WebLocalFrame* frame,
-                              String script_string,
-                              WebScriptExecutionCallback* callback,
-                              bool wait_for_promise = true,
-                              bool user_gesture = false) {
+void ExecuteScriptInMainWorld(
+    WebLocalFrame* frame,
+    String script_string,
+    WebScriptExecutionCallback* callback,
+    mojom::blink::PromiseResultOption wait_for_promise =
+        mojom::blink::PromiseResultOption::kAwait,
+    mojom::blink::UserActivationOption user_gesture =
+        mojom::blink::UserActivationOption::kDoNotActivate) {
   ExecuteScriptsInMainWorld(frame, base::make_span(&script_string, 1), callback,
                             wait_for_promise, user_gesture);
 }
@@ -655,7 +661,7 @@
       web_view_helper.LocalMainFrame()->MainWorldScriptContext());
   ExecuteScriptInMainWorld(web_view_helper.GetWebView()->MainFrameImpl(),
                            kScript, &callback_helper,
-                           /*wait_for_promise=*/false);
+                           mojom::blink::PromiseResultOption::kDoNotWait);
   RunPendingTasks();
   // Since the caller specified the script shouldn't wait for the promise to
   // be resolved, the callback should have completed normally and the result
@@ -747,7 +753,7 @@
   ScriptExecutionCallbackHelper callback_helper(
       web_view_helper.LocalMainFrame()->MainWorldScriptContext());
   ExecuteScriptsInMainWorld(web_view_helper.GetWebView()->MainFrameImpl(),
-                            scripts, &callback_helper, true);
+                            scripts, &callback_helper);
   RunPendingTasks();
   EXPECT_TRUE(callback_helper.DidComplete());
   EXPECT_EQ("hello", callback_helper.StringValueAt(0));
@@ -769,7 +775,7 @@
   ScriptExecutionCallbackHelper callback_helper(
       web_view_helper.LocalMainFrame()->MainWorldScriptContext());
   ExecuteScriptsInMainWorld(web_view_helper.GetWebView()->MainFrameImpl(),
-                            scripts, &callback_helper, true);
+                            scripts, &callback_helper);
   RunPendingTasks();
   EXPECT_FALSE(callback_helper.DidComplete());
 
@@ -804,7 +810,7 @@
   ScriptExecutionCallbackHelper callback_helper(
       web_view_helper.LocalMainFrame()->MainWorldScriptContext());
   ExecuteScriptsInMainWorld(web_view_helper.GetWebView()->MainFrameImpl(),
-                            scripts, &callback_helper, true);
+                            scripts, &callback_helper);
   RunPendingTasks();
 
   EXPECT_TRUE(callback_helper.DidComplete());
@@ -827,7 +833,7 @@
   ScriptExecutionCallbackHelper callback_helper(
       web_view_helper.LocalMainFrame()->MainWorldScriptContext());
   ExecuteScriptsInMainWorld(web_view_helper.GetWebView()->MainFrameImpl(),
-                            scripts, &callback_helper, true);
+                            scripts, &callback_helper);
   RunPendingTasks();
   EXPECT_TRUE(callback_helper.DidComplete());
   EXPECT_EQ("hello", callback_helper.StringValueAt(0));
@@ -1025,7 +1031,8 @@
     // user activation but without the delegation option.
     ExecuteScriptInMainWorld(web_view_helper.GetWebView()->MainFrameImpl(),
                              post_message_wo_request, &callback_helper,
-                             /*wait_for_promise=*/true, /*user_gesture=*/true);
+                             blink::mojom::PromiseResultOption::kAwait,
+                             blink::mojom::UserActivationOption::kActivate);
     RunPendingTasks();
     EXPECT_TRUE(callback_helper.DidComplete());
     EXPECT_FALSE(message_event_listener->DelegateCapability());
@@ -1034,7 +1041,8 @@
     // both user activation and the delegation option.
     ExecuteScriptInMainWorld(web_view_helper.GetWebView()->MainFrameImpl(),
                              post_message_w_payment_request, &callback_helper,
-                             /*wait_for_promise=*/true, /*user_gesture=*/true);
+                             blink::mojom::PromiseResultOption::kAwait,
+                             blink::mojom::UserActivationOption::kActivate);
     RunPendingTasks();
     EXPECT_TRUE(callback_helper.DidComplete());
     EXPECT_TRUE(message_event_listener->DelegateCapability());
@@ -1051,7 +1059,8 @@
     ExecuteScriptInMainWorld(web_view_helper.GetWebView()->MainFrameImpl(),
                              post_message_w_fullscreen_request,
                              &callback_helper,
-                             /*wait_for_promise=*/true, /*user_gesture=*/true);
+                             blink::mojom::PromiseResultOption::kAwait,
+                             blink::mojom::UserActivationOption::kActivate);
     RunPendingTasks();
     EXPECT_TRUE(callback_helper.DidComplete());
     EXPECT_TRUE(message_event_listener->DelegateCapability());
@@ -1066,7 +1075,8 @@
     // user activation and the delegation option for an unknown capability.
     ExecuteScriptInMainWorld(web_view_helper.GetWebView()->MainFrameImpl(),
                              post_message_w_unknown_request, &callback_helper,
-                             /*wait_for_promise=*/true, /*user_gesture=*/true);
+                             blink::mojom::PromiseResultOption::kAwait,
+                             blink::mojom::UserActivationOption::kActivate);
     RunPendingTasks();
     EXPECT_TRUE(callback_helper.DidComplete());
     EXPECT_FALSE(message_event_listener->DelegateCapability());
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
index e169c480..a051def3 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
@@ -38,6 +38,7 @@
 #include "base/debug/crash_logging.h"
 #include "base/debug/dump_without_crashing.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/numerics/safe_conversions.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
@@ -3605,9 +3606,11 @@
 
   for (const auto& ime_text_span : ime_text_spans) {
     gfx::Rect rect;
-    unsigned length = ime_text_span.end_offset - ime_text_span.start_offset;
-    focused_frame->FirstRectForCharacterRange(ime_text_span.start_offset,
-                                              length, rect);
+    auto length = base::checked_cast<wtf_size_t>(ime_text_span.end_offset -
+                                                 ime_text_span.start_offset);
+    focused_frame->FirstRectForCharacterRange(
+        base::checked_cast<wtf_size_t>(ime_text_span.start_offset), length,
+        rect);
 
     ime_text_spans_info.push_back(ui::mojom::blink::ImeTextSpanInfo::New(
         ime_text_span, widget_base_->BlinkSpaceToEnclosedDIPs(rect)));
diff --git a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
index 0fda041..d2a7456 100644
--- a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
@@ -1081,11 +1081,12 @@
 void WebLocalFrameImpl::RequestExecuteScript(
     int32_t world_id,
     base::span<const WebScriptSource> sources,
-    bool user_gesture,
-    ScriptExecutionType execution_type,
+    mojom::blink::UserActivationOption user_gesture,
+    mojom::blink::EvaluationTiming evaluation_timing,
+    mojom::blink::LoadEventBlockingOption blocking_option,
     WebScriptExecutionCallback* callback,
     BackForwardCacheAware back_forward_cache_aware,
-    PromiseBehavior promise_behavior) {
+    mojom::blink::PromiseResultOption promise_behavior) {
   DCHECK(GetFrame());
 
   scoped_refptr<DOMWrapperWorld> world;
@@ -1108,15 +1109,12 @@
   auto* executor = MakeGarbageCollected<PausableScriptExecutor>(
       GetFrame()->DomWindow(), std::move(world), std::move(script_sources),
       user_gesture, callback);
-  executor->set_wait_for_promise(promise_behavior == PromiseBehavior::kAwait);
-  switch (execution_type) {
-    case kAsynchronousBlockingOnload:
-      executor->RunAsync(PausableScriptExecutor::kOnloadBlocking);
+  executor->set_wait_for_promise(promise_behavior);
+  switch (evaluation_timing) {
+    case mojom::blink::EvaluationTiming::kAsynchronous:
+      executor->RunAsync(blocking_option);
       break;
-    case kAsynchronous:
-      executor->RunAsync(PausableScriptExecutor::kNonBlocking);
-      break;
-    case kSynchronous:
+    case mojom::blink::EvaluationTiming::kSynchronous:
       executor->Run();
       break;
   }
@@ -1262,8 +1260,8 @@
 }
 
 bool WebLocalFrameImpl::FirstRectForCharacterRange(
-    unsigned location,
-    unsigned length,
+    uint32_t location,
+    uint32_t length,
     gfx::Rect& rect_in_viewport) const {
   if ((location + length < location) && (location + length))
     length = 0;
diff --git a/third_party/blink/renderer/core/frame/web_local_frame_impl.h b/third_party/blink/renderer/core/frame/web_local_frame_impl.h
index 3377e3e3..3c12154d 100644
--- a/third_party/blink/renderer/core/frame/web_local_frame_impl.h
+++ b/third_party/blink/renderer/core/frame/web_local_frame_impl.h
@@ -54,6 +54,7 @@
 #include "third_party/blink/public/mojom/input/input_handler.mojom-blink-forward.h"
 #include "third_party/blink/public/mojom/page/widget.mojom-blink.h"
 #include "third_party/blink/public/mojom/portal/portal.mojom-blink-forward.h"
+#include "third_party/blink/public/mojom/script/script_evaluation_params.mojom-blink-forward.h"
 #include "third_party/blink/public/platform/web_file_system_type.h"
 #include "third_party/blink/public/web/web_history_commit_type.h"
 #include "third_party/blink/public/web/web_local_frame.h"
@@ -184,11 +185,12 @@
                                 WebScriptExecutionCallback*) override;
   void RequestExecuteScript(int32_t world_id,
                             base::span<const WebScriptSource> sources,
-                            bool user_gesture,
-                            ScriptExecutionType,
+                            mojom::blink::UserActivationOption,
+                            mojom::blink::EvaluationTiming,
+                            mojom::blink::LoadEventBlockingOption,
                             WebScriptExecutionCallback*,
                             BackForwardCacheAware back_forward_cache_aware,
-                            PromiseBehavior) override;
+                            mojom::blink::PromiseResultOption) override;
   void Alert(const WebString& message) override;
   bool Confirm(const WebString& message) override;
   WebString Prompt(const WebString& message,
@@ -198,8 +200,8 @@
   void UnmarkText() override;
   bool HasMarkedText() const override;
   WebRange MarkedRange() const override;
-  bool FirstRectForCharacterRange(unsigned location,
-                                  unsigned length,
+  bool FirstRectForCharacterRange(uint32_t location,
+                                  uint32_t length,
                                   gfx::Rect&) const override;
   bool ExecuteCommand(const WebString&) override;
   bool ExecuteCommand(const WebString&, const WebString& value) override;
diff --git a/third_party/blink/renderer/core/html/html_anchor_element.cc b/third_party/blink/renderer/core/html/html_anchor_element.cc
index 138b079..609d2ec3 100644
--- a/third_party/blink/renderer/core/html/html_anchor_element.cc
+++ b/third_party/blink/renderer/core/html/html_anchor_element.cc
@@ -537,8 +537,9 @@
     // If the impression could not be set, or if the value was null, mark that
     // the frame request is eligible for attribution by adding an impression.
     if (!frame_request.Impression() &&
-        CanRegisterAttributionInContext(frame, this,
-                                        /*request_id=*/absl::nullopt)) {
+        frame->GetAttributionSrcLoader()->CanRegister(
+            completed_url, this,
+            /*request_id=*/absl::nullopt)) {
       frame_request.SetImpression(blink::Impression());
     }
   }
diff --git a/third_party/blink/renderer/core/html/html_element.cc b/third_party/blink/renderer/core/html/html_element.cc
index c002234..2d6135a6 100644
--- a/third_party/blink/renderer/core/html/html_element.cc
+++ b/third_party/blink/renderer/core/html/html_element.cc
@@ -1743,8 +1743,7 @@
   if (!ParseColorWithLegacyRules(attribute_value, parsed_color))
     return;
 
-  style->SetProperty(property_id,
-                     *cssvalue::CSSColor::Create(parsed_color.Rgb()));
+  style->SetProperty(property_id, *cssvalue::CSSColor::Create(parsed_color));
 }
 
 LabelsNodeList* HTMLElement::labels() {
diff --git a/third_party/blink/renderer/core/html/html_hr_element.cc b/third_party/blink/renderer/core/html/html_hr_element.cc
index 77058e1..fcb36835 100644
--- a/third_party/blink/renderer/core/html/html_hr_element.cc
+++ b/third_party/blink/renderer/core/html/html_hr_element.cc
@@ -87,7 +87,7 @@
           style, CSSPropertyID::kBorderStyle, CSSValueID::kSolid);
 
       const cssvalue::CSSColor& dark_gray_value =
-          *cssvalue::CSSColor::Create(Color::kDarkGray.Rgb());
+          *cssvalue::CSSColor::Create(Color::kDarkGray);
       style->SetProperty(CSSPropertyID::kBorderColor, dark_gray_value);
       style->SetProperty(CSSPropertyID::kBackgroundColor, dark_gray_value);
     }
diff --git a/third_party/blink/renderer/core/html/parser/html_preload_scanner.cc b/third_party/blink/renderer/core/html/parser/html_preload_scanner.cc
index e3f05f6..0289f48 100644
--- a/third_party/blink/renderer/core/html/parser/html_preload_scanner.cc
+++ b/third_party/blink/renderer/core/html/parser/html_preload_scanner.cc
@@ -42,7 +42,6 @@
 #include "third_party/blink/renderer/core/css/parser/sizes_attribute_parser.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/execution_context/security_context.h"
-#include "third_party/blink/renderer/core/frame/attribution_src_loader.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/frame/viewport_data.h"
@@ -341,8 +340,7 @@
     if (scanner_type_ == ScannerType::kInsertion)
       request->SetFromInsertionScanner(true);
 
-    if (attributionsrc_attr_set_ &&
-        document_parameters.can_register_attribution) {
+    if (attributionsrc_attr_set_) {
       DCHECK(is_script || is_img);
       request->SetAttributionReportingEligibleImgOrScript(true);
     }
@@ -1189,12 +1187,6 @@
   }
   probe::GetDisabledImageTypes(document->GetExecutionContext(),
                                &disabled_image_types);
-
-  can_register_attribution =
-      CanRegisterAttributionInContext(document->Loader()->GetFrame(),
-                                      /*element=*/nullptr,
-                                      /*request_id=*/absl::nullopt,
-                                      /*log_issues=*/false);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/parser/html_preload_scanner.h b/third_party/blink/renderer/core/html/parser/html_preload_scanner.h
index ccd176c..35a3d54 100644
--- a/third_party/blink/renderer/core/html/parser/html_preload_scanner.h
+++ b/third_party/blink/renderer/core/html/parser/html_preload_scanner.h
@@ -92,7 +92,6 @@
   SubresourceIntegrity::IntegrityFeatures integrity_features;
   LocalFrame::LazyLoadImageSetting lazy_load_image_setting;
   HashSet<String> disabled_image_types;
-  bool can_register_attribution;
 };
 
 class TokenPreloadScanner {
diff --git a/third_party/blink/renderer/core/html/parser/html_preload_scanner_test.cc b/third_party/blink/renderer/core/html/parser/html_preload_scanner_test.cc
index 1bf19f9..8f67df6e 100644
--- a/third_party/blink/renderer/core/html/parser/html_preload_scanner_test.cc
+++ b/third_party/blink/renderer/core/html/parser/html_preload_scanner_test.cc
@@ -99,6 +99,7 @@
 
 struct AttributionSrcTestCase {
   bool use_secure_document_url;
+  const char* base_url;
   const char* input_html;
   const char* expected_header;
 };
@@ -247,9 +248,6 @@
     Resource* resource = preload_request_->Start(document);
     ASSERT_TRUE(resource);
 
-    EXPECT_EQ(!!expected_header,
-              preload_request_->IsAttributionReportingEligibleImgOrScript());
-
     EXPECT_EQ(expected_header, resource->GetResourceRequest().HttpHeaderField(
                                    http_names::kAttributionReportingEligible));
   }
@@ -428,7 +426,7 @@
   void Test(AttributionSrcTestCase test_case) {
     SCOPED_TRACE(test_case.input_html);
     HTMLMockHTMLResourcePreloader preloader(GetDocument().Url());
-    KURL base_url("http://example.test/");
+    KURL base_url(test_case.base_url);
     scanner_->AppendToEnd(String(test_case.input_html));
     std::unique_ptr<PendingPreloadData> preload_data = scanner_->Scan(base_url);
     preloader.TakePreloadData(std::move(preload_data));
@@ -1090,19 +1088,36 @@
 }
 
 TEST_F(HTMLPreloadScannerTest, testAttributionSrc) {
+  static constexpr bool kSecureDocumentUrl = true;
+  static constexpr bool kInsecureDocumentUrl = false;
+
+  static constexpr char kSecureBaseURL[] = "https://example.test";
+  static constexpr char kInsecureBaseURL[] = "http://example.test";
+
   AttributionSrcTestCase test_cases[] = {
       // Insecure context
-      {false, "<img src='/image' attributionsrc>", nullptr},
-      {false, "<script src='/script' attributionsrc></script>", nullptr},
+      {kInsecureDocumentUrl, kSecureBaseURL,
+       "<img src='/image' attributionsrc>", nullptr},
+      {kInsecureDocumentUrl, kSecureBaseURL,
+       "<script src='/script' attributionsrc></script>", nullptr},
       // No attributionsrc attribute
-      {true, "<img src='/image'>", nullptr},
-      {true, "<script src='/script'></script>", nullptr},
+      {kSecureDocumentUrl, kSecureBaseURL, "<img src='/image'>", nullptr},
+      {kSecureDocumentUrl, kSecureBaseURL, "<script src='/script'></script>",
+       nullptr},
       // Irrelevant element type
-      {true, "<video poster='/image' attributionsrc>", nullptr},
-      // Secure context, attributionsrc attribute
-      {true, "<img src='/image' attributionsrc>",
+      {kSecureDocumentUrl, kSecureBaseURL,
+       "<video poster='/image' attributionsrc>", nullptr},
+      // Not potentially trustworthy reporting origin
+      {kSecureDocumentUrl, kInsecureBaseURL,
+       "<img src='/image' attributionsrc>", nullptr},
+      {kSecureDocumentUrl, kInsecureBaseURL,
+       "<script src='/script' attributionsrc></script>", nullptr},
+      // Secure context, potentially trustworthy reporting origin,
+      // attributionsrc attribute
+      {kSecureDocumentUrl, kSecureBaseURL, "<img src='/image' attributionsrc>",
        kAttributionEligibleEventSourceAndTrigger},
-      {true, "<script src='/script' attributionsrc></script>",
+      {kSecureDocumentUrl, kSecureBaseURL,
+       "<script src='/script' attributionsrc></script>",
        kAttributionEligibleEventSourceAndTrigger},
   };
 
diff --git a/third_party/blink/renderer/core/html/parser/preload_request.cc b/third_party/blink/renderer/core/html/parser/preload_request.cc
index 244cbe7..129c4676 100644
--- a/third_party/blink/renderer/core/html/parser/preload_request.cc
+++ b/third_party/blink/renderer/core/html/parser/preload_request.cc
@@ -8,6 +8,7 @@
 #include "base/metrics/histogram_functions.h"
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/core/frame/attribution_src_loader.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"
@@ -109,7 +110,12 @@
 
   resource_request.SetFetchPriorityHint(fetch_priority_hint_);
 
-  if (is_attribution_reporting_eligible_img_or_script_) {
+  // Disable issue logging to avoid duplicates, since `CanRegister()` will be
+  // called again later.
+  if (is_attribution_reporting_eligible_img_or_script_ &&
+      document->domWindow()->GetFrame()->GetAttributionSrcLoader()->CanRegister(
+          url, /*element=*/nullptr,
+          /*request_id=*/absl::nullopt, /*log_issues=*/false)) {
     resource_request.SetHttpHeaderField(
         http_names::kAttributionReportingEligible,
         kAttributionEligibleEventSourceAndTrigger);
diff --git a/third_party/blink/renderer/core/html/plugin_document.cc b/third_party/blink/renderer/core/html/plugin_document.cc
index 98b26a4..adf2832 100644
--- a/third_party/blink/renderer/core/html/plugin_document.cc
+++ b/third_party/blink/renderer/core/html/plugin_document.cc
@@ -107,9 +107,8 @@
   auto* body = MakeGarbageCollected<HTMLBodyElement>(*GetDocument());
   body->setAttribute(html_names::kStyleAttr,
                      "height: 100%; width: 100%; overflow: hidden; margin: 0");
-  body->SetInlineStyleProperty(
-      CSSPropertyID::kBackgroundColor,
-      *cssvalue::CSSColor::Create(background_color_.Rgb()));
+  body->SetInlineStyleProperty(CSSPropertyID::kBackgroundColor,
+                               *cssvalue::CSSColor::Create(background_color_));
   root_element->AppendChild(body);
   if (IsStopped()) {
     // Possibly detached by a mutation event listener installed in
diff --git a/third_party/blink/renderer/core/html/track/text_track_container.cc b/third_party/blink/renderer/core/html/track/text_track_container.cc
index 333389a3..2a0939c 100644
--- a/third_party/blink/renderer/core/html/track/text_track_container.cc
+++ b/third_party/blink/renderer/core/html/track/text_track_container.cc
@@ -38,6 +38,7 @@
 #include "third_party/blink/renderer/core/layout/layout_video.h"
 #include "third_party/blink/renderer/core/resize_observer/resize_observer.h"
 #include "third_party/blink/renderer/core/resize_observer/resize_observer_entry.h"
+#include "ui/accessibility/accessibility_features.h"
 
 namespace blink {
 
@@ -222,7 +223,13 @@
     if (!cue->track() || !cue->track()->IsRendered() || !cue->IsActive())
       continue;
 
-    cue->UpdateDisplay(*this);
+    if (features::IsTextBasedAudioDescriptionEnabled() &&
+        cue->track()->IsSpokenKind()) {
+      cue->UpdateSpeech(*this);
+    } else {
+      cue->UpdateDisplay(*this);
+    }
+
     cue->UpdatePastAndFutureNodes(movie_time);
   }
 
diff --git a/third_party/blink/renderer/core/html/track/text_track_cue.h b/third_party/blink/renderer/core/html/track/text_track_cue.h
index bc11866..ae5b9ad 100644
--- a/third_party/blink/renderer/core/html/track/text_track_cue.h
+++ b/third_party/blink/renderer/core/html/track/text_track_cue.h
@@ -80,6 +80,9 @@
   // already been added.
   virtual void UpdateDisplay(HTMLDivElement& container) = 0;
 
+  // Vocalizes text that reaches this method.
+  virtual void UpdateSpeech(HTMLDivElement& container) = 0;
+
   // Marks the nodes of the display tree as past or future relative to
   // movieTime. If |updateDisplay| has not been called there is no display
   // tree and nothing is done.
diff --git a/third_party/blink/renderer/core/html/track/vtt/vtt_cue.cc b/third_party/blink/renderer/core/html/track/vtt/vtt_cue.cc
index 1c8886e..510ef64 100644
--- a/third_party/blink/renderer/core/html/track/vtt/vtt_cue.cc
+++ b/third_party/blink/renderer/core/html/track/vtt/vtt_cue.cc
@@ -746,6 +746,11 @@
   display_tree_->remove(ASSERT_NO_EXCEPTION);
 }
 
+void VTTCue::UpdateSpeech(HTMLDivElement& container) {
+  // TODO: handle vocalization
+  // Text to be vocalized can be accessed through the variable text_
+}
+
 void VTTCue::UpdateDisplay(HTMLDivElement& container) {
   DCHECK(track() && track()->IsRendered() && IsActive());
 
diff --git a/third_party/blink/renderer/core/html/track/vtt/vtt_cue.h b/third_party/blink/renderer/core/html/track/vtt/vtt_cue.h
index 764f97a..53000444 100644
--- a/third_party/blink/renderer/core/html/track/vtt/vtt_cue.h
+++ b/third_party/blink/renderer/core/html/track/vtt/vtt_cue.h
@@ -136,6 +136,7 @@
   DocumentFragment* getCueAsHTML();
 
   void UpdateDisplay(HTMLDivElement& container) override;
+  void UpdateSpeech(HTMLDivElement& container) override;
 
   void UpdatePastAndFutureNodes(double movie_time) override;
 
diff --git a/third_party/blink/renderer/core/loader/image_loader.cc b/third_party/blink/renderer/core/loader/image_loader.cc
index ef14123..f31294c 100644
--- a/third_party/blink/renderer/core/loader/image_loader.cc
+++ b/third_party/blink/renderer/core/loader/image_loader.cc
@@ -504,9 +504,9 @@
 
     if (IsA<HTMLImageElement>(GetElement()) &&
         GetElement()->FastHasAttribute(html_names::kAttributionsrcAttr) &&
-        CanRegisterAttributionInContext(frame,
-                                        To<HTMLImageElement>(GetElement()),
-                                        /*request_id=*/absl::nullopt)) {
+        frame->GetAttributionSrcLoader()->CanRegister(
+            url, To<HTMLImageElement>(GetElement()),
+            /*request_id=*/absl::nullopt)) {
       resource_request.SetHttpHeaderField(
           http_names::kAttributionReportingEligible,
           kAttributionEligibleEventSourceAndTrigger);
diff --git a/third_party/blink/renderer/core/loader/navigation_policy_test.cc b/third_party/blink/renderer/core/loader/navigation_policy_test.cc
index 6a58caa..56bfb3d 100644
--- a/third_party/blink/renderer/core/loader/navigation_policy_test.cc
+++ b/third_party/blink/renderer/core/loader/navigation_policy_test.cc
@@ -40,6 +40,7 @@
 #include "third_party/blink/renderer/core/events/current_input_event.h"
 #include "third_party/blink/renderer/core/events/mouse_event.h"
 #include "third_party/blink/renderer/core/page/create_window.h"
+#include "third_party/blink/renderer/platform/weborigin/kurl.h"
 
 namespace blink {
 
@@ -244,7 +245,7 @@
   for (const auto& test : kCases) {
     EXPECT_EQ(test.policy,
               NavigationPolicyForCreateWindow(GetWindowFeaturesFromString(
-                  test.feature_string, nullptr /* dom_window */)))
+                  test.feature_string, /*dom_window=*/nullptr, KURL())))
         << "Testing '" << test.feature_string << "'";
   }
 }
@@ -270,7 +271,7 @@
   for (const auto& test : kCases) {
     EXPECT_EQ(test.policy,
               NavigationPolicyForCreateWindow(GetWindowFeaturesFromString(
-                  test.feature_string, nullptr /* dom_window */)))
+                  test.feature_string, /*dom_window=*/nullptr, KURL())))
         << "Testing '" << test.feature_string << "'";
   }
 }
@@ -295,7 +296,7 @@
   for (const auto& test : kCases) {
     EXPECT_EQ(test.policy,
               NavigationPolicyForCreateWindow(GetWindowFeaturesFromString(
-                  test.feature_string, nullptr /* dom_window */)))
+                  test.feature_string, /*dom_window=*/nullptr, KURL())))
         << "Testing '" << test.feature_string << "'";
   }
 }
diff --git a/third_party/blink/renderer/core/page/create_window.cc b/third_party/blink/renderer/core/page/create_window.cc
index 02438d58..7e73ad64 100644
--- a/third_party/blink/renderer/core/page/create_window.cc
+++ b/third_party/blink/renderer/core/page/create_window.cc
@@ -66,7 +66,8 @@
 }
 
 WebWindowFeatures GetWindowFeaturesFromString(const String& feature_string,
-                                              LocalDOMWindow* dom_window) {
+                                              LocalDOMWindow* dom_window,
+                                              const KURL& url) {
   WebWindowFeatures window_features;
 
   bool attribution_reporting_enabled =
@@ -226,9 +227,10 @@
       // If the impression could not be set, or if the value was empty, mark
       // attribution eligibility by adding an impression.
       if (!window_features.impression &&
-          CanRegisterAttributionInContext(dom_window->GetFrame(),
-                                          /*element=*/nullptr,
-                                          /*request_id=*/absl::nullopt)) {
+          dom_window->GetFrame()->GetAttributionSrcLoader()->CanRegister(
+              url,
+              /*element=*/nullptr,
+              /*request_id=*/absl::nullopt)) {
         window_features.impression = blink::Impression();
       }
     }
diff --git a/third_party/blink/renderer/core/page/create_window.h b/third_party/blink/renderer/core/page/create_window.h
index 0f5e82ab..da595dc0 100644
--- a/third_party/blink/renderer/core/page/create_window.h
+++ b/third_party/blink/renderer/core/page/create_window.h
@@ -33,6 +33,7 @@
 
 namespace blink {
 class Frame;
+class KURL;
 class LocalDOMWindow;
 class LocalFrame;
 struct FrameLoadRequest;
@@ -43,7 +44,8 @@
 
 CORE_EXPORT WebWindowFeatures
 GetWindowFeaturesFromString(const String& feature_string,
-                            LocalDOMWindow* dom_window);
+                            LocalDOMWindow* dom_window,
+                            const KURL& url);
 
 }  // namespace blink
 
diff --git a/third_party/blink/renderer/core/page/window_features_test.cc b/third_party/blink/renderer/core/page/window_features_test.cc
index 4ea8b7c4..2c1e476 100644
--- a/third_party/blink/renderer/core/page/window_features_test.cc
+++ b/third_party/blink/renderer/core/page/window_features_test.cc
@@ -1,11 +1,13 @@
 // Copyright 2015 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
-//
+
 #include "third_party/blink/renderer/core/page/create_window.h"
 
 #include <gtest/gtest.h>
+
 #include "third_party/blink/public/web/web_window_features.h"
+#include "third_party/blink/renderer/platform/weborigin/kurl.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
 namespace blink {
@@ -28,9 +30,10 @@
   };
 
   for (const auto& test : kCases) {
-    EXPECT_EQ(test.noopener, GetWindowFeaturesFromString(
-                                 test.feature_string, nullptr /* dom_window */)
-                                 .noopener)
+    EXPECT_EQ(test.noopener,
+              GetWindowFeaturesFromString(test.feature_string,
+                                          /*dom_window=*/nullptr, KURL())
+                  .noopener)
         << "Testing '" << test.feature_string << "'";
   }
 }
@@ -61,7 +64,7 @@
   for (const auto& test : kCases) {
     EXPECT_EQ(test.noreferrer,
               GetWindowFeaturesFromString(test.feature_string,
-                                          nullptr /* dom_window */)
+                                          /*dom_window=*/nullptr, KURL())
                   .noreferrer)
         << "Testing '" << test.feature_string << "'";
   }
diff --git a/third_party/blink/renderer/core/paint/paint_layer.cc b/third_party/blink/renderer/core/paint/paint_layer.cc
index f834a23..5a1ebe5 100644
--- a/third_party/blink/renderer/core/paint/paint_layer.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer.cc
@@ -2138,30 +2138,6 @@
   return !clipper->HitTestClipContent(reference_box, location);
 }
 
-bool PaintLayer::IntersectsDamageRect(
-    const PhysicalRect& layer_bounds,
-    const PhysicalRect& damage_rect,
-    const PhysicalOffset& offset_from_root) const {
-  // Always examine the canvas and the root.
-  // FIXME: Could eliminate the isDocumentElement() check if we fix background
-  // painting so that the LayoutView paints the root's background.
-  if (IsRootLayer() || GetLayoutObject().IsDocumentElement())
-    return true;
-
-  // If we aren't an inline flow, and our layer bounds do intersect the damage
-  // rect, then we can go ahead and return true.
-  LayoutView* view = GetLayoutObject().View();
-  DCHECK(view);
-  if (view && !GetLayoutObject().IsLayoutInline()) {
-    if (layer_bounds.Intersects(damage_rect))
-      return true;
-  }
-
-  // Otherwise we need to compute the bounding box of this single layer and see
-  // if it intersects the damage rect.
-  return PhysicalBoundingBox(offset_from_root).Intersects(damage_rect);
-}
-
 PhysicalRect PaintLayer::LocalBoundingBox() const {
   PhysicalRect rect = GetLayoutObject().PhysicalVisualOverflowRect();
   if (GetLayoutObject().IsEffectiveRootScroller() || IsRootLayer()) {
@@ -2185,16 +2161,6 @@
   return result;
 }
 
-PhysicalRect PaintLayer::FragmentsBoundingBox(
-    const PaintLayer* ancestor_layer) const {
-  if (!EnclosingPaginationLayer())
-    return PhysicalBoundingBox(ancestor_layer);
-
-  PhysicalRect result = LocalBoundingBox();
-  ConvertFromFlowThreadToVisualBoundingBoxInAncestor(ancestor_layer, result);
-  return result;
-}
-
 void PaintLayer::ExpandRectForSelfPaintingDescendants(
     PhysicalRect& result) const {
   // If we're locked, then the subtree does not contribute painted output.
diff --git a/third_party/blink/renderer/core/paint/paint_layer.h b/third_party/blink/renderer/core/paint/paint_layer.h
index 3a53065..b96c387 100644
--- a/third_party/blink/renderer/core/paint/paint_layer.h
+++ b/third_party/blink/renderer/core/paint/paint_layer.h
@@ -340,15 +340,10 @@
                HitTestResult&,
                const PhysicalRect& hit_test_area);
 
-  bool IntersectsDamageRect(const PhysicalRect& layer_bounds,
-                            const PhysicalRect& damage_rect,
-                            const PhysicalOffset& offset_from_root) const;
-
   // Bounding box relative to some ancestor layer. Pass offsetFromRoot if known.
   PhysicalRect PhysicalBoundingBox(
       const PhysicalOffset& offset_from_root) const;
   PhysicalRect PhysicalBoundingBox(const PaintLayer* ancestor_layer) const;
-  PhysicalRect FragmentsBoundingBox(const PaintLayer* ancestor_layer) const;
 
   // Static position is set in parent's coordinate space.
   LayoutUnit StaticInlinePosition() const { return static_inline_position_; }
diff --git a/third_party/blink/renderer/core/scheduler_integration_tests/virtual_time_test.cc b/third_party/blink/renderer/core/scheduler_integration_tests/virtual_time_test.cc
index c8c86f6..693bd0c 100644
--- a/third_party/blink/renderer/core/scheduler_integration_tests/virtual_time_test.cc
+++ b/third_party/blink/renderer/core/scheduler_integration_tests/virtual_time_test.cc
@@ -48,10 +48,12 @@
     ScriptExecutionCallbackHelper callback_helper;
     WebScriptSource source(script_source);
     WebView().MainFrame()->ToWebLocalFrame()->RequestExecuteScript(
-        DOMWrapperWorld::kMainWorldId, base::make_span(&source, 1), false,
-        WebLocalFrame::kSynchronous, &callback_helper,
+        DOMWrapperWorld::kMainWorldId, base::make_span(&source, 1),
+        mojom::blink::UserActivationOption::kDoNotActivate,
+        mojom::blink::EvaluationTiming::kSynchronous,
+        mojom::blink::LoadEventBlockingOption::kDoNotBlock, &callback_helper,
         BackForwardCacheAware::kAllow,
-        WebLocalFrame::PromiseBehavior::kDontWait);
+        mojom::blink::PromiseResultOption::kDoNotWait);
 
     return callback_helper.Result();
   }
diff --git a/third_party/blink/renderer/core/script/script_loader.cc b/third_party/blink/renderer/core/script/script_loader.cc
index 34bef140..96148a1 100644
--- a/third_party/blink/renderer/core/script/script_loader.cc
+++ b/third_party/blink/renderer/core/script/script_loader.cc
@@ -545,23 +545,14 @@
       potentially_render_blocking ? RenderBlockingBehavior::kBlocking
                                   : RenderBlockingBehavior::kNonBlocking;
 
-  // TODO(apaseltiner): Propagate the element instead of passing nullptr.
-  const auto attribution_reporting_eligibility =
-      element_->HasAttributionsrcAttribute() &&
-              CanRegisterAttributionInContext(context_window->GetFrame(),
-                                              /*element=*/nullptr,
-                                              /*request_id=*/absl::nullopt)
-          ? ScriptFetchOptions::AttributionReportingEligibility::kEligible
-          : ScriptFetchOptions::AttributionReportingEligibility::kIneligible;
-
   // <spec step="27">Let options be a script fetch options whose cryptographic
   // nonce is cryptographic nonce, integrity metadata is integrity metadata,
   // parser metadata is parser metadata, credentials mode is module script
   // credentials mode, and referrer policy is referrer policy.</spec>
-  ScriptFetchOptions options(
-      nonce, integrity_metadata, integrity_attr, parser_state, credentials_mode,
-      referrer_policy, fetch_priority_hint, render_blocking_behavior,
-      RejectCoepUnsafeNone(false), attribution_reporting_eligibility);
+  ScriptFetchOptions options(nonce, integrity_metadata, integrity_attr,
+                             parser_state, credentials_mode, referrer_policy,
+                             fetch_priority_hint, render_blocking_behavior,
+                             RejectCoepUnsafeNone(false));
 
   // <spec step="28">Let settings object be el's node document's relevant
   // settings object.</spec>
@@ -647,6 +638,16 @@
       return nullptr;
     }
 
+    // TODO(apaseltiner): Propagate the element instead of passing nullptr.
+    if (element_->HasAttributionsrcAttribute() &&
+        context_window->GetFrame()->GetAttributionSrcLoader()->CanRegister(
+            url,
+            /*element=*/nullptr,
+            /*request_id=*/absl::nullopt)) {
+      options.SetAttributionReportingEligibility(
+          ScriptFetchOptions::AttributionReportingEligibility::kEligible);
+    }
+
     // <spec step="29.6">If el is potentially render-blocking, then block
     // rendering on el.</spec>
     if (potentially_render_blocking &&
diff --git a/third_party/blink/renderer/modules/credentialmanagement/credential_manager_type_converters.cc b/third_party/blink/renderer/modules/credentialmanagement/credential_manager_type_converters.cc
index 4b4f6a3..b158b7f 100644
--- a/third_party/blink/renderer/modules/credentialmanagement/credential_manager_type_converters.cc
+++ b/third_party/blink/renderer/modules/credentialmanagement/credential_manager_type_converters.cc
@@ -20,6 +20,7 @@
 #include "third_party/blink/renderer/bindings/modules/v8/v8_cable_authentication_data.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_cable_registration_data.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_identity_credential_logout_r_ps_request.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_identity_provider.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_public_key_credential_creation_options.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_public_key_credential_descriptor.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_public_key_credential_parameters.h"
@@ -49,6 +50,8 @@
 using blink::mojom::blink::CredentialInfo;
 using blink::mojom::blink::CredentialInfoPtr;
 using blink::mojom::blink::CredentialType;
+using blink::mojom::blink::IdentityProvider;
+using blink::mojom::blink::IdentityProviderPtr;
 using blink::mojom::blink::LargeBlobSupport;
 using blink::mojom::blink::LogoutRpsRequest;
 using blink::mojom::blink::LogoutRpsRequestPtr;
@@ -691,4 +694,16 @@
       blink_value.sameOriginWithAncestors());
 }
 
+// static
+IdentityProviderPtr
+TypeConverter<IdentityProviderPtr, blink::IdentityProvider>::Convert(
+    const blink::IdentityProvider& provider) {
+  auto mojo_provider = IdentityProvider::New();
+
+  mojo_provider->config_url = blink::KURL(provider.configURL());
+  mojo_provider->client_id = provider.clientId();
+  mojo_provider->nonce = provider.nonce();
+  return mojo_provider;
+}
+
 }  // namespace mojo
diff --git a/third_party/blink/renderer/modules/credentialmanagement/credential_manager_type_converters.h b/third_party/blink/renderer/modules/credentialmanagement/credential_manager_type_converters.h
index 751d070..6b320da1ca 100644
--- a/third_party/blink/renderer/modules/credentialmanagement/credential_manager_type_converters.h
+++ b/third_party/blink/renderer/modules/credentialmanagement/credential_manager_type_converters.h
@@ -20,6 +20,7 @@
 class CableRegistrationData;
 class Credential;
 class IdentityCredentialLogoutRPsRequest;
+class IdentityProvider;
 class PublicKeyCredentialCreationOptions;
 class PublicKeyCredentialDescriptor;
 class PublicKeyCredentialParameters;
@@ -190,6 +191,13 @@
       const blink::RemoteDesktopClientOverride&);
 };
 
+template <>
+struct TypeConverter<blink::mojom::blink::IdentityProviderPtr,
+                     blink::IdentityProvider> {
+  static blink::mojom::blink::IdentityProviderPtr Convert(
+      const blink::IdentityProvider&);
+};
+
 }  // namespace mojo
 
 #endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_CREDENTIALMANAGEMENT_CREDENTIAL_MANAGER_TYPE_CONVERTERS_H_
diff --git a/third_party/blink/renderer/modules/credentialmanagement/credentials_container.cc b/third_party/blink/renderer/modules/credentialmanagement/credentials_container.cc
index bea385e6..c0bea7dc 100644
--- a/third_party/blink/renderer/modules/credentialmanagement/credentials_container.cc
+++ b/third_party/blink/renderer/modules/credentialmanagement/credentials_container.cc
@@ -1210,7 +1210,6 @@
     // Some of this has not been spec'd yet.
     KURL provider_url(provider->configURL());
     String client_id = provider->clientId();
-    String nonce = provider->getNonceOr("");
 
     if (!provider_url.IsValid() || client_id == "") {
       resolver->Reject(MakeGarbageCollected<DOMException>(
@@ -1235,12 +1234,14 @@
       options->signal()->AddAlgorithm(WTF::Bind(&AbortIdentityCredentialRequest,
                                                 WrapPersistent(script_state)));
     }
+    mojom::blink::IdentityProviderPtr identity_provider =
+        blink::mojom::blink::IdentityProvider::From(*provider);
     bool prefer_auto_sign_in = options->identity()->preferAutoSignIn();
     auto* auth_request =
         CredentialManagerProxy::From(script_state)->FederatedAuthRequest();
 
     auth_request->RequestToken(
-        provider_url, client_id, nonce, prefer_auto_sign_in,
+        std::move(identity_provider), prefer_auto_sign_in,
         WTF::Bind(&OnRequestToken, WrapPersistent(resolver), provider_url,
                   client_id, WrapPersistent(options)));
 
diff --git a/third_party/blink/renderer/modules/image_downloader/image_downloader_impl.cc b/third_party/blink/renderer/modules/image_downloader/image_downloader_impl.cc
index e30f7ae5..db9b9d38 100644
--- a/third_party/blink/renderer/modules/image_downloader/image_downloader_impl.cc
+++ b/third_party/blink/renderer/modules/image_downloader/image_downloader_impl.cc
@@ -8,7 +8,6 @@
 
 #include "base/bind.h"
 #include "base/check.h"
-#include "base/numerics/safe_conversions.h"
 #include "skia/ext/image_operations.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
 #include "third_party/blink/public/platform/interface_registry.h"
@@ -59,12 +58,29 @@
   return DecodeImageData(data, mime_type, preferred_size);
 }
 
-// Images are allowed only if the following criteria is true:
-// If |max_image_size| == 0, denoting that no upper limit is provided for image
-// size.
-// If size of image (width and height) is <= max_image_size.
-// For all other cases, the image is filtered out.
-void FilterImagesBasedOnMaximalSize(
+//  Proportionally resizes the |image| to fit in a box of size
+// |max_image_size|.
+SkBitmap ResizeImage(const SkBitmap& image, uint32_t max_image_size) {
+  if (max_image_size == 0)
+    return image;
+  uint32_t max_dimension = std::max(image.width(), image.height());
+  if (max_dimension <= max_image_size)
+    return image;
+  // Proportionally resize the minimal image to fit in a box of size
+  // max_image_size.
+  return skia::ImageOperations::Resize(
+      image, skia::ImageOperations::RESIZE_BEST,
+      static_cast<uint32_t>(image.width()) * max_image_size / max_dimension,
+      static_cast<uint32_t>(image.height()) * max_image_size / max_dimension);
+}
+
+// Filters the array of bitmaps, removing all images that do not fit in a box of
+// size |max_image_size|. Returns the result if it is not empty. Otherwise,
+// find the smallest image in the array and resize it proportionally to fit
+// in a box of size |max_image_size|.
+// Sets |original_image_sizes| to the sizes of |images| before resizing. Both
+// output vectors are guaranteed to have the same size.
+void FilterAndResizeImagesForMaximalSize(
     const WTF::Vector<SkBitmap>& unfiltered,
     uint32_t max_image_size,
     WTF::Vector<SkBitmap>* images,
@@ -75,14 +91,38 @@
   if (unfiltered.IsEmpty())
     return;
 
-  for (const SkBitmap& image : unfiltered) {
-    if ((max_image_size == 0) ||
-        (base::checked_cast<uint32_t>(image.width()) <= max_image_size &&
-         base::checked_cast<uint32_t>(image.height()) <= max_image_size)) {
+  if (max_image_size == 0)
+    max_image_size = std::numeric_limits<uint32_t>::max();
+
+  const SkBitmap* min_image = nullptr;
+  uint32_t min_image_size = std::numeric_limits<uint32_t>::max();
+  // Filter the images by |max_image_size|, and also identify the smallest image
+  // in case all the images are bigger than |max_image_size|.
+  for (auto* it = unfiltered.begin(); it != unfiltered.end(); ++it) {
+    const SkBitmap& image = *it;
+    uint32_t current_size = std::max(it->width(), it->height());
+    if (current_size < min_image_size) {
+      min_image = &image;
+      min_image_size = current_size;
+    }
+    if (static_cast<uint32_t>(image.width()) <= max_image_size &&
+        static_cast<uint32_t>(image.height()) <= max_image_size) {
       images->push_back(image);
       original_image_sizes->push_back(gfx::Size(image.width(), image.height()));
     }
   }
+  DCHECK(min_image);
+  if (images->size())
+    return;
+  // Proportionally resize the minimal image to fit in a box of size
+  // |max_image_size|.
+  SkBitmap resized = ResizeImage(*min_image, max_image_size);
+  // Drop null or empty SkBitmap.
+  if (resized.drawsNothing())
+    return;
+  images->push_back(resized);
+  original_image_sizes->push_back(
+      gfx::Size(min_image->width(), min_image->height()));
 }
 
 }  // namespace
@@ -163,8 +203,8 @@
     const WTF::Vector<SkBitmap>& images) {
   WTF::Vector<SkBitmap> result_images;
   WTF::Vector<gfx::Size> result_original_image_sizes;
-  FilterImagesBasedOnMaximalSize(images, max_image_size, &result_images,
-                                 &result_original_image_sizes);
+  FilterAndResizeImagesForMaximalSize(images, max_image_size, &result_images,
+                                      &result_original_image_sizes);
 
   DCHECK_EQ(result_images.size(), result_original_image_sizes.size());
 
diff --git a/third_party/blink/renderer/modules/media_controls/elements/media_control_text_track_list_element.cc b/third_party/blink/renderer/modules/media_controls/elements/media_control_text_track_list_element.cc
index efb6d14..793c9a64 100644
--- a/third_party/blink/renderer/modules/media_controls/elements/media_control_text_track_list_element.cc
+++ b/third_party/blink/renderer/modules/media_controls/elements/media_control_text_track_list_element.cc
@@ -161,7 +161,12 @@
     if (track->kind() == track->CaptionsKeyword()) {
       track_kind_marker->SetShadowPseudoId(AtomicString(
           "-internal-media-controls-text-track-list-kind-captions"));
+    } else if (track->kind() == track->DescriptionsKeyword()) {
+      track_kind_marker->SetShadowPseudoId(AtomicString(
+          "-internal-media-controls-text-track-list-kind-descriptions"));
     } else {
+      // Aside from Captions and Descriptions, Subtitles is the only other
+      // supported keyword.
       DCHECK_EQ(track->kind(), track->SubtitlesKeyword());
       track_kind_marker->SetShadowPseudoId(AtomicString(
           "-internal-media-controls-text-track-list-kind-subtitles"));
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index 209333a..12e173e 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -1008,6 +1008,7 @@
     "graphics/paint/paint_canvas.h",
     "graphics/paint/paint_chunk.cc",
     "graphics/paint/paint_chunk.h",
+    "graphics/paint/paint_chunk_subset.cc",
     "graphics/paint/paint_chunk_subset.h",
     "graphics/paint/paint_chunker.cc",
     "graphics/paint/paint_chunker.h",
diff --git a/third_party/blink/renderer/platform/exported/web_runtime_features.cc b/third_party/blink/renderer/platform/exported/web_runtime_features.cc
index 23353f7..323bfe6d 100644
--- a/third_party/blink/renderer/platform/exported/web_runtime_features.cc
+++ b/third_party/blink/renderer/platform/exported/web_runtime_features.cc
@@ -647,6 +647,10 @@
   RuntimeEnabledFeatures::SetFedCmIframeSupportEnabled(enable);
 }
 
+void WebRuntimeFeatures::EnableFedCmMultipleIdentityProviders(bool enable) {
+  RuntimeEnabledFeatures::SetFedCmMultipleIdentityProvidersEnabled(enable);
+}
+
 void WebRuntimeFeatures::EnableDocumentTransition(bool enable) {
   RuntimeEnabledFeatures::SetDocumentTransitionEnabled(enable);
 }
diff --git a/third_party/blink/renderer/platform/fonts/font_custom_platform_data.cc b/third_party/blink/renderer/platform/fonts/font_custom_platform_data.cc
index 56d57e3..8df0ae27 100644
--- a/third_party/blink/renderer/platform/fonts/font_custom_platform_data.cc
+++ b/third_party/blink/renderer/platform/fonts/font_custom_platform_data.cc
@@ -83,10 +83,9 @@
 
 FontCustomPlatformData::~FontCustomPlatformData() = default;
 
-// TODO(crbug.com/1205794): Optical sizing should use specified size, instead of
-// zoomed size.
 FontPlatformData FontCustomPlatformData::GetFontPlatformData(
     float size,
+    float adjusted_specified_size,
     bool bold,
     bool italic,
     const FontSelectionRequest& selection_request,
@@ -189,7 +188,7 @@
     if (!explicit_opsz_configured) {
       if (optical_sizing == kAutoOpticalSizing) {
         SkFontArguments::VariationPosition::Coordinate opsz_coordinate = {
-            kOpszTag, SkFloatToScalar(size)};
+            kOpszTag, SkFloatToScalar(adjusted_specified_size)};
         variation.push_back(opsz_coordinate);
       } else if (optical_sizing == kNoneOpticalSizing) {
         // Explicitly set default value to avoid automatic application of
diff --git a/third_party/blink/renderer/platform/fonts/font_custom_platform_data.h b/third_party/blink/renderer/platform/fonts/font_custom_platform_data.h
index 6a5efce6..e1eeb46 100644
--- a/third_party/blink/renderer/platform/fonts/font_custom_platform_data.h
+++ b/third_party/blink/renderer/platform/fonts/font_custom_platform_data.h
@@ -63,8 +63,13 @@
   FontCustomPlatformData& operator=(const FontCustomPlatformData&) = delete;
   ~FontCustomPlatformData();
 
+  // The size argument should come from EffectiveFontSize() and
+  // adjusted_specified_size should come from AdjustedSpecifiedSize() of
+  // FontDescription. The latter is needed for correctly applying
+  // font-optical-sizing: auto; independent of zoom level.
   FontPlatformData GetFontPlatformData(
       float size,
+      float adjusted_specified_size,
       bool bold,
       bool italic,
       const FontSelectionRequest&,
diff --git a/third_party/blink/renderer/platform/fonts/font_description.cc b/third_party/blink/renderer/platform/fonts/font_description.cc
index dd2dfc0..d59e4b24 100644
--- a/third_party/blink/renderer/platform/fonts/font_description.cc
+++ b/third_party/blink/renderer/platform/fonts/font_description.cc
@@ -235,6 +235,14 @@
          FontCacheKey::PrecisionMultiplier();
 }
 
+float FontDescription::AdjustedSpecifiedSize() const {
+  if (HasSizeAdjust() || fields_.has_size_adjust_descriptor_) {
+    return SpecifiedSize() * (AdjustedSize() / ComputedSize());
+  } else {
+    return SpecifiedSize();
+  }
+}
+
 FontDescription FontDescription::SizeAdjustedFontDescription(
     float size_adjust) const {
   // TODO(crbug.com/451346): The font-size-adjust property and size-adjust
diff --git a/third_party/blink/renderer/platform/fonts/font_description.h b/third_party/blink/renderer/platform/fonts/font_description.h
index adcd990e..9167d5d 100644
--- a/third_party/blink/renderer/platform/fonts/font_description.h
+++ b/third_party/blink/renderer/platform/fonts/font_description.h
@@ -186,6 +186,11 @@
     return Size(KeywordSize(), SpecifiedSize(), IsAbsoluteSize());
   }
   float SpecifiedSize() const { return specified_size_; }
+  // Returns the result of applying font-size-adjust to the specified size. This
+  // is useful as an input to optical sizing and takes zooming out of the
+  // equation for determining the font size to be used for font-optical-sizing:
+  // auto;.
+  float AdjustedSpecifiedSize() const;
   float ComputedSize() const { return computed_size_; }
 
   // TODO(xiaochengh): The functions and members for size-adjust descriptor and
diff --git a/third_party/blink/renderer/platform/graphics/compositing/pending_layer.cc b/third_party/blink/renderer/platform/graphics/compositing/pending_layer.cc
index a86d04f93..5949d54a 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/pending_layer.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/pending_layer.cc
@@ -140,16 +140,7 @@
   result->SetObject("property_tree_state", GetPropertyTreeState().ToJSON());
   result->SetArray("offset_of_decomposited_transforms",
                    VectorAsJSONArray(offset_of_decomposited_transforms_));
-  std::unique_ptr<JSONArray> json_chunks = std::make_unique<JSONArray>();
-  for (auto it = chunks_.begin(); it != chunks_.end(); ++it) {
-    StringBuilder sb;
-    sb.Append("index=");
-    sb.AppendNumber(it.IndexInPaintArtifact());
-    sb.Append(" ");
-    sb.Append(it->ToString(chunks_.GetPaintArtifact()));
-    json_chunks->PushString(sb.ToString());
-  }
-  result->SetArray("paint_chunks", std::move(json_chunks));
+  result->SetArray("paint_chunks", chunks_.ToJSON());
   result->SetBoolean("draws_content", DrawsContent());
   return result;
 }
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_artifact.cc b/third_party/blink/renderer/platform/graphics/paint/paint_artifact.cc
index 380b071..b4909dc 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_artifact.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_artifact.cc
@@ -62,4 +62,38 @@
   return id.ToString();
 }
 
+std::unique_ptr<JSONArray> PaintArtifact::ToJSON() const {
+  auto json = std::make_unique<JSONArray>();
+  AppendChunksAsJSON(0, chunks_.size(), *json, 0);
+  return json;
+}
+
+void PaintArtifact::AppendChunksAsJSON(wtf_size_t start_chunk_index,
+                                       wtf_size_t end_chunk_index,
+                                       JSONArray& json_array,
+                                       unsigned flags) const {
+  DCHECK_GT(end_chunk_index, start_chunk_index);
+  for (auto i = start_chunk_index; i < end_chunk_index; ++i) {
+    const auto& chunk = chunks_[i];
+    auto json_object = std::make_unique<JSONObject>();
+
+    json_object->SetString("chunk", ClientDebugName(chunk.id.client_id) + " " +
+                                        chunk.id.ToString(*this));
+    json_object->SetString("state", chunk.properties.ToString());
+    json_object->SetString("bounds", String(chunk.bounds.ToString()));
+#if DCHECK_IS_ON()
+    if (flags & DisplayItemList::kShowPaintRecords)
+      json_object->SetString("chunkData", chunk.ToString(*this));
+    json_object->SetArray("displayItems", DisplayItemList::DisplayItemsAsJSON(
+                                              *this, chunk.begin_index,
+                                              DisplayItemsInChunk(i), flags));
+#endif
+    json_array.PushObject(std::move(json_object));
+  }
+}
+
+std::ostream& operator<<(std::ostream& os, const PaintArtifact& artifact) {
+  return os << artifact.ToJSON()->ToPrettyJSONString().Utf8();
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_artifact.h b/third_party/blink/renderer/platform/graphics/paint/paint_artifact.h
index 0cb3b06..29fe1b1 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_artifact.h
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_artifact.h
@@ -67,6 +67,12 @@
   DOMNodeId ClientOwnerNodeId(DisplayItemClientId) const;
   String IdAsString(const DisplayItem::Id& id) const;
 
+  std::unique_ptr<JSONArray> ToJSON() const;
+  void AppendChunksAsJSON(wtf_size_t start_chunk_index,
+                          wtf_size_t end_chunk_index,
+                          JSONArray&,
+                          unsigned flags) const;
+
  private:
   struct ClientDebugInfo {
     String name;
@@ -80,6 +86,8 @@
   DebugInfo debug_info_;
 };
 
+PLATFORM_EXPORT std::ostream& operator<<(std::ostream&, const PaintArtifact&);
+
 }  // namespace blink
 
 #endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_PAINT_PAINT_ARTIFACT_H_
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_chunk_subset.cc b/third_party/blink/renderer/platform/graphics/paint/paint_chunk_subset.cc
new file mode 100644
index 0000000..45e9fe6
--- /dev/null
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_chunk_subset.cc
@@ -0,0 +1,28 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/platform/graphics/paint/paint_chunk_subset.h"
+
+#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
+
+namespace blink {
+
+std::unique_ptr<JSONArray> PaintChunkSubset::ToJSON() const {
+  auto json = std::make_unique<JSONArray>();
+  for (auto it = begin(); it != end(); ++it) {
+    StringBuilder sb;
+    sb.Append("index=");
+    sb.AppendNumber(it.IndexInPaintArtifact());
+    sb.Append(" ");
+    sb.Append(it->ToString(GetPaintArtifact()));
+    json->PushString(sb.ToString());
+  }
+  return json;
+}
+
+std::ostream& operator<<(std::ostream& os, const PaintChunkSubset& subset) {
+  return os << subset.ToJSON()->ToPrettyJSONString().Utf8();
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_chunk_subset.h b/third_party/blink/renderer/platform/graphics/paint/paint_chunk_subset.h
index 08719c3..38e7f62 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_chunk_subset.h
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_chunk_subset.h
@@ -149,6 +149,8 @@
     return sizeof(*this) + subset_indices_.CapacityInBytes();
   }
 
+  std::unique_ptr<JSONArray> ToJSON() const;
+
  private:
   bool UsesSubsetIndices() const { return begin_index_ == kNotFound; }
 
@@ -162,6 +164,9 @@
 
 using PaintChunkIterator = PaintChunkSubset::Iterator;
 
+PLATFORM_EXPORT std::ostream& operator<<(std::ostream&,
+                                         const PaintChunkSubset&);
+
 }  // namespace blink
 
 #endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_PAINT_PAINT_CHUNK_SUBSET_H_
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_controller_debug_data.cc b/third_party/blink/renderer/platform/graphics/paint/paint_controller_debug_data.cc
index d3c0b5b..e910b43 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_controller_debug_data.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_controller_debug_data.cc
@@ -32,7 +32,6 @@
  private:
   std::unique_ptr<JSONObject> SubsequenceAsJSONObjectRecursive();
   std::unique_ptr<JSONArray> ChunksAsJSONArrayRecursive(wtf_size_t, wtf_size_t);
-  void AppendChunksAsJSON(wtf_size_t, wtf_size_t, JSONArray&);
 
   const PaintArtifact& artifact_;
   const Vector<SubsequenceMarkers>& subsequences_;
@@ -48,9 +47,9 @@
   auto json_object = std::make_unique<JSONObject>();
 
   json_object->SetString(
-      "subsequence",
-      String::Format("client: 0x%" PRIuPTR " ", subsequence.client_id) +
-          artifact_.ClientDebugName(subsequence.client_id));
+      "subsequence", String::Format("client: %p ", reinterpret_cast<void*>(
+                                                       subsequence.client_id)) +
+                         artifact_.ClientDebugName(subsequence.client_id));
   json_object->SetArray(
       "chunks", ChunksAsJSONArrayRecursive(subsequence.start_chunk_index,
                                            subsequence.end_chunk_index));
@@ -76,44 +75,20 @@
     DCHECK_GE(subsequence.start_chunk_index, chunk_index);
     DCHECK_LE(subsequence.end_chunk_index, end_chunk_index);
 
-    if (chunk_index < subsequence.start_chunk_index)
-      AppendChunksAsJSON(chunk_index, subsequence.start_chunk_index, *array);
+    if (chunk_index < subsequence.start_chunk_index) {
+      artifact_.AppendChunksAsJSON(chunk_index, subsequence.start_chunk_index,
+                                   *array, flags_);
+    }
     array->PushObject(SubsequenceAsJSONObjectRecursive());
     chunk_index = subsequence.end_chunk_index;
   }
 
   if (chunk_index < end_chunk_index)
-    AppendChunksAsJSON(chunk_index, end_chunk_index, *array);
+    artifact_.AppendChunksAsJSON(chunk_index, end_chunk_index, *array, flags_);
 
   return array;
 }
 
-void PaintController::PaintArtifactAsJSON::AppendChunksAsJSON(
-    wtf_size_t start_chunk_index,
-    wtf_size_t end_chunk_index,
-    JSONArray& json_array) {
-  DCHECK_GT(end_chunk_index, start_chunk_index);
-  for (auto i = start_chunk_index; i < end_chunk_index; ++i) {
-    const auto& chunk = artifact_.PaintChunks()[i];
-    auto json_object = std::make_unique<JSONObject>();
-
-    json_object->SetString("chunk",
-                           artifact_.ClientDebugName(chunk.id.client_id) + " " +
-                               chunk.id.ToString(artifact_));
-    json_object->SetString("state", chunk.properties.ToString());
-    json_object->SetString("bounds", String(chunk.bounds.ToString()));
-    if (flags_ & DisplayItemList::kShowPaintRecords)
-      json_object->SetString("chunkData", chunk.ToString(artifact_));
-
-    json_object->SetArray("displayItems",
-                          DisplayItemList::DisplayItemsAsJSON(
-                              artifact_, chunk.begin_index,
-                              artifact_.DisplayItemsInChunk(i), flags_));
-
-    json_array.PushObject(std::move(json_object));
-  }
-}
-
 void PaintController::ShowDebugDataInternal(
     DisplayItemList::JsonFlags flags) const {
   auto current_list_flags = flags;
diff --git a/third_party/blink/renderer/platform/loader/fetch/script_fetch_options.h b/third_party/blink/renderer/platform/loader/fetch/script_fetch_options.h
index 670ef46..ad7cc90 100644
--- a/third_party/blink/renderer/platform/loader/fetch/script_fetch_options.h
+++ b/third_party/blink/renderer/platform/loader/fetch/script_fetch_options.h
@@ -46,19 +46,16 @@
         referrer_policy_(network::mojom::ReferrerPolicy::kDefault),
         fetch_priority_hint_(mojom::blink::FetchPriorityHint::kAuto) {}
 
-  ScriptFetchOptions(
-      const String& nonce,
-      const IntegrityMetadataSet& integrity_metadata,
-      const String& integrity_attribute,
-      ParserDisposition parser_state,
-      network::mojom::CredentialsMode credentials_mode,
-      network::mojom::ReferrerPolicy referrer_policy,
-      mojom::blink::FetchPriorityHint fetch_priority_hint,
-      RenderBlockingBehavior render_blocking_behavior,
-      RejectCoepUnsafeNone reject_coep_unsafe_none =
-          RejectCoepUnsafeNone(false),
-      AttributionReportingEligibility attribution_reporting_eligibility =
-          AttributionReportingEligibility::kIneligible)
+  ScriptFetchOptions(const String& nonce,
+                     const IntegrityMetadataSet& integrity_metadata,
+                     const String& integrity_attribute,
+                     ParserDisposition parser_state,
+                     network::mojom::CredentialsMode credentials_mode,
+                     network::mojom::ReferrerPolicy referrer_policy,
+                     mojom::blink::FetchPriorityHint fetch_priority_hint,
+                     RenderBlockingBehavior render_blocking_behavior,
+                     RejectCoepUnsafeNone reject_coep_unsafe_none =
+                         RejectCoepUnsafeNone(false))
       : nonce_(nonce),
         integrity_metadata_(integrity_metadata),
         integrity_attribute_(integrity_attribute),
@@ -67,8 +64,7 @@
         referrer_policy_(referrer_policy),
         fetch_priority_hint_(fetch_priority_hint),
         render_blocking_behavior_(render_blocking_behavior),
-        reject_coep_unsafe_none_(reject_coep_unsafe_none),
-        attribution_reporting_eligibility_(attribution_reporting_eligibility) {}
+        reject_coep_unsafe_none_(reject_coep_unsafe_none) {}
   ~ScriptFetchOptions() = default;
 
   const String& Nonce() const { return nonce_; }
@@ -95,6 +91,11 @@
     return render_blocking_behavior_;
   }
 
+  void SetAttributionReportingEligibility(
+      AttributionReportingEligibility eligibility) {
+    attribution_reporting_eligibility_ = eligibility;
+  }
+
   // https://html.spec.whatwg.org/C/#fetch-a-classic-script
   // Steps 1 and 3.
   FetchParameters CreateFetchParameters(
@@ -135,7 +136,9 @@
       RejectCoepUnsafeNone(false);
 
   // https://wicg.github.io/attribution-reporting-api
-  const AttributionReportingEligibility attribution_reporting_eligibility_ =
+  // TODO(crbug.com/1338976): make this member const once the attributionsrc
+  // spec is drafted.
+  AttributionReportingEligibility attribution_reporting_eligibility_ =
       AttributionReportingEligibility::kIneligible;
 };
 
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 43635dd..f30a8dad 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -1004,6 +1004,10 @@
       status: "test",
     },
     {
+      name: "FedCmMultipleIdentityProviders",
+      status: "test",
+    },
+    {
       name: "FencedFrames",
       // This helps enable and expose the <fencedframe> element, but note that
       // blink::features::kFencedFrames must be enabled as well, similar to
diff --git a/third_party/blink/renderer/platform/testing/font_test_helpers.cc b/third_party/blink/renderer/platform/testing/font_test_helpers.cc
index d297fe29..953b8ea5 100644
--- a/third_party/blink/renderer/platform/testing/font_test_helpers.cc
+++ b/third_party/blink/renderer/platform/testing/font_test_helpers.cc
@@ -51,6 +51,7 @@
         {NormalWeightValue(), NormalWeightValue()});
     FontPlatformData platform_data = custom_platform_data_->GetFontPlatformData(
         font_description.EffectiveFontSize(),
+        font_description.AdjustedSpecifiedSize(),
         font_description.IsSyntheticBold() &&
             font_description.SyntheticBoldAllowed(),
         font_description.IsSyntheticItalic() &&
diff --git a/third_party/blink/renderer/platform/text/segmented_string.cc b/third_party/blink/renderer/platform/text/segmented_string.cc
index d3d71e0..39dc87da 100644
--- a/third_party/blink/renderer/platform/text/segmented_string.cc
+++ b/third_party/blink/renderer/platform/text/segmented_string.cc
@@ -124,6 +124,24 @@
   Prepend(s.current_string_, type);
 }
 
+void SegmentedString::Advance(unsigned num_chars,
+                              unsigned num_lines,
+                              int current_column) {
+  SECURITY_DCHECK(num_chars <= length());
+  current_line_ += num_lines;
+  while (num_chars) {
+    num_chars -= current_string_.Advance(num_chars);
+    if (num_chars) {
+      // AdvanceSubstring() assumes one char is remaining.
+      DCHECK_EQ(current_string_.length(), 1);
+      AdvanceSubstring();
+      --num_chars;
+    }
+  }
+  number_of_characters_consumed_prior_to_current_line_ =
+      NumberOfCharactersConsumed() - current_column;
+}
+
 UChar SegmentedString::AdvanceSubstring() {
   number_of_characters_consumed_prior_to_current_string_ +=
       current_string_.NumberOfCharactersConsumed() + 1;
diff --git a/third_party/blink/renderer/platform/text/segmented_string.h b/third_party/blink/renderer/platform/text/segmented_string.h
index 7bcf501..386e637 100644
--- a/third_party/blink/renderer/platform/text/segmented_string.h
+++ b/third_party/blink/renderer/platform/text/segmented_string.h
@@ -116,6 +116,18 @@
     return data_.string8_ptr < data_last_char_;
   }
 
+  // Advances up to `delta` characters, returning how many characters were
+  // advanced. This will not advance past the last character.
+  unsigned Advance(unsigned delta) {
+    DCHECK_NE(0, length());
+    delta = std::min(static_cast<unsigned>(length()) - 1, delta);
+    if (is_8bit_)
+      data_.string8_ptr += delta;
+    else
+      data_.string16_ptr += delta;
+    return delta;
+  }
+
   ALWAYS_INLINE UChar Advance() {
     return is_8bit_ ? *++data_.string8_ptr : *++data_.string16_ptr;
   }
@@ -216,6 +228,12 @@
     return LookAheadInline(string, kTextCaseASCIIInsensitive);
   }
 
+  // Used to advance by multiple characters. Specifically this advances by
+  // `num_chars` and `num_lines`. This function advances without analyzing the
+  // input string in anyway. As a result, the caller must know `num_lines` and
+  // `current_column`.
+  void Advance(unsigned num_chars, unsigned num_lines, int current_column);
+
   ALWAYS_INLINE UChar Advance() {
     if (LIKELY(current_string_.CanAdvance())) {
       return current_string_.Advance();
diff --git a/third_party/blink/renderer/platform/text/segmented_string_test.cc b/third_party/blink/renderer/platform/text/segmented_string_test.cc
index 48b6d05..53ed801 100644
--- a/third_party/blink/renderer/platform/text/segmented_string_test.cc
+++ b/third_party/blink/renderer/platform/text/segmented_string_test.cc
@@ -104,4 +104,42 @@
   EXPECT_EQ(s1.NumberOfCharactersConsumed(), 3);
 }
 
+TEST(SegmentedStringTest, AdvanceCurrentString) {
+  SegmentedString s("0123456789");
+
+  s.Advance(4, 0, 4);
+  EXPECT_EQ(6u, s.length());
+  EXPECT_EQ(4, s.NumberOfCharactersConsumed());
+  EXPECT_EQ(4, s.CurrentColumn().ZeroBasedInt());
+  EXPECT_EQ(0, s.CurrentLine().ZeroBasedInt());
+  EXPECT_EQ('4', static_cast<char>(s.CurrentChar()));
+}
+
+TEST(SegmentedStringTest, AdvanceThroughMultipleStrings) {
+  SegmentedString s("a");
+  s.Append(SegmentedString("b"));
+  s.Append(SegmentedString("c"));
+  s.Advance(2, 0, 0);
+  EXPECT_EQ(1u, s.length());
+  EXPECT_EQ(2, s.NumberOfCharactersConsumed());
+  EXPECT_EQ(0, s.CurrentColumn().ZeroBasedInt());
+  EXPECT_EQ(0, s.CurrentLine().ZeroBasedInt());
+  EXPECT_EQ('c', static_cast<char>(s.CurrentChar()));
+}
+
+TEST(SegmentedStringTest, AdvanceThroughNextString) {
+  SegmentedString s("0123456789");
+  s.Append(SegmentedString("\nabcdefg"));
+
+  // Advance through the first character
+  s.Advance();
+  // Advance through first string.
+  s.Advance(11, 1, 2);
+  EXPECT_EQ(6u, s.length());
+  EXPECT_EQ(12, s.NumberOfCharactersConsumed());
+  EXPECT_EQ(2, s.CurrentColumn().ZeroBasedInt());
+  EXPECT_EQ(1, s.CurrentLine().ZeroBasedInt());
+  EXPECT_EQ('b', static_cast<char>(s.CurrentChar()));
+}
+
 }  // namespace blink
diff --git a/third_party/blink/tools/blinkpy/tool/commands/rebaseline.py b/third_party/blink/tools/blinkpy/tool/commands/rebaseline.py
index 64254bd6..1daba38 100644
--- a/third_party/blink/tools/blinkpy/tool/commands/rebaseline.py
+++ b/third_party/blink/tools/blinkpy/tool/commands/rebaseline.py
@@ -537,24 +537,12 @@
                 test_flag_pairs_to_suffixes[test, flag_spec].update(
                     self._suffixes_for_actual_failures(test, build, step_name))
 
-        suffixes_flag_spec = collections.defaultdict(set)
-        test_list = collections.defaultdict(list)
+        optimize_commands = []
         for (test, flag_spec), suffixes in test_flag_pairs_to_suffixes.items():
+            # No need to optimize baselines for a test with no failures.
             if not suffixes:
                 continue
-            if flag_spec is None:
-                flag_spec = 'default'
-
-            suffixes_flag_spec[flag_spec].update(suffixes)
-            test_list[flag_spec].append(test)
-
-        optimize_commands = collections.defaultdict(lambda: ([], ''))
-        cwd = self._tool.git().checkout_root
-        path_to_blink_tool = self._tool.path()
-
-        # Build one optimize-baselines invocation command for each flag_spec.
-        # All the tests in the test list will be optimized iteratively.
-        for flag_spec, test_list_flag in test_list.items():
+            path_to_blink_tool = self._tool.path()
             command = [
                 self._tool.executable,
                 path_to_blink_tool,
@@ -566,15 +554,11 @@
             ]
             if verbose:
                 command.append('--verbose')
-            if flag_spec != 'default':
+            if flag_spec:
                 command.extend(['--flag-specific', flag_spec])
-            command.extend([
-                '--suffixes', ','.join(sorted(suffixes_flag_spec[flag_spec]))
-            ])
-            for test in test_list_flag:
-                command.append(test)
-
-            optimize_commands[flag_spec] = (command, cwd)
+            command.extend(['--suffixes', ','.join(sorted(suffixes)), test])
+            cwd = self._tool.git().checkout_root
+            optimize_commands.append((command, cwd))
 
         return optimize_commands
 
@@ -668,14 +652,9 @@
             self._update_expectations_files(lines_to_remove)
 
         if options.optimize:
-            optimize_commands = self._optimize_commands(
-                test_baseline_set, options.verbose, options.resultDB)
-
-            for _, (cmd, cwd) in optimize_commands.items():
-                output = self._tool.executive.run_command(cmd, cwd)
-                # log stderr if any.
-                for line in output[1]:
-                    print(line)
+            self._run_in_parallel(
+                self._optimize_commands(test_baseline_set, options.verbose,
+                                        options.resultDB))
 
         self._tool.git().add_list(self.unstaged_baselines())
 
diff --git a/third_party/blink/tools/blinkpy/tool/commands/rebaseline_cl_unittest.py b/third_party/blink/tools/blinkpy/tool/commands/rebaseline_cl_unittest.py
index 48292e063..8dba493 100644
--- a/third_party/blink/tools/blinkpy/tool/commands/rebaseline_cl_unittest.py
+++ b/third_party/blink/tools/blinkpy/tool/commands/rebaseline_cl_unittest.py
@@ -649,7 +649,7 @@
                               '--step-name',
                               'blink_web_tests (with patch)',
                           ]],
-                          [
+                          [[
                               'python',
                               'echo',
                               'optimize-baselines',
@@ -657,7 +657,7 @@
                               '--suffixes',
                               'wav',
                               'one/flaky-fail.html',
-                          ]])
+                          ]]])
 
     def test_rebaseline_command_invocations_multiple_steps(self):
         """Test the rebaseline tool handles multiple steps on the same builder.
@@ -700,20 +700,21 @@
                 'not_site_per_process_blink_web_tests (with patch)'
             ],
         ])
-        print(self.tool.executive.calls)
-        self.assertEqual(self.tool.executive.calls[2], [
-            'python', 'echo', 'optimize-baselines', '--no-manifest-update',
-            '--suffixes', 'txt', 'one/text-fail.html'
-        ])
-        self.assertEqual(self.tool.executive.calls[3], [
-            'python', 'echo', 'optimize-baselines', '--no-manifest-update',
-            '--flag-specific', 'disable-layout-ng', '--suffixes', 'txt',
-            'one/text-fail.html'
-        ])
-        self.assertEqual(self.tool.executive.calls[4], [
-            'python', 'echo', 'optimize-baselines', '--no-manifest-update',
-            '--flag-specific', 'disable-site-isolation-trials', '--suffixes',
-            'txt', 'one/text-fail.html'
+        self.assertEqual(sorted(self.tool.executive.calls[2]), [
+            [
+                'python', 'echo', 'optimize-baselines', '--no-manifest-update',
+                '--flag-specific', 'disable-layout-ng', '--suffixes', 'txt',
+                'one/text-fail.html'
+            ],
+            [
+                'python', 'echo', 'optimize-baselines', '--no-manifest-update',
+                '--flag-specific', 'disable-site-isolation-trials',
+                '--suffixes', 'txt', 'one/text-fail.html'
+            ],
+            [
+                'python', 'echo', 'optimize-baselines', '--no-manifest-update',
+                '--suffixes', 'txt', 'one/text-fail.html'
+            ]
         ])
 
     def test_trigger_try_jobs(self):
diff --git a/third_party/blink/tools/blinkpy/tool/commands/rebaseline_unittest.py b/third_party/blink/tools/blinkpy/tool/commands/rebaseline_unittest.py
index 0d8a3b5..09350f30 100644
--- a/third_party/blink/tools/blinkpy/tool/commands/rebaseline_unittest.py
+++ b/third_party/blink/tools/blinkpy/tool/commands/rebaseline_unittest.py
@@ -457,7 +457,7 @@
                               '--step-name',
                               'blink_web_tests (with patch)',
                           ]],
-                          [
+                          [[
                               'python',
                               'echo',
                               'optimize-baselines',
@@ -466,7 +466,7 @@
                               '--suffixes',
                               'png,txt',
                               'userscripts/first-test.html',
-                          ]])
+                          ]]])
 
     def test_rebaseline_debug(self):
         test_baseline_set = TestBaselineSet(self.tool)
@@ -504,7 +504,7 @@
                               '--step-name',
                               'blink_web_tests (with patch)',
                           ]],
-                          [
+                          [[
                               'python',
                               'echo',
                               'optimize-baselines',
@@ -513,7 +513,7 @@
                               '--suffixes',
                               'png,txt',
                               'userscripts/first-test.html',
-                          ]])
+                          ]]])
 
     def test_no_optimize(self):
         test_baseline_set = TestBaselineSet(self.tool)
@@ -631,7 +631,7 @@
                               '--step-name',
                               'blink_web_tests (with patch)',
                           ]],
-                          [
+                          [[
                               'python',
                               'echo',
                               'optimize-baselines',
@@ -640,7 +640,7 @@
                               '--suffixes',
                               'png,txt',
                               'userscripts/first-test.html',
-                          ]])
+                          ]]])
 
 
 class TestRebaselineUpdatesExpectationsFiles(BaseTestCase):
diff --git a/third_party/blink/web_tests/FlagExpectations/force-renderer-accessibility b/third_party/blink/web_tests/FlagExpectations/force-renderer-accessibility
index cac050e..42999ac2 100644
--- a/third_party/blink/web_tests/FlagExpectations/force-renderer-accessibility
+++ b/third_party/blink/web_tests/FlagExpectations/force-renderer-accessibility
@@ -109,4 +109,4 @@
 
 # Most likely a forced style/layout update from accessibiity while we are
 # render blocking, which should not happen.
-crbug.com/1351811 external/wpt/css/css-transitions/no-transition-from-ua-to-blocking-stylesheet.html [ Pass Failure ]
+crbug.com/1351811 external/wpt/css/css-transitions/no-transition-from-ua-to-blocking-stylesheet.html [ Failure Pass ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index b4af098..2254887d 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -3388,7 +3388,7 @@
 crbug.com/626703 [ Mac11 ] virtual/fenced-frame-shadow-dom/wpt_internal/fenced_frame/background-sync.https.html [ Timeout ]
 crbug.com/626703 [ Mac10.15 ] external/wpt/fullscreen/api/document-fullscreen-enabled-cross-origin.sub.html [ Timeout ]
 crbug.com/626703 [ Win10.20h2 ] virtual/attribution-reporting-debug-mode/wpt_internal/attribution-reporting/debug-key.sub.https.html?include=trigger [ Failure Timeout ]
-crbug.com/626703 [ Mac11 ] virtual/attribution-reporting-debug-mode/wpt_internal/attribution-reporting/source-registration.sub.https.html?method=img&eligible [ Timeout ]
+crbug.com/626703 [ Mac11 ] virtual/attribution-reporting-debug-mode/wpt_internal/attribution-reporting/source-registration.sub.https.html?method=img&eligible [ Skip Timeout ]
 crbug.com/626703 [ Win11 ] virtual/attribution-reporting-debug-mode/wpt_internal/attribution-reporting/trigger-data-sanitization.sub.https.html?source-type=navigation [ Failure Timeout ]
 crbug.com/626703 [ Mac11-arm64 ] virtual/fenced-frame-mparch/wpt_internal/fenced_frame/key-scrolling.https.html [ Failure Timeout ]
 crbug.com/626703 virtual/attribution-reporting-debug-mode/wpt_internal/attribution-reporting/trigger-registration.sub.https.html?method=xhr [ Pass Timeout ]
@@ -3399,9 +3399,9 @@
 crbug.com/626703 virtual/attribution-reporting-debug-mode/wpt_internal/attribution-reporting/trigger-registration.sub.https.html?method=script [ Pass Timeout ]
 crbug.com/626703 virtual/attribution-reporting-debug-mode/wpt_internal/attribution-reporting/trigger-registration.sub.https.html?method=script&eligible [ Pass Timeout ]
 crbug.com/626703 [ Mac11 ] virtual/document-transition/wpt_internal/document-transition/commit-timeout-crash.html [ Timeout ]
-crbug.com/626703 [ Win11 ] virtual/attribution-reporting-debug-mode/wpt_internal/attribution-reporting/source-registration.sub.https.html?method=a [ Timeout ]
-crbug.com/626703 [ Win11 ] virtual/attribution-reporting-debug-mode/wpt_internal/attribution-reporting/source-registration.sub.https.html?method=fetch&eligible=event-source [ Timeout ]
-crbug.com/626703 [ Win11 ] virtual/attribution-reporting-debug-mode/wpt_internal/attribution-reporting/source-registration.sub.https.html?method=script&eligible [ Timeout ]
+crbug.com/626703 [ Win11 ] virtual/attribution-reporting-debug-mode/wpt_internal/attribution-reporting/source-registration.sub.https.html?method=a [ Skip Timeout ]
+crbug.com/626703 [ Win11 ] virtual/attribution-reporting-debug-mode/wpt_internal/attribution-reporting/source-registration.sub.https.html?method=fetch&eligible=event-source [ Skip Timeout ]
+crbug.com/626703 [ Win11 ] virtual/attribution-reporting-debug-mode/wpt_internal/attribution-reporting/source-registration.sub.https.html?method=script&eligible [ Skip Timeout ]
 crbug.com/626703 [ Win10.20h2 ] wpt_internal/geolocation-api/watchPosition-page-visibility.https.html [ Timeout ]
 crbug.com/626703 [ Win11 ] virtual/attribution-reporting-debug-mode/wpt_internal/attribution-reporting/debug-key-test.sub.https.html?include=source [ Timeout ]
 crbug.com/626703 [ Win11 ] virtual/attribution-reporting-debug-mode/wpt_internal/attribution-reporting/simple-event-level-report-test.sub.https.html [ Timeout ]
@@ -7000,9 +7000,6 @@
 crbug.com/1180274 virtual/partitioned-cookies/http/tests/inspector-protocol/network/navigate-iframe-out2in.js [ Skip ]
 
 # Sheriff 2022-07-11
-crbug.com/1343262 [ Win11 ] compositing/overlap-blending/reflection-opacity-huge.html [ Failure ]
-crbug.com/1343262 [ Win11 ] compositing/reflections/nested-reflection-opacity.html [ Failure ]
-crbug.com/1343262 [ Win11 ] fast/reflections/transparent-reflected-sublayers.html [ Failure ]
 crbug.com/1343270 [ Win11 ] media/controls/video-controls-with-cast-rendering.html [ Failure ]
 
 # Sheriff 2022-07-12
@@ -7100,3 +7097,8 @@
 
 # TODO(crbug.com/1352009): Research how to re-enable test once not flaky
 crbug.com/1352009 external/wpt/preload/preconnect.html [ Failure Pass ]
+
+# Sheriff 2022-08-11
+# Most likely a forced style/layout update from accessibiity while we are
+# render blocking, which should not happen.
+crbug.com/1351811 external/wpt/css/css-transitions/no-transition-from-ua-to-blocking-stylesheet.html [ Pass Failure ]
diff --git a/third_party/blink/web_tests/external/Version b/third_party/blink/web_tests/external/Version
index 596ce405..8a82631 100644
--- a/third_party/blink/web_tests/external/Version
+++ b/third_party/blink/web_tests/external/Version
@@ -1 +1 @@
-Version: 321bbd64536947a851b62a1af992efd8f8efc1fe
+Version: 1d7b12e92199a765ff83c89c8c4f8345fb5137c3
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index 1e23bb1..35dc05f4 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -833,6 +833,13 @@
         ]
        ]
       },
+      "table-in-abspos-multicol-with-nested-meter-crash.html": [
+       "363324b225b2e72d05defcb2ddea4865ca76ef8a",
+       [
+        null,
+        {}
+       ]
+      ],
       "table-row-end-border-1-crash.html": [
        "3947dbdadaa1b8a5c836cc82ac0fb3508eeb45e1",
        [
@@ -3553,6 +3560,13 @@
        {}
       ]
      ],
+     "forwarddelete-delete-after-justifyleft-indent.html": [
+      "0b0e188732751cf79141cab3fb95726ae5b91c3a",
+      [
+       null,
+       {}
+      ]
+     ],
      "forwarddelete-in-list-editing-host-after-selectall-with-focus.html": [
       "266cc4fb600227ed134c062d6c01cf52a9f1abed",
       [
@@ -3574,6 +3588,13 @@
        {}
       ]
      ],
+     "indent-outdent-after-closing-editable-dialog-element.html": [
+      "7f73de048d714c99ce47ae4da61d7128e53216e8",
+      [
+       null,
+       {}
+      ]
+     ],
      "insert-image-with-joining-header-element-and-body.html": [
       "cf5b2df225be06c833fea6d3bf2ceab6b1231018",
       [
@@ -185362,7 +185383,7 @@
        ]
       ],
       "transform-interpolation-rotate.html": [
-       "d69023c9b7acbeb97fa790482ccc93a82b233528",
+       "cba2d2086fc7feb7542d8cac9b7271a394e6af14",
        [
         null,
         [
@@ -185382,7 +185403,7 @@
             ],
             [
              0,
-             17
+             246
             ]
            ]
           ]
@@ -189831,7 +189852,7 @@
       ]
      ],
      "transform-3d-rotateY-stair-above-001.xht": [
-      "ce03b4fca16936167aaed9c778538ee799b85044",
+      "e637eb0b828eadf22c2254583e5dfeeb54863d7b",
       [
        null,
        [
@@ -189844,7 +189865,7 @@
       ]
      ],
      "transform-3d-rotateY-stair-below-001.xht": [
-      "9282f245f733803f7fa21cd351293a36ee4ae7f4",
+      "8d9b610bf030a83a60185dadd3b26c6e546af261",
       [
        null,
        [
@@ -189860,7 +189881,7 @@
           [
            [
             0,
-            3
+            14
            ],
            [
             0,
@@ -439325,7 +439346,7 @@
      ]
     ],
     "FileSystemSyncAccessHandle-read-write.https.tentative.worker.js": [
-     "9f2a61db78e6cc79748ac6e037797d468fc77941",
+     "cdefc78b6cb2115114accbd449e34766fbf0849b",
      [
       "fs/FileSystemSyncAccessHandle-read-write.https.tentative.worker.html",
       {}
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/animation/transform-interpolation-rotate.html b/third_party/blink/web_tests/external/wpt/css/css-transforms/animation/transform-interpolation-rotate.html
index d69023c9..cba2d20 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-transforms/animation/transform-interpolation-rotate.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-transforms/animation/transform-interpolation-rotate.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <html class="reftest-wait">
 <link rel="match" href="transform-interpolation-ref.html?rotate">
-<meta name="fuzzy" content="maxDifference=0-2;totalPixels=0-17">
+<meta name="fuzzy" content="maxDifference=0-2;totalPixels=0-246">
 <link rel="help" href="https://drafts.csswg.org/css-transforms/">
 
 <script src="../../../common/reftest-wait.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/transform-3d-rotateY-stair-above-001.xht b/third_party/blink/web_tests/external/wpt/css/css-transforms/transform-3d-rotateY-stair-above-001.xht
index ce03b4f..e637eb0b 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-transforms/transform-3d-rotateY-stair-above-001.xht
+++ b/third_party/blink/web_tests/external/wpt/css/css-transforms/transform-3d-rotateY-stair-above-001.xht
@@ -45,7 +45,7 @@
 }
 #three {
   width: 33px;
-  height: 147px;
+  height: 148px;
   top: 21px;
   left: 86px;
 }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/transform-3d-rotateY-stair-below-001.xht b/third_party/blink/web_tests/external/wpt/css/css-transforms/transform-3d-rotateY-stair-below-001.xht
index 9282f24..8d9b610 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-transforms/transform-3d-rotateY-stair-below-001.xht
+++ b/third_party/blink/web_tests/external/wpt/css/css-transforms/transform-3d-rotateY-stair-below-001.xht
@@ -5,7 +5,7 @@
   <link rel="author" title="Apple Inc." href="http://www.apple.com/"/>
   <link rel="help" href="http://www.w3.org/TR/css-transforms-2/#3d-transform-rendering"/>
   <link rel="match" href="reference/transform-3d-rotateY-stair-above-ref-001.xht"/>
-  <meta name="fuzzy" content="maxDifference=0-3;totalPixels=0-80" />
+  <meta name="fuzzy" content="maxDifference=0-14;totalPixels=0-80" />
   <meta name="assert" content="A rotateY transform with perspective
   should result in a trapezoid."/>
   <style type="text/css"><![CDATA[
diff --git a/third_party/blink/web_tests/external/wpt/editing/crashtests/forwarddelete-delete-after-justifyleft-indent.html b/third_party/blink/web_tests/external/wpt/editing/crashtests/forwarddelete-delete-after-justifyleft-indent.html
new file mode 100644
index 0000000..0b0e188
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/editing/crashtests/forwarddelete-delete-after-justifyleft-indent.html
@@ -0,0 +1,20 @@
+<script>
+document.addEventListener("DOMContentLoaded", () => {
+  const fieldset = document.querySelector("fieldset");
+  document.documentElement.contentEditable = true;
+  fieldset.contentEditable = false;
+  document.execCommand("justifyLeft");
+  document.designMode = "on";
+  document.execCommand("indent");
+  document.execCommand("forwardDelete");
+  document.execCommand("delete");
+});
+</script>
+<acronym readonly autofocus>
+<sup>
+<fieldset>
+
+here is fieldset
+</fieldset>
+</acronym>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/editing/crashtests/indent-outdent-after-closing-editable-dialog-element.html b/third_party/blink/web_tests/external/wpt/editing/crashtests/indent-outdent-after-closing-editable-dialog-element.html
new file mode 100644
index 0000000..7f73de04
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/editing/crashtests/indent-outdent-after-closing-editable-dialog-element.html
@@ -0,0 +1,31 @@
+<html class="reftest-wait">
+<script>
+var eventCount = 0;
+document.addEventListener("DOMContentLoaded", () => {
+  const dialog = document.querySelector("dialog");
+  const object = document.createElement("object");
+  object.addEventListener("DOMSubtreeModified", () => {
+    dialog.show();
+    dialog.focus();
+    document.execCommand("selectAll");
+    dialog.close();
+    setTimeout(() => {
+      document.execCommand("selectAll");
+      document.execCommand("strikeThrough");
+      document.execCommand("indent");
+      document.execCommand("outdent");
+      eventCount--;
+      if (!eventCount) {
+        document.documentElement.removeAttribute("class");
+      }
+    });
+    eventCount++;
+  });
+  object.setAttribute("role", "x"); // Run DOMSubtreeModified
+  object.setAttribute("role", "y"); // Run DOMSubtreeModified
+  document.execCommand("forwardDelete");
+  document.execCommand("justifyRight");
+})
+</script>
+<dialog id="a" contenteditable="true">a</dialog>
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/attribution-reporting/insecure-subresource-preload.js b/third_party/blink/web_tests/http/tests/inspector-protocol/attribution-reporting/insecure-subresource-preload.js
new file mode 100644
index 0000000..1902cb8
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/attribution-reporting/insecure-subresource-preload.js
@@ -0,0 +1,19 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+(async function(testRunner) {
+  const {page, dp} = await testRunner.startBlank(
+      `Test that an untrustworthy attributionsrc triggers an issue when the img is preloaded.`);
+
+  await dp.Audits.enable();
+
+  const issuePromise = dp.Audits.onceIssueAdded();
+
+  await page.navigate(
+      'https://devtools.test:8443/inspector-protocol/attribution-reporting/resources/preload.html');
+
+  const issue = await issuePromise;
+  testRunner.log(issue.params.issue, 'Issue reported: ', ['violatingNodeId']);
+  testRunner.completeTest();
+})
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/attribution-reporting/resources/preload.html b/third_party/blink/web_tests/http/tests/inspector-protocol/attribution-reporting/resources/preload.html
new file mode 100644
index 0000000..d5525de
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/attribution-reporting/resources/preload.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Preloaded Attributionsrc</title>
+<body>
+<img attributionsrc src="wss://example.test">
diff --git a/third_party/blink/web_tests/platform/generic/http/tests/inspector-protocol/attribution-reporting/insecure-subresource-preload-expected.txt b/third_party/blink/web_tests/platform/generic/http/tests/inspector-protocol/attribution-reporting/insecure-subresource-preload-expected.txt
new file mode 100644
index 0000000..029846ef
--- /dev/null
+++ b/third_party/blink/web_tests/platform/generic/http/tests/inspector-protocol/attribution-reporting/insecure-subresource-preload-expected.txt
@@ -0,0 +1,12 @@
+Test that an untrustworthy attributionsrc triggers an issue when the img is preloaded.
+Issue reported: {
+    code : AttributionReportingIssue
+    details : {
+        attributionReportingIssueDetails : {
+            invalidParameter : wss://example.test
+            violatingNodeId : <number>
+            violationType : UntrustworthyReportingOrigin
+        }
+    }
+}
+
diff --git a/third_party/blink/web_tests/platform/win/compositing/overlap-blending/reflection-opacity-huge-expected.png b/third_party/blink/web_tests/platform/win/compositing/overlap-blending/reflection-opacity-huge-expected.png
index 3b00e70..acbed94 100644
--- a/third_party/blink/web_tests/platform/win/compositing/overlap-blending/reflection-opacity-huge-expected.png
+++ b/third_party/blink/web_tests/platform/win/compositing/overlap-blending/reflection-opacity-huge-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/reflections/transparent-reflected-sublayers-expected.png b/third_party/blink/web_tests/platform/win/fast/reflections/transparent-reflected-sublayers-expected.png
index b0124c78..497baee2c 100644
--- a/third_party/blink/web_tests/platform/win/fast/reflections/transparent-reflected-sublayers-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/reflections/transparent-reflected-sublayers-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win10/compositing/overlap-blending/reflection-opacity-huge-expected.png b/third_party/blink/web_tests/platform/win10/compositing/overlap-blending/reflection-opacity-huge-expected.png
deleted file mode 100644
index acbed94..0000000
--- a/third_party/blink/web_tests/platform/win10/compositing/overlap-blending/reflection-opacity-huge-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win10/fast/reflections/transparent-reflected-sublayers-expected.png b/third_party/blink/web_tests/platform/win10/fast/reflections/transparent-reflected-sublayers-expected.png
deleted file mode 100644
index 497baee2c..0000000
--- a/third_party/blink/web_tests/platform/win10/fast/reflections/transparent-reflected-sublayers-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/wpt_internal/attribution-reporting/source-registration.sub.https.html b/third_party/blink/web_tests/wpt_internal/attribution-reporting/source-registration.sub.https.html
index 19ae2bcb..d751c1c 100644
--- a/third_party/blink/web_tests/wpt_internal/attribution-reporting/source-registration.sub.https.html
+++ b/third_party/blink/web_tests/wpt_internal/attribution-reporting/source-registration.sub.https.html
@@ -1,5 +1,6 @@
 <!doctype html>
 <meta charset=utf-8>
+<meta name=timeout content=long>
 <meta name=variant content="?method=a">
 <meta name=variant content="?method=a&eligible">
 <meta name=variant content="?method=fetch&eligible=event-source">
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-fonts/resources/variabletest_box.ttf b/third_party/blink/web_tests/wpt_internal/css/css-fonts/resources/variabletest_box.ttf
new file mode 100644
index 0000000..0d5bf3e2
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-fonts/resources/variabletest_box.ttf
Binary files differ
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-fonts/variable-opsz-zoom-ref.html b/third_party/blink/web_tests/wpt_internal/css/css-fonts/variable-opsz-zoom-ref.html
new file mode 100644
index 0000000..0cd4d2b
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-fonts/variable-opsz-zoom-ref.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<style>
+    @font-face {
+    font-family: variabletest_box;
+    src: url(resources/variabletest_box.ttf);
+    }
+
+    body {
+    font-family: variabletest_box, sans-serif;
+    font-optical-sizing: none;
+    }
+
+    .columns {
+    display: flex;
+    gap: 20px;
+    }
+</style>
+<div class="columns">
+<div>
+<div style="font-size:   6px; font-variation-settings: 'opsz'    6;">B</div>
+<div style="font-size:  12px; font-variation-settings: 'opsz'   12;">B</div>
+<div style="font-size:  24px; font-variation-settings: 'opsz'   24;">B</div>
+</div>
+<div>
+<div style="font-size:  48px; font-variation-settings: 'opsz'   48;">B</div>
+<div style="font-size:  64px; font-variation-settings: 'opsz'   64;">B</div>
+<div style="font-size: 128px; font-variation-settings: 'opsz'  128;">B</div>
+</div>
+<div>
+<div style="font-size:  24px; font-variation-settings: 'opsz'  128;">B</div>
+<div style="font-size:  48px; font-variation-settings: 'opsz'   12;">B</div>
+</div>
+</div>
+<script>
+  matchMedia('(resolution: 2dppx)').addEventListener("change", zoomReached, { once: true })
+    if (window.testRunner)
+        testRunner.setPageZoomFactor(2);
+
+  function zoomReached() {
+      document.fonts.ready.then(
+          () => { document.documentElement.classList.remove("reftest-wait"); });
+  }
+</script>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-fonts/variable-opsz-zoom-size-adjust-ref.html b/third_party/blink/web_tests/wpt_internal/css/css-fonts/variable-opsz-zoom-size-adjust-ref.html
new file mode 100644
index 0000000..566c746
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-fonts/variable-opsz-zoom-size-adjust-ref.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<style>
+    @font-face {
+    font-family: variabletest_box;
+    src: url(resources/variabletest_box.ttf);
+    }
+
+    body {
+    font-family: variabletest_box, sans-serif;
+    font-optical-sizing: none;
+    }
+
+    .columns {
+    display: flex;
+    gap: 20px;
+    }
+</style>
+<div class="columns">
+<div>
+<div style="font-size:   9px; font-variation-settings: 'opsz'    9;">B</div>
+<div style="font-size:  18px; font-variation-settings: 'opsz'   18;">B</div>
+<div style="font-size:  36px; font-variation-settings: 'opsz'   36;">B</div>
+</div>
+<div>
+<div style="font-size:  72px; font-variation-settings: 'opsz'   72;">B</div>
+<div style="font-size:  96px; font-variation-settings: 'opsz'   96;">B</div>
+</div>
+<div>
+<div style="font-size:  36px; font-variation-settings: 'opsz'  128;">B</div>
+<div style="font-size:  72px; font-variation-settings: 'opsz'   12;">B</div>
+</div>
+</div>
+<script>
+  matchMedia('(resolution: 2dppx)').addEventListener("change", zoomReached, { once: true })
+    if (window.testRunner)
+        testRunner.setPageZoomFactor(2);
+
+  function zoomReached() {
+      document.fonts.ready.then(
+          () => { document.documentElement.classList.remove("reftest-wait"); });
+  }
+</script>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-fonts/variable-opsz-zoom-size-adjust.html b/third_party/blink/web_tests/wpt_internal/css/css-fonts/variable-opsz-zoom-size-adjust.html
new file mode 100644
index 0000000..3ec9d72e
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-fonts/variable-opsz-zoom-size-adjust.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<link rel="author" title="Dominik Röttsches" href="drott@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-fonts-4/#font-optical-sizing-def"/>
+<link rel="help" href="https://drafts.csswg.org/css-fonts-5/#size-adjust-desc"/>
+<meta name="assert" content="Ensures that optical size is automatically applied, including under zoom."/>
+<link rel="match" href="variable-opsz-zoom-size-adjust-ref.html">
+<meta charset="utf-8">
+<style>
+    @font-face {
+    font-family: variabletest_box;
+    src: url(resources/variabletest_box.ttf);
+    size-adjust: 150%;
+    }
+
+    body {
+    font-family: variabletest_box, sans-serif;
+    }
+    .columns {
+    display: flex;
+    gap: 20px;
+    }
+</style>
+<!-- The variabletest_box font file contains a glyph for letter B which moves a
+  horizontal bar up from the middle for opsz > 12 and moves it down for opsz >
+  12 where the opsz axis ranges from 12 to 128. -->
+<div class="columns">
+<div>
+<div style="font-size:   6px;">B</div>
+<div style="font-size:  12px;">B</div>
+<div style="font-size:  24px;">B</div>
+</div>
+<div>
+<div style="font-size:  48px;">B</div>
+<div style="font-size:  64px;">B</div>
+</div>
+<div>
+<!-- Explicit value overrides auto. -->
+<div style="font-size:  24px; font-variation-settings: 'opsz' 128;">B</div>
+<div style="font-size:  48px; font-optical-sizing: none;">B</div>
+</div>
+</div>
+  <script>
+    matchMedia('(resolution: 2dppx)').addEventListener("change", zoomReached, { once: true })
+    if (window.testRunner)
+        testRunner.setPageZoomFactor(2);
+
+  function zoomReached() {
+      document.fonts.ready.then(
+          () => { document.documentElement.classList.remove("reftest-wait"); });
+  }
+</script>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-fonts/variable-opsz-zoom.html b/third_party/blink/web_tests/wpt_internal/css/css-fonts/variable-opsz-zoom.html
new file mode 100644
index 0000000..de76eae
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-fonts/variable-opsz-zoom.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<link rel="author" title="Dominik Röttsches" href="drott@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-fonts-4/#font-optical-sizing-def"/>
+<meta name="assert" content="Ensures that optical size is automatically applied, including under zoom."/>
+<link rel="match" href="variable-opsz-zoom-ref.html">
+<meta charset="utf-8">
+<style>
+    @font-face {
+    font-family: variabletest_box;
+    src: url(resources/variabletest_box.ttf);
+    }
+
+    body {
+    font-family: variabletest_box, sans-serif;
+    }
+    .columns {
+    display: flex;
+    gap: 20px;
+    }
+</style>
+<!-- The variabletest_box font file contains a glyph for letter B which moves a
+  horizontal bar up from the middle for opsz > 12 and moves it down for opsz >
+  12 where the opsz axis ranges from 12 to 128. -->
+<div class="columns">
+<div>
+<div style="font-size:   6px;">B</div>
+<div style="font-size:  12px;">B</div>
+<div style="font-size:  24px;">B</div>
+</div>
+<div>
+<div style="font-size:  48px;">B</div>
+<div style="font-size:  64px;">B</div>
+<div style="font-size: 128px;">B</div>
+</div>
+<div>
+<!-- Explicit value overrides auto. -->
+<div style="font-size:  24px; font-variation-settings: 'opsz' 128;">B</div>
+<div style="font-size:  48px; font-optical-sizing: none;">B</div>
+</div>
+</div>
+  <script>
+    matchMedia('(resolution: 2dppx)').addEventListener("change", zoomReached, { once: true })
+    if (window.testRunner)
+        testRunner.setPageZoomFactor(2);
+
+  function zoomReached() {
+      document.fonts.ready.then(
+          () => { document.documentElement.classList.remove("reftest-wait"); });
+  }
+</script>
+</html>
diff --git a/third_party/ipcz/src/reference_drivers/random.cc b/third_party/ipcz/src/reference_drivers/random.cc
index d2d0310..9131225 100644
--- a/third_party/ipcz/src/reference_drivers/random.cc
+++ b/third_party/ipcz/src/reference_drivers/random.cc
@@ -29,6 +29,7 @@
 
 #if BUILDFLAG(IS_POSIX)
 #include <fcntl.h>
+#include <unistd.h>
 #endif
 
 #if BUILDFLAG(IS_WIN)
@@ -97,6 +98,8 @@
   } else {
     RandomBytesFromDevUrandom(destination);
   }
+#elif BUILDFLAG(IS_IOS)
+  RandomBytesFromDevUrandom(destination);
 #elif BUILDFLAG(IS_NACL)
   while (!destination.empty()) {
     size_t nread;
diff --git a/third_party/nearby/README.chromium b/third_party/nearby/README.chromium
index ad185d5..086f5b1 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: 30a82350cdda38c01fd6794d9addee89ced7d10f
+Version: 76d45f20db7260c315b52396c740ae3e3f7bfd9f
 License: Apache 2.0
 License File: LICENSE
 Security Critical: yes
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index edc99fc..c205ff4 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -56159,6 +56159,7 @@
   <int value="-1818565030"
       label="AutofillUseConsistentPopupSettingsIcons:disabled"/>
   <int value="-1818135677" label="DisableLacrosTtsSupport:enabled"/>
+  <int value="-1817721380" label="RemoveCanceledTasksInTaskQueue2:disabled"/>
   <int value="-1817209284" label="PayWithGoogleV1:enabled"/>
   <int value="-1816066138" label="CastAllowAllIPs:enabled"/>
   <int value="-1813088913" label="CCTResizable90MaximumHeight:disabled"/>
@@ -56770,6 +56771,7 @@
   <int value="-1437526584" label="HelpAppReleaseNotes:enabled"/>
   <int value="-1436892902" label="FastPairSavedDevices:disabled"/>
   <int value="-1436251034" label="VoiceSearchOnLocalNtp:disabled"/>
+  <int value="-1435804236" label="ThrottleIntersectionObserverUMA:enabled"/>
   <int value="-1433719718" label="enable-webrtc-stun-origin"/>
   <int value="-1433452630" label="AllowRemoteContextForNotifications:enabled"/>
   <int value="-1433087548" label="enable-app-install-alerts"/>
@@ -58342,6 +58344,7 @@
   <int value="-470247915"
       label="AutofillUpstreamEditableExpirationDate:enabled"/>
   <int value="-468697885" label="ArcInputMethod:enabled"/>
+  <int value="-468657548" label="ReduceGpuPriorityOnBackground:enabled"/>
   <int value="-467792766" label="ReengagementNotification:enabled"/>
   <int value="-466704882" label="webview-log-js-console-messages"/>
   <int value="-466164593"
@@ -59045,6 +59048,7 @@
   <int value="-9599490" label="KernelnextVMs:disabled"/>
   <int value="-9322265" label="ShowBluetoothDebugLogToggle:enabled"/>
   <int value="-8063095" label="AutofillEnableRemadeDownstreamMetrics:enabled"/>
+  <int value="-7996532" label="ThrottleIntersectionObserverUMA:disabled"/>
   <int value="-5492723" label="CCTIncognito:enabled"/>
   <int value="-5305648" label="OmniboxClosePopupWithEscape:enabled"/>
   <int value="-5052940" label="enable-simplified-fullscreen"/>
@@ -59613,6 +59617,7 @@
   <int value="353898777" label="ClipboardHistoryScreenshotNudge:disabled"/>
   <int value="354466842" label="UachOverrideBlank:enabled"/>
   <int value="354631905" label="RecoverFromNeverSaveAndroid:disabled"/>
+  <int value="354662118" label="RemoveCanceledTasksInTaskQueue2:enabled"/>
   <int value="355367368" label="SharesheetCopyToClipboard:disabled"/>
   <int value="357138275" label="enable-floating-virtual-keyboard:disabled"/>
   <int value="357165937" label="ShareToGoogleCollections:enabled"/>
@@ -61435,6 +61440,7 @@
   <int value="1514066770" label="DisplayCutoutAPI:enabled"/>
   <int value="1514119870" label="enable-file-manager-touch-mode"/>
   <int value="1514158607" label="NearbySharingDeviceContacts:enabled"/>
+  <int value="1514951731" label="ReduceGpuPriorityOnBackground:disabled"/>
   <int value="1515196403" label="fast-user-switching"/>
   <int value="1515334367" label="ToolbarMicIphAndroid:disabled"/>
   <int value="1515472077" label="VirtualKeyboardRoundCorners:disabled"/>
diff --git a/tools/metrics/histograms/metadata/cookie/histograms.xml b/tools/metrics/histograms/metadata/cookie/histograms.xml
index c7f0244..1b4080a 100644
--- a/tools/metrics/histograms/metadata/cookie/histograms.xml
+++ b/tools/metrics/histograms/metadata/cookie/histograms.xml
@@ -429,7 +429,7 @@
 </histogram>
 
 <histogram name="Cookie.IsPartitionedValid" enum="BooleanValid"
-    expires_after="2022-10-30">
+    expires_after="2023-02-01">
   <owner>dylancutler@chromium.org</owner>
   <owner>bingler@chromium.org</owner>
   <summary>
@@ -471,6 +471,18 @@
   </summary>
 </histogram>
 
+<histogram name="Cookie.MaxSameSiteNoneCookiesPerKey" units="units"
+    expires_after="2023-02-01">
+  <owner>dylancutler@chromium.org</owner>
+  <owner>src/net/cookies/OWNERS</owner>
+  <summary>
+    Maximum number of SameSite=None cookies that belong to a single domain on
+    the client. This histogram will be used to inform the
+    per-partition-per-domain limit for partitioned cookies. Recorded every 10
+    minutes of active browsing time.
+  </summary>
+</histogram>
+
 <histogram name="Cookie.NumDomainPurgedKeys" units="keys"
     expires_after="2022-08-28">
   <owner>cfredric@chromium.org</owner>
diff --git a/ui/accessibility/ax_tree.cc b/ui/accessibility/ax_tree.cc
index 38256f64..966b3c9 100644
--- a/ui/accessibility/ax_tree.cc
+++ b/ui/accessibility/ax_tree.cc
@@ -1171,7 +1171,7 @@
     if (!root_) {
       ACCESSIBILITY_TREE_UNSERIALIZE_ERROR_HISTOGRAM(
           AXTreeUnserializeError::kNoRoot);
-      RecordError("Tree has no root.");
+      RecordError(update_state, "Tree has no root.");
       return false;
     }
 
@@ -1530,8 +1530,10 @@
     if (!is_new_root) {
       ACCESSIBILITY_TREE_UNSERIALIZE_ERROR_HISTOGRAM(
           AXTreeUnserializeError::kNotInTree);
-      RecordError(base::StringPrintf(
-          "%d will not be in the tree and is not the new root", new_data.id));
+      RecordError(*update_state,
+                  base::StringPrintf(
+                      "%d will not be in the tree and is not the new root",
+                      new_data.id));
       return false;
     }
 
@@ -1541,9 +1543,11 @@
                                                        absl::nullopt)) {
       ACCESSIBILITY_TREE_UNSERIALIZE_ERROR_HISTOGRAM(
           AXTreeUnserializeError::kCreationPending);
-      RecordError(base::StringPrintf(
-          "Node %d is already pending for creation, cannot be the new root",
-          new_data.id));
+      RecordError(
+          *update_state,
+          base::StringPrintf(
+              "Node %d is already pending for creation, cannot be the new root",
+              new_data.id));
       return false;
     }
     if (update_state->pending_root_id) {
@@ -1559,7 +1563,8 @@
     if (base::Contains(new_child_id_set, new_child_id)) {
       ACCESSIBILITY_TREE_UNSERIALIZE_ERROR_HISTOGRAM(
           AXTreeUnserializeError::kDuplicateChild);
-      RecordError(base::StringPrintf("Node %d has duplicate child id %d",
+      RecordError(*update_state,
+                  base::StringPrintf("Node %d has duplicate child id %d",
                                      new_data.id, new_child_id));
       return false;
     }
@@ -1584,9 +1589,10 @@
                                                          new_data.id)) {
         ACCESSIBILITY_TREE_UNSERIALIZE_ERROR_HISTOGRAM(
             AXTreeUnserializeError::kCreationPendingForChild);
-        RecordError(base::StringPrintf(
-            "Node %d is already pending for creation, cannot be a new child",
-            child_id));
+        RecordError(*update_state,
+                    base::StringPrintf("Node %d is already pending for "
+                                       "creation, cannot be a new child",
+                                       child_id));
         return false;
       }
     }
@@ -1628,9 +1634,10 @@
       if (update_state->ShouldPendingNodeExistInTree(child_id)) {
         ACCESSIBILITY_TREE_UNSERIALIZE_ERROR_HISTOGRAM(
             AXTreeUnserializeError::kReparent);
-        RecordError(base::StringPrintf(
-            "Node %d is not marked for destruction, would be reparented to %d",
-            child_id, new_data.id));
+        RecordError(*update_state,
+                    base::StringPrintf("Node %d is not marked for destruction, "
+                                       "would be reparented to %d",
+                                       child_id, new_data.id));
         return false;
       }
 
@@ -1641,9 +1648,10 @@
                                                          new_data.id)) {
         ACCESSIBILITY_TREE_UNSERIALIZE_ERROR_HISTOGRAM(
             AXTreeUnserializeError::kCreationPendingForChild);
-        RecordError(base::StringPrintf(
-            "Node %d is already pending for creation, cannot be a new child",
-            child_id));
+        RecordError(*update_state,
+                    base::StringPrintf("Node %d is already pending for "
+                                       "creation, cannot be a new child",
+                                       child_id));
         return false;
       }
     } else {
@@ -1682,8 +1690,9 @@
     if (!is_new_root) {
       ACCESSIBILITY_TREE_UNSERIALIZE_ERROR_HISTOGRAM(
           AXTreeUnserializeError::kNotInTree);
-      RecordError(base::StringPrintf(
-          "%d is not in the tree and not the new root", src.id));
+      RecordError(*update_state,
+                  base::StringPrintf(
+                      "%d is not in the tree and not the new root", src.id));
       return false;
     }
 
@@ -2007,7 +2016,7 @@
     std::string error = "Nodes left pending by the update:";
     for (const AXNodeID pending_id : update_state.pending_node_ids)
       error += base::StringPrintf(" %d", pending_id);
-    RecordError(error);
+    RecordError(update_state, error);
     return false;
   }
 
@@ -2033,11 +2042,13 @@
     if (has_pending_changes) {
       ACCESSIBILITY_TREE_UNSERIALIZE_ERROR_HISTOGRAM(
           AXTreeUnserializeError::kPendingChanges);
-      RecordError(base::StringPrintf(
-          "Changes left pending by the update; "
-          "destroy subtrees: %s, destroy nodes: %s, create nodes: %s",
-          destroy_subtree_ids.c_str(), destroy_node_ids.c_str(),
-          create_node_ids.c_str()));
+      RecordError(
+          update_state,
+          base::StringPrintf(
+              "Changes left pending by the update; "
+              "destroy subtrees: %s, destroy nodes: %s, create nodes: %s",
+              destroy_subtree_ids.c_str(), destroy_node_ids.c_str(),
+              create_node_ids.c_str()));
     }
     return !has_pending_changes;
   }
@@ -2146,7 +2157,8 @@
         // If this case occurs, continue so this node isn't left in an
         // inconsistent state, but return failure at the end.
         if (child->parent()) {
-          RecordError(base::StringPrintf("Node %d reparented from %d to %d",
+          RecordError(*update_state,
+                      base::StringPrintf("Node %d reparented from %d to %d",
                                          child->id(), child->parent()->id(),
                                          node->id()));
         } else {
@@ -2728,18 +2740,36 @@
     observer.OnTreeManagerWillBeRemoved(previous_tree_id);
 }
 
-void AXTree::RecordError(std::string new_error) {
+void AXTree::RecordError(const AXTreeUpdateState& update_state,
+                         std::string new_error) {
   if (!error_.empty())
     error_ = error_ + "\n";  // Add visual separation between errors.
   error_ = error_ + new_error;
 
-  if (!error_.empty()) {
-    // Add a crash key so we can figure out why this is happening.
-    static crash_reporter::CrashKeyString<256> ax_tree_error(
-        "ax_tree_unserialize_error");
-    ax_tree_error.Set(error_);
-    LOG(ERROR) << error_;
-  }
+  LOG(ERROR) << new_error;
+
+  static auto* const ax_tree_error_key = base::debug::AllocateCrashKeyString(
+      "ax_tree_error", base::debug::CrashKeySize::Size256);
+  static auto* const ax_tree_update_key = base::debug::AllocateCrashKeyString(
+      "ax_tree_update", base::debug::CrashKeySize::Size256);
+  static auto* const ax_tree_key = base::debug::AllocateCrashKeyString(
+      "ax_tree", base::debug::CrashKeySize::Size256);
+  static auto* const ax_tree_data_key = base::debug::AllocateCrashKeyString(
+      "ax_tree_data", base::debug::CrashKeySize::Size256);
+
+  // Log additional crash keys so we can debug bad tree updates.
+  base::debug::SetCrashKeyString(ax_tree_error_key, new_error);
+  base::debug::SetCrashKeyString(ax_tree_update_key,
+                                 update_state.pending_tree_update.ToString());
+  base::debug::SetCrashKeyString(ax_tree_key, TreeToStringHelper(root_, 1));
+  base::debug::SetCrashKeyString(ax_tree_data_key, data().ToString());
+
+  // In fast-failing-builds, crash immediately with a message, otherwise
+  // rely on AccessibilityFatalError(), which will not crash until multiple
+  // errors occur.
+  SANITIZER_NOTREACHED() << new_error << "\n"
+                         << update_state.pending_tree_update.ToString() << "\n"
+                         << ToString();
 }
 
 }  // namespace ui
diff --git a/ui/accessibility/ax_tree.h b/ui/accessibility/ax_tree.h
index 0686f564..fd17b85a 100644
--- a/ui/accessibility/ax_tree.h
+++ b/ui/accessibility/ax_tree.h
@@ -14,6 +14,7 @@
 #include <vector>
 
 #include "base/containers/flat_map.h"
+#include "base/debug/crash_logging.h"
 #include "base/memory/raw_ptr.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/observer_list.h"
@@ -247,7 +248,9 @@
 
   // Accumulate errors as there can be more than one before Chrome is crashed
   // via AccessibilityFatalError();
-  void RecordError(std::string new_error);
+  // In an AX_FAIL_FAST_BUILD, will assert/crash immediately.
+  void RecordError(const AXTreeUpdateState& update_state,
+                   std::string new_error);
 
   // AXNode::OwnerTree override.
   //
diff --git a/ui/accessibility/ax_tree_unittest.cc b/ui/accessibility/ax_tree_unittest.cc
index 9388778..c40f5b5 100644
--- a/ui/accessibility/ax_tree_unittest.cc
+++ b/ui/accessibility/ax_tree_unittest.cc
@@ -449,6 +449,10 @@
   update.node_id_to_clear = 2;
   update.nodes.resize(1);
   update.nodes[0].id = 3;
+#if defined(AX_FAIL_FAST_BUILD)
+  EXPECT_DEATH_IF_SUPPORTED(tree.Unserialize(update),
+                            "Nodes left pending by the update: 2");
+#else
   EXPECT_FALSE(tree.Unserialize(update));
   ASSERT_EQ("Nodes left pending by the update: 2", tree.error());
   histogram_tester.ExpectUniqueSample(
@@ -456,6 +460,7 @@
       AXTreeUnserializeError::kPendingNodes, 1);
   histogram_tester.ExpectTotalCount(
       "Accessibility.Performance.Tree.Unserialize", 2);
+#endif
 }
 
 TEST(AXTreeTest, LeaveOrphanedNewChildFails) {
@@ -475,6 +480,10 @@
   update.nodes.resize(1);
   update.nodes[0].id = 1;
   update.nodes[0].child_ids.push_back(2);
+#if defined(AX_FAIL_FAST_BUILD)
+  EXPECT_DEATH_IF_SUPPORTED(tree.Unserialize(update),
+                            "Nodes left pending by the update: 2");
+#else
   EXPECT_FALSE(tree.Unserialize(update));
   ASSERT_EQ("Nodes left pending by the update: 2", tree.error());
   histogram_tester.ExpectUniqueSample(
@@ -482,6 +491,7 @@
       AXTreeUnserializeError::kPendingNodes, 1);
   histogram_tester.ExpectTotalCount(
       "Accessibility.Performance.Tree.Unserialize", 2);
+#endif
 }
 
 TEST(AXTreeTest, DuplicateChildIdFails) {
@@ -502,6 +512,10 @@
   update.nodes[0].child_ids.push_back(2);
   update.nodes[0].child_ids.push_back(2);
   update.nodes[1].id = 2;
+#if defined(AX_FAIL_FAST_BUILD)
+  EXPECT_DEATH_IF_SUPPORTED(tree.Unserialize(update),
+                            "Node 1 has duplicate child id 2");
+#else
   EXPECT_FALSE(tree.Unserialize(update));
   ASSERT_EQ("Node 1 has duplicate child id 2", tree.error());
   histogram_tester.ExpectUniqueSample(
@@ -509,6 +523,7 @@
       AXTreeUnserializeError::kDuplicateChild, 1);
   histogram_tester.ExpectTotalCount(
       "Accessibility.Performance.Tree.Unserialize", 1);
+#endif
 }
 
 TEST(AXTreeTest, InvalidReparentingFails) {
@@ -536,6 +551,11 @@
   update.nodes[0].child_ids.push_back(2);
   update.nodes[1].id = 2;
   update.nodes[2].id = 3;
+#if defined(AX_FAIL_FAST_BUILD)
+  EXPECT_DEATH_IF_SUPPORTED(
+      tree.Unserialize(update),
+      "Node 3 is not marked for destruction, would be reparented to 1");
+#else
   EXPECT_FALSE(tree.Unserialize(update));
   ASSERT_EQ("Node 3 is not marked for destruction, would be reparented to 1",
             tree.error());
@@ -544,6 +564,7 @@
       AXTreeUnserializeError::kReparent, 1);
   histogram_tester.ExpectTotalCount(
       "Accessibility.Performance.Tree.Unserialize", 1);
+#endif
 }
 
 TEST(AXTreeTest, NoReparentingOfRootIfNoNewRoot) {
@@ -1205,7 +1226,13 @@
   node2.child_ids.push_back(0);
   initial_state.nodes.push_back(node2);
   ui::AXTree tree;
-  tree.Unserialize(initial_state);
+#if defined(AX_FAIL_FAST_BUILD)
+  EXPECT_DEATH_IF_SUPPORTED(tree.Unserialize(initial_state),
+                            "Node 0 has duplicate child id 0");
+#else
+  EXPECT_FALSE(tree.Unserialize(initial_state));
+  EXPECT_EQ("Node 0 has duplicate child id 0", tree.error());
+#endif
 }
 
 // UAF caught by ax_tree_fuzzer
@@ -1223,7 +1250,13 @@
   initial_state.nodes.push_back(node2);
 
   ui::AXTree tree;
-  tree.Unserialize(initial_state);
+#if defined(AX_FAIL_FAST_BUILD)
+  EXPECT_DEATH_IF_SUPPORTED(tree.Unserialize(initial_state),
+                            "Node 1 has duplicate child id 1");
+#else
+  EXPECT_FALSE(tree.Unserialize(initial_state));
+  EXPECT_EQ("Node 1 has duplicate child id 1", tree.error());
+#endif
 }
 
 TEST(AXTreeTest, RoleAndStateChangeCallbacks) {
@@ -5381,10 +5414,17 @@
   ui::AXNodeData disconnected_node;
   disconnected_node.id = 2;
   tree_update_3.nodes.push_back(disconnected_node);
+#if defined(AX_FAIL_FAST_BUILD)
+  EXPECT_DEATH_IF_SUPPORTED(
+      tree.Unserialize(tree_update_3),
+      "2 will not be in the tree and is not the new root");
+#else
   EXPECT_FALSE(tree.Unserialize(tree_update_3));
+  EXPECT_EQ("2 will not be in the tree and is not the new root", tree.error());
   histogram_tester.ExpectUniqueSample(
       "Accessibility.Reliability.Tree.UnserializeError",
       AXTreeUnserializeError::kNotInTree, 1);
+#endif
 }
 
 }  // namespace ui
diff --git a/ui/base/cocoa/menu_controller.h b/ui/base/cocoa/menu_controller.h
index 6eb5d2e..823d80a 100644
--- a/ui/base/cocoa/menu_controller.h
+++ b/ui/base/cocoa/menu_controller.h
@@ -21,7 +21,7 @@
 // Called as each item is created during menu or submenu creation.
 - (void)controllerWillAddItem:(NSMenuItem*)menuItem
                     fromModel:(ui::MenuModel*)model
-                      atIndex:(NSInteger)index
+                      atIndex:(size_t)index
             withColorProvider:(const ui::ColorProvider*)colorProvider;
 // Called after all menu items in a menu or submenu are created.
 - (void)controllerWillAddMenu:(NSMenu*)menu fromModel:(ui::MenuModel*)model;
diff --git a/ui/base/ime/dummy_text_input_client.cc b/ui/base/ime/dummy_text_input_client.cc
index 309b66f..0b0c567 100644
--- a/ui/base/ime/dummy_text_input_client.cc
+++ b/ui/base/ime/dummy_text_input_client.cc
@@ -82,7 +82,7 @@
 }
 
 bool DummyTextInputClient::GetCompositionCharacterBounds(
-    uint32_t index,
+    size_t index,
     gfx::Rect* rect) const {
   return false;
 }
diff --git a/ui/base/ime/dummy_text_input_client.h b/ui/base/ime/dummy_text_input_client.h
index cfbb4df..26ee8c8 100644
--- a/ui/base/ime/dummy_text_input_client.h
+++ b/ui/base/ime/dummy_text_input_client.h
@@ -42,7 +42,7 @@
   bool CanComposeInline() const override;
   gfx::Rect GetCaretBounds() const override;
   gfx::Rect GetSelectionBoundingBox() const override;
-  bool GetCompositionCharacterBounds(uint32_t index,
+  bool GetCompositionCharacterBounds(size_t index,
                                      gfx::Rect* rect) const override;
   bool HasCompositionText() const override;
   ui::TextInputClient::FocusReason GetFocusReason() const override;
diff --git a/ui/base/ime/fake_text_input_client.cc b/ui/base/ime/fake_text_input_client.cc
index 4c58110..a5d110c 100644
--- a/ui/base/ime/fake_text_input_client.cc
+++ b/ui/base/ime/fake_text_input_client.cc
@@ -93,7 +93,7 @@
   return {};
 }
 
-bool FakeTextInputClient::GetCompositionCharacterBounds(uint32_t index,
+bool FakeTextInputClient::GetCompositionCharacterBounds(size_t index,
                                                         gfx::Rect* rect) const {
   return false;
 }
diff --git a/ui/base/ime/fake_text_input_client.h b/ui/base/ime/fake_text_input_client.h
index ebaed0f..3792ef09 100644
--- a/ui/base/ime/fake_text_input_client.h
+++ b/ui/base/ime/fake_text_input_client.h
@@ -49,7 +49,7 @@
   bool CanComposeInline() const override;
   gfx::Rect GetCaretBounds() const override;
   gfx::Rect GetSelectionBoundingBox() const override;
-  bool GetCompositionCharacterBounds(uint32_t index,
+  bool GetCompositionCharacterBounds(size_t index,
                                      gfx::Rect* rect) const override;
   bool HasCompositionText() const override;
   ui::TextInputClient::FocusReason GetFocusReason() const override;
diff --git a/ui/base/ime/ime_text_span.cc b/ui/base/ime/ime_text_span.cc
index 1fa3bb0..3a54dd87 100644
--- a/ui/base/ime/ime_text_span.cc
+++ b/ui/base/ime/ime_text_span.cc
@@ -10,8 +10,8 @@
 namespace ui {
 
 ImeTextSpan::ImeTextSpan(Type type,
-                         uint32_t start_offset,
-                         uint32_t end_offset,
+                         size_t start_offset,
+                         size_t end_offset,
                          Thickness thickness,
                          UnderlineStyle underline_style,
                          SkColor background_color,
diff --git a/ui/base/ime/ime_text_span.h b/ui/base/ime/ime_text_span.h
index 2d373da..d03f57b 100644
--- a/ui/base/ime/ime_text_span.h
+++ b/ui/base/ime/ime_text_span.h
@@ -48,8 +48,8 @@
 
   explicit ImeTextSpan(
       Type type = Type::kComposition,
-      uint32_t start_offset = 0,
-      uint32_t end_offset = 0,
+      size_t start_offset = 0,
+      size_t end_offset = 0,
       Thickness thickness = Thickness::kThin,
       UnderlineStyle underline_style = UnderlineStyle::kSolid,
       SkColor background_color = SK_ColorTRANSPARENT,
@@ -80,8 +80,8 @@
   bool operator!=(const ImeTextSpan& rhs) const { return !(*this == rhs); }
 
   Type type;
-  uint32_t start_offset;
-  uint32_t end_offset;
+  size_t start_offset;
+  size_t end_offset;
   SkColor underline_color = SK_ColorTRANSPARENT;
   Thickness thickness;
   UnderlineStyle underline_style;
diff --git a/ui/base/ime/mojom/ime_types_mojom_traits.h b/ui/base/ime/mojom/ime_types_mojom_traits.h
index 50489fa..bd8235d 100644
--- a/ui/base/ime/mojom/ime_types_mojom_traits.h
+++ b/ui/base/ime/mojom/ime_types_mojom_traits.h
@@ -43,10 +43,10 @@
 struct COMPONENT_EXPORT(IME_SHARED_MOJOM_TRAITS)
     StructTraits<ui::mojom::ImeTextSpanDataView, ui::ImeTextSpan> {
   static ui::ImeTextSpan::Type type(const ui::ImeTextSpan& c) { return c.type; }
-  static uint32_t start_offset(const ui::ImeTextSpan& c) {
+  static size_t start_offset(const ui::ImeTextSpan& c) {
     return c.start_offset;
   }
-  static uint32_t end_offset(const ui::ImeTextSpan& c) { return c.end_offset; }
+  static size_t end_offset(const ui::ImeTextSpan& c) { return c.end_offset; }
   static uint32_t underline_color(const ui::ImeTextSpan& c) {
     return c.underline_color;
   }
diff --git a/ui/base/ime/text_input_client.h b/ui/base/ime/text_input_client.h
index 8e12d28..5b7b255 100644
--- a/ui/base/ime/text_input_client.h
+++ b/ui/base/ime/text_input_client.h
@@ -146,7 +146,7 @@
   // The |index| is zero-based index of character position in composition text.
   // Returns false if there is no composition text or |index| is out of range.
   // The |rect| is not touched in the case of failure.
-  virtual bool GetCompositionCharacterBounds(uint32_t index,
+  virtual bool GetCompositionCharacterBounds(size_t index,
                                              gfx::Rect* rect) const = 0;
 
   // Returns true if there is composition text.
diff --git a/ui/base/ime/win/tsf_text_store_unittest.cc b/ui/base/ime/win/tsf_text_store_unittest.cc
index e25ca61..d09d1ae7 100644
--- a/ui/base/ime/win/tsf_text_store_unittest.cc
+++ b/ui/base/ime/win/tsf_text_store_unittest.cc
@@ -49,7 +49,7 @@
   MOCK_CONST_METHOD0(CanComposeInline, bool());
   MOCK_CONST_METHOD0(GetCaretBounds, gfx::Rect());
   MOCK_CONST_METHOD0(GetSelectionBoundingBox, gfx::Rect());
-  MOCK_CONST_METHOD2(GetCompositionCharacterBounds, bool(uint32_t, gfx::Rect*));
+  MOCK_CONST_METHOD2(GetCompositionCharacterBounds, bool(size_t, gfx::Rect*));
   MOCK_CONST_METHOD0(HasCompositionText, bool());
   MOCK_CONST_METHOD0(GetFocusReason, ui::TextInputClient::FocusReason());
   MOCK_METHOD0(ShouldDoLearning, bool());
diff --git a/ui/base/interaction/element_identifier.h b/ui/base/interaction/element_identifier.h
index 3542cd458..021c0f1 100644
--- a/ui/base/interaction/element_identifier.h
+++ b/ui/base/interaction/element_identifier.h
@@ -11,6 +11,7 @@
 #include <set>
 
 #include "base/component_export.h"
+#include "base/numerics/safe_conversions.h"
 #include "ui/base/class_property.h"
 
 // Overview:
@@ -246,7 +247,7 @@
  public:
   static int64_t ToInt64(ui::ElementIdentifier x) { return x.GetRawValue(); }
   static ui::ElementIdentifier FromInt64(int64_t x) {
-    return ui::ElementIdentifier::FromRawValue(x);
+    return ui::ElementIdentifier::FromRawValue(base::checked_cast<intptr_t>(x));
   }
 };
 
diff --git a/ui/chromeos/styles/cros_sys_colors.json5 b/ui/chromeos/styles/cros_sys_colors.json5
index 5c31e35..ad949cb 100644
--- a/ui/chromeos/styles/cros_sys_colors.json5
+++ b/ui/chromeos/styles/cros_sys_colors.json5
@@ -185,6 +185,12 @@
     // TODO(b/224402466): Add cros.sys.warning
     // TODO(b/224402466): Add cros.sys.success
 
+    // These values have a very specific meaning and should never be tinted.
+    'privacy-indicator': {
+      light: '#146c2e',
+      dark: '#37be5f',
+    },
+
     /* Effects */
     'hover-on-prominent': {
       light: 'rgba($cros.ref.neutral10.rgb, 0.10)',
diff --git a/ui/compositor/layer_animation_element.cc b/ui/compositor/layer_animation_element.cc
index c27bdfb9..82bfcc5 100644
--- a/ui/compositor/layer_animation_element.cc
+++ b/ui/compositor/layer_animation_element.cc
@@ -385,10 +385,10 @@
     DCHECK_EQ(start_.step_count(), target_.step_count());
     for (auto i = 0; i < static_cast<int>(start_.step_count()); ++i) {
       gradient_mask.AddStep(
-          gfx::Tween::FloatValueBetween(t, start_.steps()[i].percent,
-                                        target_.steps()[i].percent),
+          gfx::Tween::FloatValueBetween(t, start_.steps()[i].fraction,
+                                        target_.steps()[i].fraction),
           gfx::Tween::IntValueBetween(t, start_.steps()[i].alpha,
-                                        target_.steps()[i].alpha));
+                                      target_.steps()[i].alpha));
     }
 
     delegate->SetGradientMaskFromAnimation(
diff --git a/ui/compositor/layer_animation_element_unittest.cc b/ui/compositor/layer_animation_element_unittest.cc
index c412c9e..3d1fe60 100644
--- a/ui/compositor/layer_animation_element_unittest.cc
+++ b/ui/compositor/layer_animation_element_unittest.cc
@@ -475,9 +475,9 @@
   gfx::LinearGradient start(45);
   start.AddStep(0, 0);
   gfx::LinearGradient target(135);
-  target.AddStep(50, 255);
+  target.AddStep(.5, 255);
   gfx::LinearGradient middle(90);
-  middle.AddStep(25, 127);
+  middle.AddStep(.25, 127);
 
   base::TimeTicks start_time;
   base::TimeDelta delta = base::Seconds(1);
diff --git a/ui/compositor/layer_unittest.cc b/ui/compositor/layer_unittest.cc
index 6497d7d1..fc4de27 100644
--- a/ui/compositor/layer_unittest.cc
+++ b/ui/compositor/layer_unittest.cc
@@ -773,7 +773,7 @@
   gfx::Rect clip_rect(1, 1, 2, 2);
 
   gfx::LinearGradient gradient_mask(45);
-  gradient_mask.AddStep(50, 50);
+  gradient_mask.AddStep(.5, 50);
 
   layer->SetTransform(transform);
   layer->SetColor(SK_ColorRED);
@@ -816,7 +816,7 @@
   layer->SetRoundedCornerRadius({3, 6, 9, 12});
 
   gradient_mask.set_angle(90);
-  gradient_mask.AddStep(90, 30);
+  gradient_mask.AddStep(.9, 30);
   layer->SetGradientMask(gradient_mask);
 
   // The clone is an independent copy, so state changes do not propagate.
@@ -1064,7 +1064,7 @@
   constexpr viz::SubtreeCaptureId kSubtreeCaptureId(22);
   l1->SetSubtreeCaptureId(kSubtreeCaptureId);
   gfx::LinearGradient gradient_mask(45);
-  gradient_mask.AddStep(50, 50);
+  gradient_mask.AddStep(.5, 50);
   l1->SetGradientMask(gradient_mask);
 
   EXPECT_EQ(gfx::Point3F(), l1->cc_layer_for_testing()->transform_origin());
@@ -1348,7 +1348,7 @@
 TEST_F(LayerWithDelegateTest, GradientMask) {
   gfx::Rect layer_bounds(10, 20, 100, 100);
   gfx::LinearGradient gradient_mask;
-  gradient_mask.AddStep(50, 50);
+  gradient_mask.AddStep(.5, 50);
 
   auto layer = std::make_unique<Layer>(LAYER_TEXTURED);
 
diff --git a/ui/gfx/geometry/linear_gradient.cc b/ui/gfx/geometry/linear_gradient.cc
index 05f9c39..ef7ca260 100644
--- a/ui/gfx/geometry/linear_gradient.cc
+++ b/ui/gfx/geometry/linear_gradient.cc
@@ -28,16 +28,16 @@
 
 LinearGradient::LinearGradient(const LinearGradient& copy) = default;
 
-void LinearGradient::AddStep(float percent, uint8_t alpha) {
+void LinearGradient::AddStep(float fraction, uint8_t alpha) {
   DCHECK_LT(step_count_, kMaxStepSize);
-  DCHECK_GE(percent, 0);
-  DCHECK_LE(percent, 100);
-  // make sure the step's percent is monotonically increasing.
-  DCHECK(step_count_ ? steps_[step_count_ - 1].percent < percent : true)
+  DCHECK_GE(fraction, 0);
+  DCHECK_LE(fraction, 1);
+  // make sure the step's fraction is monotonically increasing.
+  DCHECK(step_count_ ? steps_[step_count_ - 1].fraction < fraction : true)
       << base::StringPrintf("prev[%zu]=%f, next[%zu]=%f", step_count_ - 1,
-                            steps_[step_count_ - 1].percent, step_count_,
-                            steps_[step_count_].percent);
-  steps_[step_count_].percent = percent;
+                            steps_[step_count_ - 1].fraction, step_count_,
+                            fraction);
+  steps_[step_count_].fraction = fraction;
   steps_[step_count_++].alpha = alpha;
 }
 
@@ -45,7 +45,7 @@
   std::reverse(steps_.begin(), steps_.end());
   std::rotate(steps_.begin(), steps_.end() - step_count_, steps_.end());
   for (size_t i = 0; i < step_count_; i++)
-    steps_[i].percent = 100.f - steps_[i].percent;
+    steps_[i].fraction = 1.f - steps_[i].fraction;
 }
 
 void LinearGradient::Transform(const gfx::Transform& transform) {
@@ -70,7 +70,7 @@
   for (size_t i = 0; i < step_count_; ++i) {
     if (i)
       result += " - ";
-    result += base::StringPrintf("%f:%u", steps_[i].percent, steps_[i].alpha);
+    result += base::StringPrintf("%f:%u", steps_[i].fraction, steps_[i].alpha);
   }
   return result + "]}";
 }
diff --git a/ui/gfx/geometry/linear_gradient.h b/ui/gfx/geometry/linear_gradient.h
index cb638fe..aa57451 100644
--- a/ui/gfx/geometry/linear_gradient.h
+++ b/ui/gfx/geometry/linear_gradient.h
@@ -26,8 +26,8 @@
 class GEOMETRY_SKIA_EXPORT LinearGradient {
  public:
   struct Step {
-    // Percent that defines a position in diagonal, from 0 to 100.
-    float percent = 0;
+    // Fraction that defines a position in diagonal, from 0 to 1.
+    float fraction = 0;
     // Alpha, from 0 to 255.
     uint8_t alpha = 0;
   };
@@ -44,7 +44,7 @@
   bool IsEmpty() const { return !step_count_; }
 
   // Add a new step. Adding more than 6 results in DCHECK or ignored.
-  void AddStep(float percent, uint8_t alpha);
+  void AddStep(float fraction, uint8_t alpha);
 
   // Get step information.
   const StepArray& steps() const { return steps_; }
@@ -72,7 +72,7 @@
 
 inline bool operator==(const LinearGradient::Step& lhs,
                        const LinearGradient::Step& rhs) {
-  return lhs.percent == rhs.percent && lhs.alpha == rhs.alpha;
+  return lhs.fraction == rhs.fraction && lhs.alpha == rhs.alpha;
 }
 
 inline bool operator==(const LinearGradient& lhs, const LinearGradient& rhs) {
diff --git a/ui/gfx/geometry/linear_gradient_unittest.cc b/ui/gfx/geometry/linear_gradient_unittest.cc
index 79c6ffb6..bf29ee5 100644
--- a/ui/gfx/geometry/linear_gradient_unittest.cc
+++ b/ui/gfx/geometry/linear_gradient_unittest.cc
@@ -15,31 +15,31 @@
   LinearGradient gradient(45);
   EXPECT_TRUE(gradient.IsEmpty());
 
-  gradient.AddStep(10, 0);
-  gradient.AddStep(50, 50);
-  gradient.AddStep(80, 1);
+  gradient.AddStep(.1, 0);
+  gradient.AddStep(.5, 50);
+  gradient.AddStep(.8, 1);
 
   EXPECT_FALSE(gradient.IsEmpty());
   EXPECT_EQ(45, gradient.angle());
   EXPECT_EQ(3u, gradient.step_count());
-  EXPECT_EQ(gradient.steps()[0].percent, 10);
+  EXPECT_FLOAT_EQ(gradient.steps()[0].fraction, .1);
   EXPECT_EQ(gradient.steps()[0].alpha, 0);
-  EXPECT_EQ(gradient.steps()[1].percent, 50);
+  EXPECT_FLOAT_EQ(gradient.steps()[1].fraction, .5);
   EXPECT_EQ(gradient.steps()[1].alpha, 50);
-  EXPECT_EQ(gradient.steps()[2].percent, 80);
+  EXPECT_FLOAT_EQ(gradient.steps()[2].fraction, .8);
   EXPECT_EQ(gradient.steps()[2].alpha, 1);
 
   LinearGradient gradient2(90);
-  gradient2.AddStep(10, 0);
-  gradient2.AddStep(50, 50);
-  gradient2.AddStep(80, 1);
+  gradient2.AddStep(.1, 0);
+  gradient2.AddStep(.5, 50);
+  gradient2.AddStep(.8, 1);
 
   EXPECT_NE(gradient, gradient2);
 
   gradient2.set_angle(45);
   EXPECT_EQ(gradient, gradient2);
 
-  gradient2.AddStep(90, 0);
+  gradient2.AddStep(.9, 0);
   EXPECT_NE(gradient, gradient2);
 }
 
@@ -48,20 +48,20 @@
   // Make sure reversing an empty LinearGradient doesn't cause an issue.
   gradient.ReverseSteps();
 
-  gradient.AddStep(10, 0);
-  gradient.AddStep(50, 50);
-  gradient.AddStep(80, 1);
+  gradient.AddStep(.1, 0);
+  gradient.AddStep(.5, 50);
+  gradient.AddStep(.8, 1);
 
   gradient.ReverseSteps();
 
   EXPECT_EQ(45, gradient.angle());
   EXPECT_EQ(3u, gradient.step_count());
 
-  EXPECT_EQ(gradient.steps()[0].percent, 20);
+  EXPECT_FLOAT_EQ(gradient.steps()[0].fraction, .2);
   EXPECT_EQ(gradient.steps()[0].alpha, 1);
-  EXPECT_EQ(gradient.steps()[1].percent, 50);
+  EXPECT_FLOAT_EQ(gradient.steps()[1].fraction, .5);
   EXPECT_EQ(gradient.steps()[1].alpha, 50);
-  EXPECT_EQ(gradient.steps()[2].percent, 90);
+  EXPECT_FLOAT_EQ(gradient.steps()[2].fraction, .9);
   EXPECT_EQ(gradient.steps()[2].alpha, 0);
 }
 
diff --git a/ui/gfx/gpu_memory_buffer.h b/ui/gfx/gpu_memory_buffer.h
index fd5f5c1..1ab6446a 100644
--- a/ui/gfx/gpu_memory_buffer.h
+++ b/ui/gfx/gpu_memory_buffer.h
@@ -74,7 +74,7 @@
   GpuMemoryBufferId id{0};
   base::UnsafeSharedMemoryRegion region;
   uint32_t offset = 0;
-  int32_t stride = 0;
+  uint32_t stride = 0;
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_FUCHSIA)
   NativePixmapHandle native_pixmap_handle;
 #elif BUILDFLAG(IS_MAC)
diff --git a/ui/gfx/image/image_util.cc b/ui/gfx/image/image_util.cc
index bb49ea5..63305ce 100644
--- a/ui/gfx/image/image_util.cc
+++ b/ui/gfx/image/image_util.cc
@@ -8,17 +8,13 @@
 
 #include <algorithm>
 #include <memory>
-#include <vector>
 
-#include "base/check.h"
 #include "base/cxx17_backports.h"
-#include "base/numerics/safe_conversions.h"
 #include "build/build_config.h"
 #include "skia/ext/image_operations.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "ui/gfx/codec/jpeg_codec.h"
 #include "ui/gfx/codec/webp_codec.h"
-#include "ui/gfx/geometry/size.h"
 #include "ui/gfx/image/image.h"
 #include "ui/gfx/image/image_skia.h"
 #include "ui/gfx/image/image_skia_rep.h"
@@ -162,61 +158,4 @@
   *right = bitmap.width() - 1 - x;
 }
 
-SkBitmap ResizeImageToLargestSize(const SkBitmap& image,
-                                  uint32_t max_image_size) {
-  uint32_t max_dimension = std::max(image.width(), image.height());
-  if (max_dimension <= max_image_size)
-    return image;
-  // Proportionally resize the minimal image to fit in a box of size
-  // max_image_size.
-  return skia::ImageOperations::Resize(
-      image, skia::ImageOperations::RESIZE_BEST,
-      base::checked_cast<uint32_t>(image.width()) * max_image_size /
-          max_dimension,
-      base::checked_cast<uint32_t>(image.height()) * max_image_size /
-          max_dimension);
-}
-
-void FilterAndResizeImagesForMaximalSize(
-    const std::vector<SkBitmap>& images,
-    uint32_t max_image_size,
-    std::vector<SkBitmap>& filtered_images,
-    std::vector<gfx::Size>& filtered_image_sizes) {
-  filtered_images.clear();
-  filtered_image_sizes.clear();
-
-  if (images.empty())
-    return;
-
-  const SkBitmap* min_image = nullptr;
-  uint32_t min_image_size = std::numeric_limits<uint32_t>::max();
-  // Filter the images by |max_image_size|, and also identify the smallest
-  // image in case all the images are bigger than |max_image_size|.
-  for (const SkBitmap& image : images) {
-    uint32_t current_size = std::max(image.width(), image.height());
-    if (current_size < min_image_size) {
-      min_image = &image;
-      min_image_size = current_size;
-    }
-    if (base::checked_cast<uint32_t>(image.width()) <= max_image_size &&
-        base::checked_cast<uint32_t>(image.height()) <= max_image_size) {
-      filtered_images.emplace_back(image);
-      filtered_image_sizes.emplace_back(
-          gfx::Size(image.width(), image.height()));
-    }
-  }
-  if (!filtered_images.empty())
-    return;
-  // Proportionally resize the minimal image to fit in a box of size
-  // |max_image_size|.
-  DCHECK(min_image);
-  SkBitmap resized = ResizeImageToLargestSize(*min_image, max_image_size);
-  // Drop null or empty SkBitmap.
-  if (resized.drawsNothing())
-    return;
-  filtered_images.emplace_back(resized);
-  filtered_image_sizes.emplace_back(
-      gfx::Size(min_image->width(), min_image->height()));
-}
-
 }  // namespace gfx
diff --git a/ui/gfx/image/image_util.h b/ui/gfx/image/image_util.h
index 140403e50..aa954a2 100644
--- a/ui/gfx/image/image_util.h
+++ b/ui/gfx/image/image_util.h
@@ -9,8 +9,6 @@
 
 #include <vector>
 
-#include "third_party/skia/include/core/SkBitmap.h"
-#include "ui/gfx/geometry/size.h"
 #include "ui/gfx/gfx_export.h"
 
 namespace gfx {
@@ -76,24 +74,6 @@
                                                      int max_height,
                                                      int max_area);
 
-// Proportionally resizes the |image| to fit in a box of size
-// |max_image_size|. If the |image| already fits, it is
-// returned without any resizing.
-GFX_EXPORT SkBitmap ResizeImageToLargestSize(const SkBitmap& image,
-                                             uint32_t max_image_size);
-
-// Filters the array of bitmaps, removing all images that do not fit in a box of
-// size |max_image_size|. Returns the result if it is not empty. Otherwise,
-// find the smallest image in the array and resize it proportionally to fit
-// in a box of size |max_image_size|.
-// Sets |filtered_image_sizes| to the sizes of |filtered_images| before
-// resizing. Both output vectors are guaranteed to have the same size.
-GFX_EXPORT void FilterAndResizeImagesForMaximalSize(
-    const std::vector<SkBitmap>& images,
-    uint32_t max_image_size,
-    std::vector<SkBitmap>& filtered_images,
-    std::vector<gfx::Size>& filtered_image_sizes);
-
 }  // namespace gfx
 
 #endif  // UI_GFX_IMAGE_IMAGE_UTIL_H_
diff --git a/ui/gfx/image/image_util_unittest.cc b/ui/gfx/image/image_util_unittest.cc
index 5244772..0ef65fd 100644
--- a/ui/gfx/image/image_util_unittest.cc
+++ b/ui/gfx/image/image_util_unittest.cc
@@ -14,17 +14,6 @@
 #include "ui/gfx/image/image_unittest_util.h"
 #include "ui/gfx/image/resize_image_dimensions.h"
 
-namespace {
-
-SkBitmap CreateRandomImage(int size, SkColor color) {
-  SkBitmap bitmap;
-  bitmap.allocN32Pixels(size, size);
-  bitmap.eraseColor(color);
-  return bitmap;
-}
-
-}  // namespace
-
 TEST(ImageUtilTest, JPEGEncodeAndDecode) {
   gfx::Image original = gfx::test::CreateImage(100, 100);
 
@@ -168,70 +157,3 @@
   EXPECT_EQ(resized_image.Width(), 400);
   EXPECT_EQ(resized_image.Height(), 400);
 }
-
-TEST(ImageUtilTest, NoFilterNoResize) {
-  std::vector<SkBitmap> previous_images;
-  previous_images.push_back(CreateRandomImage(1, SK_ColorBLACK));
-  previous_images.push_back(CreateRandomImage(2, SK_ColorGRAY));
-  previous_images.push_back(CreateRandomImage(3, SK_ColorBLUE));
-
-  std::vector<SkBitmap> filtered_images;
-  std::vector<gfx::Size> filtered_sizes;
-  gfx::FilterAndResizeImagesForMaximalSize(
-      previous_images, /*max_image_size=*/5, filtered_images, filtered_sizes);
-
-  // No image gets filtered.
-  EXPECT_EQ(filtered_images.size(), previous_images.size());
-  EXPECT_EQ(3u, filtered_sizes.size());
-  EXPECT_EQ(SK_ColorBLACK, filtered_images.at(0).getColor(0, 0));
-  EXPECT_EQ(SK_ColorGRAY, filtered_images.at(1).getColor(1, 1));
-  EXPECT_EQ(SK_ColorBLUE, filtered_images.at(2).getColor(2, 2));
-}
-
-TEST(ImageUtilTest, FilterImageNoResize) {
-  std::vector<SkBitmap> previous_images;
-  previous_images.push_back(CreateRandomImage(1, SK_ColorBLACK));
-  previous_images.push_back(CreateRandomImage(3, SK_ColorGRAY));
-  previous_images.push_back(CreateRandomImage(4, SK_ColorBLUE));
-
-  std::vector<SkBitmap> filtered_images;
-  std::vector<gfx::Size> filtered_sizes;
-  gfx::FilterAndResizeImagesForMaximalSize(
-      previous_images, /*max_image_size=*/2, filtered_images, filtered_sizes);
-
-  // No image gets filtered.
-  EXPECT_EQ(1u, filtered_images.size());
-  EXPECT_EQ(1u, filtered_sizes.size());
-  EXPECT_EQ(SK_ColorBLACK, filtered_images.at(0).getColor(0, 0));
-  // Verify grey and blue SkBitmaps are not in the filtered image.
-  EXPECT_NE(SK_ColorGRAY, filtered_images.at(0).getColor(0, 0));
-  EXPECT_NE(SK_ColorBLUE, filtered_images.at(0).getColor(0, 0));
-}
-
-TEST(ImageUtilTest, AllFilterOnlyResize) {
-  std::vector<SkBitmap> previous_images;
-  previous_images.push_back(CreateRandomImage(6, SK_ColorBLACK));
-  previous_images.push_back(CreateRandomImage(5, SK_ColorGRAY));
-  previous_images.push_back(CreateRandomImage(4, SK_ColorBLUE));
-
-  std::vector<SkBitmap> filtered_images;
-  std::vector<gfx::Size> filtered_sizes;
-  gfx::FilterAndResizeImagesForMaximalSize(
-      previous_images, /*max_image_size=*/3, filtered_images, filtered_sizes);
-
-  // Only 1 image gets resized, and it is the smallest one (the blue one).
-  // All other images are not filtered.
-  EXPECT_EQ(1u, filtered_images.size());
-  EXPECT_EQ(1u, filtered_sizes.size());
-  // Verify that resizing happens to proper size.
-  EXPECT_EQ(3, filtered_images.at(0).dimensions().width());
-  EXPECT_EQ(3, filtered_images.at(0).dimensions().height());
-  // Original sizes.
-  EXPECT_EQ(4, filtered_sizes.at(0).width());
-  EXPECT_EQ(4, filtered_sizes.at(0).height());
-  // Verify grey and black SkBitmaps are not in the filtered image.
-  // Only blue image is filtered.
-  EXPECT_EQ(SK_ColorBLUE, filtered_images.at(0).getColor(0, 0));
-  EXPECT_NE(SK_ColorGRAY, filtered_images.at(0).getColor(0, 0));
-  EXPECT_NE(SK_ColorBLACK, filtered_images.at(0).getColor(0, 0));
-}
diff --git a/ui/gfx/mojom/linear_gradient.mojom b/ui/gfx/mojom/linear_gradient.mojom
index c95519c..b535aa19 100644
--- a/ui/gfx/mojom/linear_gradient.mojom
+++ b/ui/gfx/mojom/linear_gradient.mojom
@@ -6,7 +6,7 @@
 
 // See ui/gfx/geometry/linear_gradient.h.
 struct Step {
-  float percent;
+  float fraction;
   uint8 alpha;
 };
 
diff --git a/ui/gfx/mojom/linear_gradient_mojom_traits.cc b/ui/gfx/mojom/linear_gradient_mojom_traits.cc
index 07d741f..25057da6 100644
--- a/ui/gfx/mojom/linear_gradient_mojom_traits.cc
+++ b/ui/gfx/mojom/linear_gradient_mojom_traits.cc
@@ -18,7 +18,7 @@
     return false;
 
   for (int i = 0; i < data.step_count(); ++i) {
-    out->AddStep(steps_data[i].percent, steps_data[i].alpha);
+    out->AddStep(steps_data[i].fraction, steps_data[i].alpha);
   }
   out->set_angle(data.angle());
 
diff --git a/ui/gfx/mojom/linear_gradient_mojom_traits.h b/ui/gfx/mojom/linear_gradient_mojom_traits.h
index 09412c3..1480d0ad 100644
--- a/ui/gfx/mojom/linear_gradient_mojom_traits.h
+++ b/ui/gfx/mojom/linear_gradient_mojom_traits.h
@@ -12,8 +12,8 @@
 
 template <>
 struct StructTraits<gfx::mojom::StepDataView, gfx::LinearGradient::Step> {
-  static float percent(const gfx::LinearGradient::Step& input) {
-    return input.percent;
+  static float fraction(const gfx::LinearGradient::Step& input) {
+    return input.fraction;
   }
 
   static uint8_t alpha(const gfx::LinearGradient::Step& input) {
@@ -22,7 +22,7 @@
 
   static bool Read(gfx::mojom::StepDataView data,
                    gfx::LinearGradient::Step* out) {
-    out->percent = data.percent();
+    out->fraction = data.fraction();
     out->alpha = data.alpha();
     return true;
   }
diff --git a/ui/gfx/mojom/mojom_traits_unittest.cc b/ui/gfx/mojom/mojom_traits_unittest.cc
index 2ba0059..40681ac 100644
--- a/ui/gfx/mojom/mojom_traits_unittest.cc
+++ b/ui/gfx/mojom/mojom_traits_unittest.cc
@@ -147,7 +147,7 @@
 TEST_F(StructTraitsTest, GpuMemoryBufferHandle) {
   const gfx::GpuMemoryBufferId kId(99);
   const uint32_t kOffset = 126;
-  const int32_t kStride = 256;
+  const uint32_t kStride = 256;
   base::UnsafeSharedMemoryRegion shared_memory_region =
       base::UnsafeSharedMemoryRegion::Create(1024);
   ASSERT_TRUE(shared_memory_region.IsValid());
diff --git a/ui/gl/generate_bindings.py b/ui/gl/generate_bindings.py
index 1203312c..aceaf5eb 100755
--- a/ui/gl/generate_bindings.py
+++ b/ui/gl/generate_bindings.py
@@ -2819,6 +2819,7 @@
   'EGL_ANGLE_create_context_webgl_compatibility',
   'EGL_ANGLE_external_context_and_surface',
   'EGL_ANGLE_keyed_mutex',
+  'EGL_ANGLE_program_cache_control',
   'EGL_ANGLE_robust_resource_initialization',
   'EGL_ANGLE_surface_orientation',
   'EGL_ANGLE_window_fixed_size',
diff --git a/ui/gl/gl_bindings_autogen_egl.cc b/ui/gl/gl_bindings_autogen_egl.cc
index e203c35..7b03af65 100644
--- a/ui/gl/gl_bindings_autogen_egl.cc
+++ b/ui/gl/gl_bindings_autogen_egl.cc
@@ -302,6 +302,8 @@
       gfx::HasExtension(extensions, "EGL_ANGLE_keyed_mutex");
   b_EGL_ANGLE_power_preference =
       gfx::HasExtension(extensions, "EGL_ANGLE_power_preference");
+  b_EGL_ANGLE_program_cache_control =
+      gfx::HasExtension(extensions, "EGL_ANGLE_program_cache_control");
   b_EGL_ANGLE_query_surface_pointer =
       gfx::HasExtension(extensions, "EGL_ANGLE_query_surface_pointer");
   b_EGL_ANGLE_robust_resource_initialization =
diff --git a/ui/gl/gl_bindings_autogen_egl.h b/ui/gl/gl_bindings_autogen_egl.h
index 2fdffb0..0113878b 100644
--- a/ui/gl/gl_bindings_autogen_egl.h
+++ b/ui/gl/gl_bindings_autogen_egl.h
@@ -355,6 +355,7 @@
   bool b_EGL_ANGLE_external_context_and_surface;
   bool b_EGL_ANGLE_keyed_mutex;
   bool b_EGL_ANGLE_power_preference;
+  bool b_EGL_ANGLE_program_cache_control;
   bool b_EGL_ANGLE_query_surface_pointer;
   bool b_EGL_ANGLE_robust_resource_initialization;
   bool b_EGL_ANGLE_stream_producer_d3d_texture;
diff --git a/ui/gl/gl_context_egl.cc b/ui/gl/gl_context_egl.cc
index d210618..f22f7cc 100644
--- a/ui/gl/gl_context_egl.cc
+++ b/ui/gl/gl_context_egl.cc
@@ -87,6 +87,11 @@
 #define EGL_CONTEXT_VIRTUALIZATION_GROUP_ANGLE 0x3481
 #endif /* EGL_ANGLE_context_virtualization */
 
+#ifndef EGL_ANGLE_program_cache_control
+#define EGL_ANGLE_program_cache_control 1
+#define EGL_CONTEXT_PROGRAM_BINARY_CACHE_ENABLED_ANGLE 0x3459
+#endif /* EGL_ANGLE_program_cache_control */
+
 using ui::GetLastEGLErrorString;
 
 namespace gl {
@@ -310,6 +315,14 @@
         static_cast<EGLint>(attribs.angle_context_virtualization_group_number));
   }
 
+  // Skia manages program cache by itself.
+  // For WebGL program, it should manage program by itself too.
+  if (gl_display_->ext->b_EGL_ANGLE_program_cache_control) {
+    context_attributes.push_back(
+        EGL_CONTEXT_PROGRAM_BINARY_CACHE_ENABLED_ANGLE);
+    context_attributes.push_back(EGL_FALSE);
+  }
+
   // Append final EGL_NONE to signal the context attributes are finished
   context_attributes.push_back(EGL_NONE);
   context_attributes.push_back(EGL_NONE);
diff --git a/ui/platform_window/platform_window_init_properties.h b/ui/platform_window/platform_window_init_properties.h
index b67f867..51d227b 100644
--- a/ui/platform_window/platform_window_init_properties.h
+++ b/ui/platform_window/platform_window_init_properties.h
@@ -11,6 +11,7 @@
 #include "base/memory/raw_ptr.h"
 #include "build/build_config.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/skia/include/core/SkColor.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/native_widget_types.h"
 
@@ -118,7 +119,7 @@
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
   bool prefer_dark_theme = false;
   raw_ptr<gfx::ImageSkia> icon = nullptr;
-  absl::optional<int> background_color;
+  absl::optional<SkColor> background_color;
 
   // Specifies the res_name and res_class fields,
   // respectively, of the WM_CLASS window property. Controls window grouping
diff --git a/ui/views/BUILD.gn b/ui/views/BUILD.gn
index cf74f307..cbcbd2e 100644
--- a/ui/views/BUILD.gn
+++ b/ui/views/BUILD.gn
@@ -500,7 +500,12 @@
   ]
 
   sources += get_target_outputs(":views_vector_icons")
-  configs += [ "//build/config:precompiled_headers" ]
+
+  configs += [
+    "//build/config:precompiled_headers",
+    "//build/config/compiler:prevent_unsafe_narrowing",
+  ]
+
   defines = [ "VIEWS_IMPLEMENTATION" ]
 
   deps = [
diff --git a/ui/views/cocoa/native_widget_mac_ns_window_host.mm b/ui/views/cocoa/native_widget_mac_ns_window_host.mm
index aebada61..cdaf472a 100644
--- a/ui/views/cocoa/native_widget_mac_ns_window_host.mm
+++ b/ui/views/cocoa/native_widget_mac_ns_window_host.mm
@@ -11,6 +11,7 @@
 #include "base/containers/contains.h"
 #include "base/mac/foundation_util.h"
 #include "base/no_destructor.h"
+#include "base/numerics/safe_conversions.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/time/time.h"
 #include "components/remote_cocoa/app_shim/mouse_capture.h"
@@ -1170,7 +1171,8 @@
                                display_.color_spaces());
   }
   if (display_id_changed) {
-    display_link_ = ui::DisplayLinkMac::GetForDisplay(display_.id());
+    display_link_ = ui::DisplayLinkMac::GetForDisplay(
+        base::checked_cast<CGDirectDisplayID>(display_.id()));
     if (!display_link_) {
       // Note that on some headless systems, the display link will fail to be
       // created, so this should not be a fatal error.
diff --git a/ui/views/controls/menu/menu_runner_impl_cocoa.mm b/ui/views/controls/menu/menu_runner_impl_cocoa.mm
index 46722c1..a6dec3b 100644
--- a/ui/views/controls/menu/menu_runner_impl_cocoa.mm
+++ b/ui/views/controls/menu/menu_runner_impl_cocoa.mm
@@ -9,6 +9,7 @@
 #include "base/i18n/rtl.h"
 #include "base/mac/mac_util.h"
 #import "base/message_loop/message_pump_mac.h"
+#include "base/numerics/safe_conversions.h"
 #import "skia/ext/skia_utils_mac.h"
 #import "ui/base/cocoa/cocoa_base_utils.h"
 #import "ui/base/cocoa/menu_controller.h"
@@ -212,7 +213,7 @@
 
 - (void)controllerWillAddItem:(NSMenuItem*)menuItem
                     fromModel:(ui::MenuModel*)model
-                      atIndex:(NSInteger)index
+                      atIndex:(size_t)index
             withColorProvider:(const ui::ColorProvider*)colorProvider {
   if (model->IsNewFeatureAt(index)) {
     NSMutableAttributedString* attrTitle = [[[NSMutableAttributedString alloc]
@@ -262,7 +263,9 @@
         if ([menu respondsToSelector:@selector(_menuImpl)]) {
           NSCarbonMenuImpl* menuImpl = [menu_obj _menuImpl];
           if ([menuImpl respondsToSelector:@selector(highlightItemAtIndex:)]) {
-            [menuImpl highlightItemAtIndex:alerted_index.value()];
+            const auto index =
+                base::checked_cast<NSInteger>(alerted_index.value());
+            [menuImpl highlightItemAtIndex:index];
           }
         }
       }
@@ -366,8 +369,10 @@
 // Returns the first item in |menu_controller|'s menu that will be checked.
 NSMenuItem* FirstCheckedItem(MenuControllerCocoa* menu_controller) {
   for (NSMenuItem* item in [[menu_controller menu] itemArray]) {
-    if ([menu_controller model]->IsItemCheckedAt([item tag]))
+    if ([menu_controller model]->IsItemCheckedAt(
+            base::checked_cast<size_t>([item tag]))) {
       return item;
+    }
   }
   return nil;
 }
diff --git a/ui/views/controls/prefix_selector.cc b/ui/views/controls/prefix_selector.cc
index 37cc84dd..a65c9e2 100644
--- a/ui/views/controls/prefix_selector.cc
+++ b/ui/views/controls/prefix_selector.cc
@@ -93,7 +93,7 @@
   return gfx::Rect();
 }
 
-bool PrefixSelector::GetCompositionCharacterBounds(uint32_t index,
+bool PrefixSelector::GetCompositionCharacterBounds(size_t index,
                                                    gfx::Rect* rect) const {
   // TextInputClient::GetCompositionCharacterBounds is expected to fill |rect|
   // in screen coordinates and GetCaretBounds returns screen coordinates.
diff --git a/ui/views/controls/prefix_selector.h b/ui/views/controls/prefix_selector.h
index 9b5977f..ce19d4d8d 100644
--- a/ui/views/controls/prefix_selector.h
+++ b/ui/views/controls/prefix_selector.h
@@ -59,7 +59,7 @@
   bool CanComposeInline() const override;
   gfx::Rect GetCaretBounds() const override;
   gfx::Rect GetSelectionBoundingBox() const override;
-  bool GetCompositionCharacterBounds(uint32_t index,
+  bool GetCompositionCharacterBounds(size_t index,
                                      gfx::Rect* rect) const override;
   bool HasCompositionText() const override;
   FocusReason GetFocusReason() const override;
diff --git a/ui/views/controls/textfield/textfield.cc b/ui/views/controls/textfield/textfield.cc
index e051ad7b..7efde3e 100644
--- a/ui/views/controls/textfield/textfield.cc
+++ b/ui/views/controls/textfield/textfield.cc
@@ -1459,7 +1459,7 @@
   return gfx::Rect();
 }
 
-bool Textfield::GetCompositionCharacterBounds(uint32_t index,
+bool Textfield::GetCompositionCharacterBounds(size_t index,
                                               gfx::Rect* rect) const {
   DCHECK(rect);
   if (!HasCompositionText())
diff --git a/ui/views/controls/textfield/textfield.h b/ui/views/controls/textfield/textfield.h
index 8703148..9768a28 100644
--- a/ui/views/controls/textfield/textfield.h
+++ b/ui/views/controls/textfield/textfield.h
@@ -421,7 +421,7 @@
   bool CanComposeInline() const override;
   gfx::Rect GetCaretBounds() const override;
   gfx::Rect GetSelectionBoundingBox() const override;
-  bool GetCompositionCharacterBounds(uint32_t index,
+  bool GetCompositionCharacterBounds(size_t index,
                                      gfx::Rect* rect) const override;
   bool HasCompositionText() const override;
   FocusReason GetFocusReason() const override;
diff --git a/ui/views/style/platform_style_mac.mm b/ui/views/style/platform_style_mac.mm
index 218163c..227be4a8 100644
--- a/ui/views/style/platform_style_mac.mm
+++ b/ui/views/style/platform_style_mac.mm
@@ -4,6 +4,7 @@
 
 #include "ui/views/style/platform_style.h"
 
+#include "base/numerics/safe_conversions.h"
 #include "base/strings/sys_string_conversions.h"
 #include "ui/base/buildflags.h"
 #include "ui/gfx/color_utils.h"
@@ -71,16 +72,18 @@
 
   base::ScopedCFTypeRef<CFStringRef> cf_string(CFStringCreateWithCharacters(
       kCFAllocatorDefault, reinterpret_cast<const UniChar*>(text.data()),
-      text.size()));
+      base::checked_cast<CFIndex>(text.size())));
   CFRange range_to_delete = CFStringGetRangeOfCharacterClusterAtIndex(
-      cf_string, cursor_position - 1, kCFStringBackwardDeletionCluster);
+      cf_string, base::checked_cast<CFIndex>(cursor_position - 1),
+      kCFStringBackwardDeletionCluster);
 
   if (range_to_delete.location == NSNotFound)
     return gfx::Range();
 
   // The range needs to be reversed to undo correctly.
-  return gfx::Range(range_to_delete.location + range_to_delete.length,
-                    range_to_delete.location);
+  return gfx::Range(base::checked_cast<size_t>(range_to_delete.location +
+                                               range_to_delete.length),
+                    base::checked_cast<size_t>(range_to_delete.location));
 }
 
 }  // namespace views
diff --git a/ui/views/view.cc b/ui/views/view.cc
index a363a58..7e786be 100644
--- a/ui/views/view.cc
+++ b/ui/views/view.cc
@@ -18,6 +18,7 @@
 #include "base/i18n/rtl.h"
 #include "base/logging.h"
 #include "base/notreached.h"
+#include "base/numerics/safe_conversions.h"
 #include "base/observer_list.h"
 #include "base/scoped_observation.h"
 #include "base/strings/utf_string_conversions.h"
@@ -2389,7 +2390,8 @@
 void View::AfterPropertyChange(const void* key, int64_t old_value) {
   if (key == kElementIdentifierKey) {
     const ui::ElementIdentifier old_element_id =
-        ui::ElementIdentifier::FromRawValue(old_value);
+        ui::ElementIdentifier::FromRawValue(
+            base::checked_cast<intptr_t>(old_value));
     if (old_element_id) {
       views::ElementTrackerViews::GetInstance()->UnregisterView(old_element_id,
                                                                 this);
diff --git a/ui/views/widget/native_widget_mac.mm b/ui/views/widget/native_widget_mac.mm
index d9df913..b3fc364 100644
--- a/ui/views/widget/native_widget_mac.mm
+++ b/ui/views/widget/native_widget_mac.mm
@@ -54,7 +54,7 @@
 static base::RepeatingCallback<void(NativeWidgetMac*)>*
     g_init_native_widget_callback = nullptr;
 
-NSInteger StyleMaskForParams(const Widget::InitParams& params) {
+uint64_t StyleMaskForParams(const Widget::InitParams& params) {
   // If the Widget is modal, it will be displayed as a sheet. This works best if
   // it has NSWindowStyleMaskTitled. For example, with
   // NSWindowStyleMaskBorderless, the parent window still accepts input.
diff --git a/ui/views/win/hwnd_message_handler.cc b/ui/views/win/hwnd_message_handler.cc
index 7438cdf5..29e812d4 100644
--- a/ui/views/win/hwnd_message_handler.cc
+++ b/ui/views/win/hwnd_message_handler.cc
@@ -862,7 +862,8 @@
   // tasks while in windows move loop.
   base::CurrentThread::ScopedNestableTaskAllower allow_nested;
 
-  SendMessage(hwnd(), WM_SYSCOMMAND, SC_MOVE | 0x0002, GetMessagePos());
+  SendMessage(hwnd(), WM_SYSCOMMAND, SC_MOVE | 0x0002,
+              static_cast<LPARAM>(GetMessagePos()));
   // Windows doesn't appear to offer a way to determine whether the user
   // canceled the move or not. We assume if the user released the mouse it was
   // successful.
diff --git a/ui/webui/resources/BUILD.gn b/ui/webui/resources/BUILD.gn
index 5bb59f9b..af92076 100644
--- a/ui/webui/resources/BUILD.gn
+++ b/ui/webui/resources/BUILD.gn
@@ -174,7 +174,7 @@
   "cr_elements/cr_radio_button/cr_card_radio_button.m.d.ts",
   "cr_elements/cr_radio_button/cr_radio_button_behavior.m.d.ts",
   "cr_elements/cr_radio_button/cr_radio_button.m.d.ts",
-  "cr_elements/cr_radio_group/cr_radio_group.m.d.ts",
+  "cr_elements/cr_radio_group/cr_radio_group.d.ts",
   "cr_elements/cr_scrollable_behavior.m.d.ts",
   "cr_elements/cr_toggle/cr_toggle.m.d.ts",
   "cr_elements/find_shortcut_behavior.d.ts",
diff --git a/ui/webui/resources/cr_components/chromeos/network/BUILD.gn b/ui/webui/resources/cr_components/chromeos/network/BUILD.gn
index 27a001dd..6fceab5 100644
--- a/ui/webui/resources/cr_components/chromeos/network/BUILD.gn
+++ b/ui/webui/resources/cr_components/chromeos/network/BUILD.gn
@@ -146,7 +146,6 @@
   deps = [
     ":cr_policy_network_behavior_mojo",
     ":onc_mojo",
-    "../../../cr_elements/cr_radio_group:cr_radio_group",
     "//ui/webui/resources/js:i18n_behavior",
   ]
 }
@@ -512,7 +511,7 @@
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_elements/cr_input:cr_input.m",
     "//ui/webui/resources/cr_elements/cr_radio_button:cr_radio_button.m",
-    "//ui/webui/resources/cr_elements/cr_radio_group:cr_radio_group.m",
+    "//ui/webui/resources/cr_elements/cr_radio_group:cr_radio_group",
     "//ui/webui/resources/cr_elements/policy:cr_policy_indicator.m",
     "//ui/webui/resources/js:i18n_behavior.m",
   ]
@@ -798,6 +797,7 @@
   html_file = "network_nameservers.html"
   html_type = "dom-module"
   auto_imports = cr_components_chromeos_auto_imports
+  migrated_imports = cr_components_migrated_imports
 }
 
 polymer_modulizer("network_password_input") {
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_nameservers.js b/ui/webui/resources/cr_components/chromeos/network/network_nameservers.js
index b9799c99..676c03e1 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_nameservers.js
+++ b/ui/webui/resources/cr_components/chromeos/network/network_nameservers.js
@@ -107,10 +107,10 @@
 
   /*
    * Returns the nameserver type CrRadioGroupElement.
-   * @return {?CrRadioGroupElement}
+   * @return {?HTMLElement}
    */
   getNameserverRadioButtons() {
-    return /** @type {?CrRadioGroupElement} */ (this.$$('#nameserverType'));
+    return /** @type {?HTMLElement} */ (this.$$('#nameserverType'));
   },
 
   /**
diff --git a/ui/webui/resources/cr_components/chromeos/os_cr_components.gni b/ui/webui/resources/cr_components/chromeos/os_cr_components.gni
index da0a181..cbb9e2ab 100644
--- a/ui/webui/resources/cr_components/chromeos/os_cr_components.gni
+++ b/ui/webui/resources/cr_components/chromeos/os_cr_components.gni
@@ -73,5 +73,6 @@
 
 cr_components_migrated_imports = [
   "ui/webui/resources/cr_elements/cr_icon_button/cr_icon_button.html",
+  "ui/webui/resources/cr_elements/cr_radio_group/cr_radio_group.html",
   "ui/webui/resources/html/list_property_update_behavior.html",
 ]
diff --git a/ui/webui/resources/cr_elements/BUILD.gn b/ui/webui/resources/cr_elements/BUILD.gn
index 5a15887..639bd22 100644
--- a/ui/webui/resources/cr_elements/BUILD.gn
+++ b/ui/webui/resources/cr_elements/BUILD.gn
@@ -103,8 +103,6 @@
       "cr_radio_button/cr_radio_button.html",
       "cr_radio_button/cr_radio_button.js",
       "cr_radio_button/cr_radio_button_style_css.html",
-      "cr_radio_group/cr_radio_group.html",
-      "cr_radio_group/cr_radio_group.js",
       "cr_scrollable_behavior.html",
       "cr_scrollable_behavior.js",
       "cr_toggle/cr_toggle.html",
@@ -149,7 +147,7 @@
       "cr_radio_button/cr_radio_button_behavior.m.js",
       "cr_radio_button/cr_radio_button.m.js",
       "cr_radio_button/cr_radio_button_style_css.m.js",
-      "cr_radio_group/cr_radio_group.m.js",
+      "cr_radio_group/cr_radio_group.js",
       "cr_scrollable_behavior.m.js",
       "cr_toggle/cr_toggle.m.js",
       "hidden_style_css.m.js",
@@ -195,10 +193,7 @@
     ]
 
     if (is_chromeos_ash) {
-      deps += [
-        "cr_button:closure_compile",
-        "cr_radio_group:closure_compile",
-      ]
+      deps += [ "cr_button:closure_compile" ]
     }
   }
 
@@ -296,7 +291,7 @@
       "cr_input:polymer3_elements",
       "cr_lottie:cr_lottie_module",
       "cr_radio_button:polymer3_elements",
-      "cr_radio_group:cr_radio_group_module",
+      "cr_radio_group:web_components",
       "cr_toggle:cr_toggle_module",
       "policy:polymer3_elements",
     ]
diff --git a/ui/webui/resources/cr_elements/cr_radio_group/BUILD.gn b/ui/webui/resources/cr_elements/cr_radio_group/BUILD.gn
index 6b44fc4..d24aeeb 100644
--- a/ui/webui/resources/cr_elements/cr_radio_group/BUILD.gn
+++ b/ui/webui/resources/cr_elements/cr_radio_group/BUILD.gn
@@ -4,46 +4,22 @@
 
 import("//build/config/chromeos/ui_mode.gni")
 import("//third_party/closure_compiler/compile_js.gni")
-import("//tools/polymer/polymer.gni")
-
-if (is_chromeos_ash) {
-  js_type_check("closure_compile") {
-    uses_legacy_modules = true
-    deps = [ ":cr_radio_group" ]
-  }
-
-  js_library("cr_radio_group") {
-    deps = [
-      "//ui/webui/resources/cr_elements/cr_radio_button:cr_radio_button",
-      "//ui/webui/resources/js:event_tracker",
-    ]
-  }
-}
+import("//tools/polymer/html_to_js.gni")
 
 # Targets for auto-generating and typechecking Polymer 3 JS modules
-
-polymer_modulizer("cr_radio_group") {
-  js_file = "cr_radio_group.js"
-  html_file = "cr_radio_group.html"
-  html_type = "dom-module"
-  auto_imports = [
-    "ui/webui/resources/html/event_tracker.html|EventTracker",
-    "ui/webui/resources/html/polymer.html|Polymer,html,dom",
-  ]
-  namespace_rewrites = [ "Polymer.dom|dom" ]
+html_to_js("web_components") {
+  js_files = [ "cr_radio_group.js" ]
 }
 
 js_type_check("closure_compile_module") {
   is_polymer3 = true
-  deps = [ ":cr_radio_group.m" ]
+  deps = [ ":cr_radio_group" ]
 }
 
-js_library("cr_radio_group.m") {
-  sources = [ "$root_gen_dir/ui/webui/resources/cr_elements/cr_radio_group/cr_radio_group.m.js" ]
+js_library("cr_radio_group") {
   deps = [
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
     "//ui/webui/resources/cr_elements/cr_radio_button:cr_radio_button.m",
     "//ui/webui/resources/js:event_tracker.m",
   ]
-  extra_deps = [ ":cr_radio_group_module" ]
 }
diff --git a/ui/webui/resources/cr_elements/cr_radio_group/cr_radio_group.m.d.ts b/ui/webui/resources/cr_elements/cr_radio_group/cr_radio_group.d.ts
similarity index 100%
rename from ui/webui/resources/cr_elements/cr_radio_group/cr_radio_group.m.d.ts
rename to ui/webui/resources/cr_elements/cr_radio_group/cr_radio_group.d.ts
diff --git a/ui/webui/resources/cr_elements/cr_radio_group/cr_radio_group.html b/ui/webui/resources/cr_elements/cr_radio_group/cr_radio_group.html
index 0e4996e4..63c43aa9 100644
--- a/ui/webui/resources/cr_elements/cr_radio_group/cr_radio_group.html
+++ b/ui/webui/resources/cr_elements/cr_radio_group/cr_radio_group.html
@@ -1,11 +1,3 @@
-<link rel="import" href="../../html/polymer.html">
-
-<link rel="import" href="../../html/event_tracker.html">
-<link rel="import" href="../cr_radio_button/cr_radio_button.html">
-<link rel="import" href="../shared_vars_css.html">
-
-<dom-module id="cr-radio-group">
-  <template>
     <style>
       :host {
         display: inline-block;
@@ -26,6 +18,3 @@
       }
     </style>
     <slot></slot>
-  </template>
-  <script src="cr_radio_group.js"></script>
-</dom-module>
diff --git a/ui/webui/resources/cr_elements/cr_radio_group/cr_radio_group.js b/ui/webui/resources/cr_elements/cr_radio_group/cr_radio_group.js
index 5b5bdfe3..edf77214 100644
--- a/ui/webui/resources/cr_elements/cr_radio_group/cr_radio_group.js
+++ b/ui/webui/resources/cr_elements/cr_radio_group/cr_radio_group.js
@@ -2,21 +2,33 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-(() => {
+import '../cr_radio_button/cr_radio_button.m.js';
+import '../shared_vars_css.m.js';
 
-  /**
-   * @param {!Element} radio
-   * @return {boolean}
-   */
-  function isEnabled(radio) {
-    return radio.matches(':not([disabled]):not([hidden])') &&
-        radio.style.display !== 'none' && radio.style.visibility !== 'hidden';
+import {html, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {EventTracker} from '../../js/event_tracker.m.js';
+
+/**
+ * @param {!Element} radio
+ * @return {boolean}
+ */
+function isEnabled(radio) {
+  return radio.matches(':not([disabled]):not([hidden])') &&
+      radio.style.display !== 'none' && radio.style.visibility !== 'hidden';
+}
+
+export class CrRadioGroupElement extends PolymerElement {
+  static get is() {
+    return 'cr-radio-group';
   }
 
-  Polymer({
-    is: 'cr-radio-group',
+  static get template() {
+    return html`{__html_template__}`;
+  }
 
-    properties: {
+  static get properties() {
+    return {
       disabled: {
         type: Boolean,
         value: false,
@@ -43,240 +55,233 @@
         value: Object,
         computed: 'computeSelectableRegExp_(selectableElements)',
       },
-    },
+    };
+  }
 
-    listeners: {
-      keydown: 'onKeyDown_',
-      click: 'onClick_',
-    },
+  constructor() {
+    super();
+    /** @private {?Array<!CrRadioButtonElement>} */
+    this.buttons_ = null;
 
-    hostAttributes: {
-      'aria-disabled': 'false',
-      role: 'radiogroup',
-    },
+    /** @private {?EventTracker} */
+    this.buttonEventTracker_ = null;
 
-    /** @private {Array<!CrRadioButtonElement>} */
-    buttons_: null,
-
-    /** @private {cr.EventTracker} */
-    buttonEventTracker_: null,
-
-    /** @private {Map<string, number>} */
-    deltaKeyMap_: null,
+    /** @private {?Map<string, number>} */
+    this.deltaKeyMap_ = null;
 
     /** @private {boolean} */
-    isRtl_: false,
-
-    /** @private  {PolymerDomApi.ObserveHandle} */
-    observer_: null,
+    this.isRtl_ = false;
 
     /** @private {Function} */
-    populateBound_: null,
+    this.populateBound_ = null;
+  }
 
-    /** @override */
-    attached() {
-      this.isRtl_ = this.matches(':host-context([dir=rtl]) cr-radio-group');
-      this.deltaKeyMap_ = new Map([
-        ['ArrowDown', 1],
-        ['ArrowLeft', this.isRtl_ ? 1 : -1],
-        ['ArrowRight', this.isRtl_ ? -1 : 1],
-        ['ArrowUp', -1],
-        ['PageDown', 1],
-        ['PageUp', -1],
-      ]);
-      this.buttonEventTracker_ = new cr.EventTracker();
+  /** @override */
+  ready() {
+    super.ready();
+    this.addEventListener(
+        'keydown', e => this.onKeyDown_(/** @type {!KeyboardEvent} */ (e)));
+    this.addEventListener('click', this.onClick_.bind(this));
 
-      this.populateBound_ = () => this.populate_();
-      // Needed for when the radio buttons change when using dom-repeat or
-      // dom-if.
-      // TODO(crbug.com/738611): After migration to Polymer 2, remove Polymer 1
-      // references.
-      if (Polymer.DomIf) {
-        this.$$('slot').addEventListener('slotchange', this.populateBound_);
-      } else {
-        this.observer_ = Polymer.dom(this).observeNodes(this.populateBound_);
-      }
+    if (!this.hasAttribute('role')) {
+      this.setAttribute('role', 'radiogroup');
+    }
+    this.setAttribute('aria-disabled', 'false');
+  }
 
-      this.populate_();
-    },
+  /** @override */
+  connectedCallback() {
+    super.connectedCallback();
+    this.isRtl_ = this.matches(':host-context([dir=rtl]) cr-radio-group');
+    this.deltaKeyMap_ = new Map([
+      ['ArrowDown', 1],
+      ['ArrowLeft', this.isRtl_ ? 1 : -1],
+      ['ArrowRight', this.isRtl_ ? -1 : 1],
+      ['ArrowUp', -1],
+      ['PageDown', 1],
+      ['PageUp', -1],
+    ]);
+    this.buttonEventTracker_ = new EventTracker();
 
-    /** @override */
-    detached() {
-      if (Polymer.DomIf) {
-        this.$$('slot').removeEventListener('slotchange', this.populateBound_);
-      } else if (this.observer_) {
-        Polymer.dom(this).unobserveNodes(
-            /** @type {!PolymerDomApi.ObserveHandle} */ (this.observer_));
-      }
-      this.buttonEventTracker_.removeAll();
-    },
+    this.populateBound_ = () => this.populate_();
+    this.shadowRoot.querySelector('slot').addEventListener(
+        'slotchange', this.populateBound_);
 
-    /** @override */
-    focus() {
-      if (this.disabled || !this.buttons_) {
-        return;
-      }
+    this.populate_();
+  }
 
-      const radio =
-          this.buttons_.find(radio => this.isButtonEnabledAndSelected_(radio));
-      if (radio) {
-        radio.focus();
-      }
-    },
+  /** @override */
+  disconnectedCallback() {
+    super.disconnectedCallback();
+    this.shadowRoot.querySelector('slot').removeEventListener(
+        'slotchange', this.populateBound_);
+    this.buttonEventTracker_.removeAll();
+  }
 
-    /**
-     * @param {!KeyboardEvent} event
-     * @private
-     */
-    onKeyDown_(event) {
-      if (this.disabled) {
-        return;
-      }
+  /** @override */
+  focus() {
+    if (this.disabled || !this.buttons_) {
+      return;
+    }
 
-      if (event.ctrlKey || event.shiftKey || event.metaKey || event.altKey) {
-        return;
-      }
+    const radio =
+        this.buttons_.find(radio => this.isButtonEnabledAndSelected_(radio));
+    if (radio) {
+      radio.focus();
+    }
+  }
 
-      const targetElement = /** @type {!CrRadioButtonElement} */ (event.target);
-      if (!this.buttons_.includes(targetElement)) {
-        return;
-      }
+  /**
+   * @param {!KeyboardEvent} event
+   * @private
+   */
+  onKeyDown_(event) {
+    if (this.disabled) {
+      return;
+    }
 
-      if (event.key === ' ' || event.key === 'Enter') {
-        event.preventDefault();
-        this.select_(/** @type {!CrRadioButtonElement} */ (event.target));
-        return;
-      }
+    if (event.ctrlKey || event.shiftKey || event.metaKey || event.altKey) {
+      return;
+    }
 
-      const enabledRadios = this.buttons_.filter(isEnabled);
-      if (enabledRadios.length === 0) {
-        return;
-      }
+    const targetElement = /** @type {!CrRadioButtonElement} */ (event.target);
+    if (!this.buttons_.includes(targetElement)) {
+      return;
+    }
 
-      let selectedIndex;
-      const max = enabledRadios.length - 1;
-      if (event.key === 'Home') {
+    if (event.key === ' ' || event.key === 'Enter') {
+      event.preventDefault();
+      this.select_(/** @type {!CrRadioButtonElement} */ (event.target));
+      return;
+    }
+
+    const enabledRadios = this.buttons_.filter(isEnabled);
+    if (enabledRadios.length === 0) {
+      return;
+    }
+
+    let selectedIndex;
+    const max = enabledRadios.length - 1;
+    if (event.key === 'Home') {
+      selectedIndex = 0;
+    } else if (event.key === 'End') {
+      selectedIndex = max;
+    } else if (this.deltaKeyMap_.has(event.key)) {
+      const delta = this.deltaKeyMap_.get(event.key);
+      // If nothing selected, start from the first radio then add |delta|.
+      const lastSelection = enabledRadios.findIndex(radio => radio.checked);
+      selectedIndex = Math.max(0, lastSelection) + delta;
+      // Wrap the selection, if needed.
+      if (selectedIndex > max) {
         selectedIndex = 0;
-      } else if (event.key === 'End') {
+      } else if (selectedIndex < 0) {
         selectedIndex = max;
-      } else if (this.deltaKeyMap_.has(event.key)) {
-        const delta = this.deltaKeyMap_.get(event.key);
-        // If nothing selected, start from the first radio then add |delta|.
-        const lastSelection = enabledRadios.findIndex(radio => radio.checked);
-        selectedIndex = Math.max(0, lastSelection) + delta;
-        // Wrap the selection, if needed.
-        if (selectedIndex > max) {
-          selectedIndex = 0;
-        } else if (selectedIndex < 0) {
-          selectedIndex = max;
-        }
+      }
+    } else {
+      return;
+    }
+
+    const radio = enabledRadios[selectedIndex];
+    const name = `${radio.name}`;
+    if (this.selected !== name) {
+      event.preventDefault();
+      this.selected = name;
+      radio.focus();
+    }
+  }
+
+  /**
+   * @return {!RegExp}
+   * @private
+   */
+  computeSelectableRegExp_() {
+    const tags = this.selectableElements.split(', ').join('|');
+    return new RegExp(`^(${tags})$`, 'i');
+  }
+
+  /**
+   * @param {!Event} event
+   * @private
+   */
+  onClick_(event) {
+    const path = event.composedPath();
+    if (path.some(target => /^a$/i.test(target.tagName))) {
+      return;
+    }
+    const target = /** @type {!CrRadioButtonElement} */ (
+        path.find(n => this.selectableRegExp_.test(n.tagName)));
+    if (target && this.buttons_.includes(target)) {
+      this.select_(/** @type {!CrRadioButtonElement} */ (target));
+    }
+  }
+
+  /** @private */
+  populate_() {
+    const nodes =
+        this.shadowRoot.querySelector('slot').assignedNodes({flatten: true});
+    this.buttons_ = Array.from(nodes).filter(
+        node => node.nodeType === Node.ELEMENT_NODE &&
+            node.matches(this.selectableElements));
+    this.buttonEventTracker_.removeAll();
+    this.buttons_.forEach(el => {
+      this.buttonEventTracker_.add(
+          el, 'disabled-changed', () => this.populate_());
+      this.buttonEventTracker_.add(el, 'name-changed', () => this.populate_());
+    });
+    this.update_();
+  }
+
+  /**
+   * @param {!CrRadioButtonElement} button
+   * @private
+   */
+  select_(button) {
+    if (!isEnabled(button)) {
+      return;
+    }
+
+    const name = `${button.name}`;
+    if (this.selected !== name) {
+      this.selected = name;
+    }
+  }
+
+  /**
+   * @param {!Element} button
+   * @return {boolean}
+   * @private
+   */
+  isButtonEnabledAndSelected_(button) {
+    return !this.disabled && button.checked && isEnabled(button);
+  }
+
+  /** @private */
+  update_() {
+    if (!this.buttons_) {
+      return;
+    }
+    let noneMadeFocusable = true;
+    this.buttons_.forEach(radio => {
+      radio.checked =
+          this.selected !== undefined && `${radio.name}` === `${this.selected}`;
+      const disabled = this.disabled || !isEnabled(radio);
+      const canBeFocused = radio.checked && !disabled;
+      if (canBeFocused) {
+        radio.focusable = true;
+        noneMadeFocusable = false;
       } else {
-        return;
+        radio.focusable = false;
       }
-
-      const radio = enabledRadios[selectedIndex];
-      const name = `${radio.name}`;
-      if (this.selected !== name) {
-        event.preventDefault();
-        this.selected = name;
-        radio.focus();
+      radio.setAttribute('aria-disabled', `${disabled}`);
+    });
+    this.setAttribute('aria-disabled', `${this.disabled}`);
+    if (noneMadeFocusable && !this.disabled) {
+      const radio = this.buttons_.find(isEnabled);
+      if (radio) {
+        radio.focusable = true;
       }
-    },
+    }
+  }
+}
 
-    /**
-     * @return {!RegExp}
-     * @private
-     */
-    computeSelectableRegExp_() {
-      const tags = this.selectableElements.split(', ').join('|');
-      return new RegExp(`^(${tags})$`, 'i');
-    },
-
-    /**
-     * @param {!Event} event
-     * @private
-     */
-    onClick_(event) {
-      const path = event.composedPath();
-      if (path.some(target => /^a$/i.test(target.tagName))) {
-        return;
-      }
-      const target = /** @type {!CrRadioButtonElement} */ (
-          path.find(n => this.selectableRegExp_.test(n.tagName)));
-      if (target && this.buttons_.includes(target)) {
-        this.select_(/** @type {!CrRadioButtonElement} */ (target));
-      }
-    },
-
-    /** @private */
-    populate_() {
-      // TODO(crbug.com/738611): After migration to Polymer 2, remove
-      // Polymer 1 references.
-      this.buttons_ = Polymer.DomIf ?
-          this.$$('slot')
-              .assignedNodes({flatten: true})
-              .filter(n => this.selectableRegExp_.test(n.tagName)) :
-          this.queryAllEffectiveChildren(this.selectableElements);
-      this.buttonEventTracker_.removeAll();
-      this.buttons_.forEach(el => {
-        this.buttonEventTracker_.add(
-            el, 'disabled-changed', () => this.populate_());
-        this.buttonEventTracker_.add(
-            el, 'name-changed', () => this.populate_());
-      });
-      this.update_();
-    },
-
-    /**
-     * @param {!CrRadioButtonElement} button
-     * @private
-     */
-    select_(button) {
-      if (!isEnabled(button)) {
-        return;
-      }
-
-      const name = `${button.name}`;
-      if (this.selected !== name) {
-        this.selected = name;
-      }
-    },
-
-    /**
-     * @param {!Element} button
-     * @return {boolean}
-     * @private
-     */
-    isButtonEnabledAndSelected_(button) {
-      return !this.disabled && button.checked && isEnabled(button);
-    },
-
-    /** @private */
-    update_() {
-      if (!this.buttons_) {
-        return;
-      }
-      let noneMadeFocusable = true;
-      this.buttons_.forEach(radio => {
-        radio.checked = this.selected !== undefined &&
-            `${radio.name}` === `${this.selected}`;
-        const disabled = this.disabled || !isEnabled(radio);
-        const canBeFocused = radio.checked && !disabled;
-        if (canBeFocused) {
-          radio.focusable = true;
-          noneMadeFocusable = false;
-        } else {
-          radio.focusable = false;
-        }
-        radio.setAttribute('aria-disabled', `${disabled}`);
-      });
-      this.setAttribute('aria-disabled', `${this.disabled}`);
-      if (noneMadeFocusable && !this.disabled) {
-        const radio = this.buttons_.find(isEnabled);
-        if (radio) {
-          radio.focusable = true;
-        }
-      }
-    },
-  });
-})();
+customElements.define(CrRadioGroupElement.is, CrRadioGroupElement);
diff --git a/weblayer/browser/permissions/permission_manager_factory.cc b/weblayer/browser/permissions/permission_manager_factory.cc
index ef813c2..b13d7428 100644
--- a/weblayer/browser/permissions/permission_manager_factory.cc
+++ b/weblayer/browser/permissions/permission_manager_factory.cc
@@ -126,7 +126,7 @@
       continue;
 #endif
     ContentSettingsType content_settings_type =
-        permissions::PermissionUtil::PermissionTypeToContentSetting(type);
+        permissions::PermissionUtil::PermissionTypeToContentSettingType(type);
     if (permission_contexts.find(content_settings_type) ==
         permission_contexts.end()) {
       permission_contexts[content_settings_type] =
diff --git a/weblayer/browser/permissions/weblayer_permissions_client.cc b/weblayer/browser/permissions/weblayer_permissions_client.cc
index e099327..6cd171c 100644
--- a/weblayer/browser/permissions/weblayer_permissions_client.cc
+++ b/weblayer/browser/permissions/weblayer_permissions_client.cc
@@ -12,7 +12,6 @@
 #include "weblayer/browser/cookie_settings_factory.h"
 #include "weblayer/browser/host_content_settings_map_factory.h"
 #include "weblayer/browser/permissions/permission_decision_auto_blocker_factory.h"
-#include "weblayer/browser/permissions/permission_manager_factory.h"
 #include "weblayer/browser/subresource_filter_profile_context_factory.h"
 
 #if BUILDFLAG(IS_ANDROID)
@@ -63,11 +62,6 @@
   return nullptr;
 }
 
-permissions::PermissionManager* WebLayerPermissionsClient::GetPermissionManager(
-    content::BrowserContext* browser_context) {
-  return PermissionManagerFactory::GetForBrowserContext(browser_context);
-}
-
 permissions::ObjectPermissionContextBase*
 WebLayerPermissionsClient::GetChooserContext(
     content::BrowserContext* browser_context,
diff --git a/weblayer/browser/permissions/weblayer_permissions_client.h b/weblayer/browser/permissions/weblayer_permissions_client.h
index a193c0ea..a772aeb3 100644
--- a/weblayer/browser/permissions/weblayer_permissions_client.h
+++ b/weblayer/browser/permissions/weblayer_permissions_client.h
@@ -30,8 +30,6 @@
       content::BrowserContext* browser_context) override;
   permissions::PermissionDecisionAutoBlocker* GetPermissionDecisionAutoBlocker(
       content::BrowserContext* browser_context) override;
-  permissions::PermissionManager* GetPermissionManager(
-      content::BrowserContext* browser_context) override;
   permissions::ObjectPermissionContextBase* GetChooserContext(
       content::BrowserContext* browser_context,
       ContentSettingsType type) override;
diff --git a/weblayer/browser/url_bar/page_info_browsertest.cc b/weblayer/browser/url_bar/page_info_browsertest.cc
index b5e8fd7..ab6368a2 100644
--- a/weblayer/browser/url_bar/page_info_browsertest.cc
+++ b/weblayer/browser/url_bar/page_info_browsertest.cc
@@ -8,6 +8,8 @@
 #include "components/content_settings/core/common/content_settings.h"
 #include "components/page_info/android/page_info_client.h"
 #include "components/page_info/page_info_delegate.h"
+#include "third_party/blink/public/common/permissions/permission_utils.h"
+#include "url/origin.h"
 #include "weblayer/browser/tab_impl.h"
 #include "weblayer/browser/url_bar/page_info_delegate_impl.h"
 #include "weblayer/public/navigation_controller.h"
@@ -70,7 +72,7 @@
   EXPECT_TRUE(page_info_delegate->GetContentSettings());
 }
 
-IN_PROC_BROWSER_TEST_F(PageInfoBrowserTest, PermissionStatus) {
+IN_PROC_BROWSER_TEST_F(PageInfoBrowserTest, PermissionResult) {
   std::unique_ptr<PageInfoDelegate> page_info_delegate =
       page_info::GetPageInfoClient()->CreatePageInfoDelegate(GetWebContents());
   ASSERT_TRUE(page_info_delegate);
@@ -83,7 +85,8 @@
 
   // Check that |page_info_delegate| returns expected ContentSettingsType.
   EXPECT_EQ(page_info_delegate
-                ->GetPermissionStatus(ContentSettingsType::NOTIFICATIONS, url)
+                ->GetPermissionResult(blink::PermissionType::NOTIFICATIONS,
+                                      url::Origin::Create(url))
                 .content_setting,
             CONTENT_SETTING_BLOCK);
 }
diff --git a/weblayer/browser/url_bar/page_info_delegate_impl.cc b/weblayer/browser/url_bar/page_info_delegate_impl.cc
index a3ae2bbd..687a31d 100644
--- a/weblayer/browser/url_bar/page_info_delegate_impl.cc
+++ b/weblayer/browser/url_bar/page_info_delegate_impl.cc
@@ -5,16 +5,17 @@
 #include "weblayer/browser/url_bar/page_info_delegate_impl.h"
 
 #include "build/build_config.h"
-#include "components/permissions/permission_manager.h"
 #include "components/security_interstitials/content/stateful_ssl_host_state_delegate.h"
 #include "components/security_state/content/content_utils.h"
 #include "components/subresource_filter/content/browser/subresource_filter_content_settings_manager.h"
 #include "components/subresource_filter/content/browser/subresource_filter_profile_context.h"
 #include "content/public/browser/browser_context.h"
+#include "content/public/browser/permission_controller.h"
+#include "content/public/browser/permission_result.h"
+#include "url/origin.h"
 #include "weblayer/browser/host_content_settings_map_factory.h"
 #include "weblayer/browser/page_specific_content_settings_delegate.h"
 #include "weblayer/browser/permissions/permission_decision_auto_blocker_factory.h"
-#include "weblayer/browser/permissions/permission_manager_factory.h"
 #include "weblayer/browser/stateful_ssl_host_state_delegate_factory.h"
 #include "weblayer/browser/subresource_filter_profile_context_factory.h"
 
@@ -55,11 +56,14 @@
 }
 #endif
 
-permissions::PermissionResult PageInfoDelegateImpl::GetPermissionStatus(
-    ContentSettingsType type,
-    const GURL& site_url) {
-  return PermissionManagerFactory::GetForBrowserContext(GetBrowserContext())
-      ->GetPermissionStatusForDisplayOnSettingsUI(type, site_url);
+permissions::PermissionResult PageInfoDelegateImpl::GetPermissionResult(
+    blink::PermissionType permission,
+    const url::Origin& origin) {
+  content::PermissionResult permission_result =
+      GetBrowserContext()
+          ->GetPermissionController()
+          ->GetPermissionResultForOriginWithoutContext(permission, origin);
+  return permissions::PermissionUtil::ToPermissionResult(permission_result);
 }
 
 #if !BUILDFLAG(IS_ANDROID)
diff --git a/weblayer/browser/url_bar/page_info_delegate_impl.h b/weblayer/browser/url_bar/page_info_delegate_impl.h
index 7c3062f..1d666633a 100644
--- a/weblayer/browser/url_bar/page_info_delegate_impl.h
+++ b/weblayer/browser/url_bar/page_info_delegate_impl.h
@@ -32,9 +32,9 @@
   void OnUserActionOnPasswordUi(safe_browsing::WarningAction action) override;
   std::u16string GetWarningDetailText() override;
 #endif
-  permissions::PermissionResult GetPermissionStatus(
-      ContentSettingsType type,
-      const GURL& site_url) override;
+  permissions::PermissionResult GetPermissionResult(
+      blink::PermissionType permission,
+      const url::Origin& origin) override;
 
 #if !BUILDFLAG(IS_ANDROID)
   bool CreateInfoBarDelegate() override;