diff --git a/DEPS b/DEPS
index 5f96c95..4b582b4 100644
--- a/DEPS
+++ b/DEPS
@@ -297,23 +297,23 @@
   # 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': '0cc4b51ab9d5c72c052db4cf3782583df0dfa44e',
+  'skia_revision': 'bfbc0c9abd675f4804b97000a93837b157b43754',
   # 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': 'f43f657e166b75db1f91f9f554ec8c474f87194b',
+  'v8_revision': 'e5493ce937eafff2bf7c2091a89d5e64d04bf341',
   # 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': '8050079c116c993a5385737da12ad871c4f53fed',
+  'angle_revision': '2c35135180285c722687620d9d7caa4e2bba6a03',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': 'ed9d5ae1e79c3ecc3bee8e07e852ba24868d27d5',
+  'swiftshader_revision': 'aafa10869568fd3e84bfef1df2ed0e159b179dce',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': '962e4bff2fa741f0a8efc06fb84d55111bcc11b7',
+  'pdfium_revision': '744e9f8b0f89481c4623b712f8ecdb02799ddcb5',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -324,7 +324,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Fuchsia sdk
   # and whatever else without interference from each other.
-  'fuchsia_version': 'version:9.20220809.0.1',
+  'fuchsia_version': 'version:9.20220809.1.1',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling google-toolbox-for-mac
   # and whatever else without interference from each other.
@@ -376,7 +376,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': '50a84ca8e5b556e27bb285477f21a99f0ccb7050',
+  'devtools_frontend_revision': '1f6ef0d58292665e06eded4059d8714a2e487e8a',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -412,7 +412,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '1eed2c2e3638a0c2fbb9494549798cea096d15e5',
+  'dawn_revision': '76eeb828c8fff6e83c099b6202d2585fa18fddfa',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # 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': '0a8f1f1c39af06dff550d8ca96c6e087994155b7',
+  'nearby_revision': '55f20775d209871190580f0ef461ae1c816b1870',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling securemessage
   # and whatever else without interference from each other.
@@ -1693,7 +1693,7 @@
       'dep_type': 'cipd',
   },
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@30403bafe4dc5b14e820ae922278455053e8f7e2',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@c728ed48ce2fb0d011918eea7013f94d03c75bfe',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + 'ebe84bec02c041d28f902da0214bf442743fc907',
@@ -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@fe63cb6846edc3c548739c989e6a85a6fa568bde',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@b77bd0ccfd4c6f0e69dbd465bd4032555d2447e6',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/ash/app_list/model/search/search_box_model.cc b/ash/app_list/model/search/search_box_model.cc
index 9911203..e99b7daa 100644
--- a/ash/app_list/model/search/search_box_model.cc
+++ b/ash/app_list/model/search/search_box_model.cc
@@ -32,26 +32,6 @@
     observer.SearchEngineChanged();
 }
 
-void SearchBoxModel::Update(const std::u16string& text,
-                            bool initiated_by_user) {
-  if (text_ == text)
-    return;
-
-  if (initiated_by_user) {
-    if (text_.empty() && !text.empty()) {
-      UMA_HISTOGRAM_ENUMERATION("Apps.AppListSearchCommenced", 1, 2);
-      base::RecordAction(base::UserMetricsAction("AppList_EnterSearch"));
-    } else if (!text_.empty() && text.empty()) {
-      // The user ended a search interaction. Reset search start time.
-      base::RecordAction(base::UserMetricsAction("AppList_LeaveSearch"));
-    }
-  }
-
-  text_ = text;
-  for (auto& observer : observers_)
-    observer.Update();
-}
-
 void SearchBoxModel::AddObserver(SearchBoxModelObserver* observer) {
   observers_.AddObserver(observer);
 }
diff --git a/ash/app_list/model/search/search_box_model.h b/ash/app_list/model/search/search_box_model.h
index 32e56b0..78d0cced4 100644
--- a/ash/app_list/model/search/search_box_model.h
+++ b/ash/app_list/model/search/search_box_model.h
@@ -5,12 +5,8 @@
 #ifndef ASH_APP_LIST_MODEL_SEARCH_SEARCH_BOX_MODEL_H_
 #define ASH_APP_LIST_MODEL_SEARCH_SEARCH_BOX_MODEL_H_
 
-#include <memory>
-#include <string>
-
 #include "ash/app_list/model/app_list_model_export.h"
 #include "base/observer_list.h"
-#include "ui/gfx/selection_model.h"
 
 namespace ash {
 
@@ -31,16 +27,10 @@
   void SetSearchEngineIsGoogle(bool is_google);
   bool search_engine_is_google() const { return search_engine_is_google_; }
 
-  // Sets/gets the text for the search box's Textfield and the voice search
-  // flag.
-  void Update(const std::u16string& text, bool initiated_by_user);
-  const std::u16string& text() const { return text_; }
-
   void AddObserver(SearchBoxModelObserver* observer);
   void RemoveObserver(SearchBoxModelObserver* observer);
 
  private:
-  std::u16string text_;
   bool search_engine_is_google_ = false;
   bool show_assistant_button_ = false;
 
diff --git a/ash/app_list/model/search/search_box_model_observer.h b/ash/app_list/model/search/search_box_model_observer.h
index 9434734..288d04c 100644
--- a/ash/app_list/model/search/search_box_model_observer.h
+++ b/ash/app_list/model/search/search_box_model_observer.h
@@ -13,9 +13,6 @@
 class APP_LIST_MODEL_EXPORT SearchBoxModelObserver
     : public base::CheckedObserver {
  public:
-  // Invoked when text or voice search flag is changed.
-  virtual void Update() = 0;
-
   // Invoked when the search engine is changed.
   virtual void SearchEngineChanged() = 0;
 
diff --git a/ash/app_list/views/app_list_bubble_view.cc b/ash/app_list/views/app_list_bubble_view.cc
index a0d0cac..43f4d40 100644
--- a/ash/app_list/views/app_list_bubble_view.cc
+++ b/ash/app_list/views/app_list_bubble_view.cc
@@ -614,6 +614,7 @@
 void AppListBubbleView::QueryChanged(const std::u16string& trimmed_query,
                                      bool initiated_by_user) {
   if (current_page_ != AppListBubblePage::kNone) {
+    search_page_->search_view()->UpdateForNewSearch(!trimmed_query.empty());
     if (!trimmed_query.empty())
       ShowPage(AppListBubblePage::kSearch);
     else
diff --git a/ash/app_list/views/app_list_main_view.cc b/ash/app_list/views/app_list_main_view.cc
index 87a1d40..778e189 100644
--- a/ash/app_list/views/app_list_main_view.cc
+++ b/ash/app_list/views/app_list_main_view.cc
@@ -130,6 +130,7 @@
                                             initiated_by_user);
   contents_view_->ShowSearchResults(search_box_view_->is_search_box_active() ||
                                     !trimmed_query.empty());
+  contents_view_->search_result_page_view()->UpdateForNewSearch();
 }
 
 void AppListMainView::ActiveChanged(SearchBoxViewBase* sender) {
@@ -140,17 +141,13 @@
   if (search_box_view_->is_search_box_active()) {
     // Show zero state suggestions when search box is activated with an empty
     // query.
-    SearchModel* const search_model =
-        AppListModelProvider::Get()->search_model();
-    const std::u16string raw_query = search_model->search_box()->text();
-    std::u16string query;
-    base::TrimWhitespace(raw_query, base::TRIM_ALL, &query);
+    const bool is_query_empty = sender->IsSearchBoxTrimmedQueryEmpty();
     if (features::IsProductivityLauncherEnabled()) {
       app_list_view_->SetStateFromSearchBoxView(
-          query.empty(), true /*triggered_by_contents_change*/);
+          is_query_empty, true /*triggered_by_contents_change*/);
       contents_view_->ShowSearchResults(true);
     } else {
-      if (query.empty())
+      if (is_query_empty)
         search_box_view_->ShowZeroStateSuggestions();
     }
   } else {
diff --git a/ash/app_list/views/app_list_view_unittest.cc b/ash/app_list/views/app_list_view_unittest.cc
index c4b63963..8216c65 100644
--- a/ash/app_list/views/app_list_view_unittest.cc
+++ b/ash/app_list/views/app_list_view_unittest.cc
@@ -15,7 +15,6 @@
 
 #include "ash/app_list/app_list_test_view_delegate.h"
 #include "ash/app_list/model/app_list_test_model.h"
-#include "ash/app_list/model/search/search_box_model.h"
 #include "ash/app_list/model/search/test_search_result.h"
 #include "ash/app_list/views/app_list_folder_view.h"
 #include "ash/app_list/views/app_list_item_view.h"
@@ -2571,8 +2570,8 @@
       search_text,
       ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
   // Check that the current search is using |search_text|.
-  EXPECT_EQ(search_text, GetSearchModel()->search_box()->text());
   EXPECT_EQ(search_text, main_view->search_box_view()->search_box()->GetText());
+  EXPECT_EQ(search_text, main_view->search_box_view()->current_query());
   contents_view->Layout();
   EXPECT_TRUE(
       contents_view->IsStateActive(ash::AppListState::kStateSearchResults));
@@ -2592,9 +2591,9 @@
       new_search_text,
       ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
   // Check that the current search is using |new_search_text|.
-  EXPECT_EQ(new_search_text, GetSearchModel()->search_box()->text());
   EXPECT_EQ(new_search_text,
             main_view->search_box_view()->search_box()->GetText());
+  EXPECT_EQ(search_text, main_view->search_box_view()->current_query());
   contents_view->Layout();
   EXPECT_TRUE(IsStateShown(ash::AppListState::kStateSearchResults));
   EXPECT_TRUE(CheckSearchBoxWidget(contents_view->GetSearchBoxBounds(
diff --git a/ash/app_list/views/productivity_launcher_search_view.cc b/ash/app_list/views/productivity_launcher_search_view.cc
index d24242d5..bc3abaa61 100644
--- a/ash/app_list/views/productivity_launcher_search_view.cc
+++ b/ash/app_list/views/productivity_launcher_search_view.cc
@@ -145,8 +145,6 @@
 
   AppListModelProvider* const model_provider = AppListModelProvider::Get();
   model_provider->AddObserver(this);
-  search_box_model_observer_.Observe(
-      model_provider->search_model()->search_box());
 }
 
 ProductivityLauncherSearchView::~ProductivityLauncherSearchView() {
@@ -268,8 +266,7 @@
   node_data->role = ax::mojom::Role::kListBox;
 
   std::u16string value;
-  std::u16string query =
-      AppListModelProvider::Get()->search_model()->search_box()->text();
+  const std::u16string& query = search_box_view_->current_query();
   if (!query.empty()) {
     if (last_search_result_count_ == 1) {
       value = l10n_util::GetStringFUTF16(
@@ -295,26 +292,24 @@
     SearchModel* search_model) {
   for (auto* container : result_container_views_)
     container->SetResults(search_model->results());
-  search_box_model_observer_.Reset();
-  search_box_model_observer_.Observe(search_model->search_box());
 }
 
-void ProductivityLauncherSearchView::Update() {
+void ProductivityLauncherSearchView::UpdateForNewSearch(bool search_active) {
   if (app_list_features::IsDynamicSearchUpdateAnimationEnabled()) {
-    // Scan result_container_views_ to see if there are any in progress
-    // animations when the search model is updated.
-    for (SearchResultContainerView* view : result_container_views_) {
-      if (view->HasAnimatingChildView()) {
-        search_result_fast_update_time_ = base::TimeTicks::Now();
+    if (search_active) {
+      // Scan result_container_views_ to see if there are any in progress
+      // animations when the search model is updated.
+      for (SearchResultContainerView* view : result_container_views_) {
+        if (view->HasAnimatingChildView()) {
+          search_result_fast_update_time_ = base::TimeTicks::Now();
+        }
       }
+    } else {
+      search_result_fast_update_time_.reset();
     }
   }
 }
 
-void ProductivityLauncherSearchView::SearchEngineChanged() {}
-
-void ProductivityLauncherSearchView::ShowAssistantChanged() {}
-
 void ProductivityLauncherSearchView::OnSelectedResultChanged() {
   if (!result_selection_controller_->selected_result()) {
     return;
diff --git a/ash/app_list/views/productivity_launcher_search_view.h b/ash/app_list/views/productivity_launcher_search_view.h
index ecad3cb..a392464 100644
--- a/ash/app_list/views/productivity_launcher_search_view.h
+++ b/ash/app_list/views/productivity_launcher_search_view.h
@@ -10,8 +10,6 @@
 #include <vector>
 
 #include "ash/app_list/app_list_model_provider.h"
-#include "ash/app_list/model/search/search_box_model.h"
-#include "ash/app_list/model/search/search_box_model_observer.h"
 #include "ash/app_list/views/search_result_container_view.h"
 #include "ash/ash_export.h"
 #include "base/time/time.h"
@@ -32,8 +30,7 @@
 class ASH_EXPORT ProductivityLauncherSearchView
     : public views::View,
       public SearchResultContainerView::Delegate,
-      public AppListModelProvider::Observer,
-      public SearchBoxModelObserver {
+      public AppListModelProvider::Observer {
  public:
   METADATA_HEADER(ProductivityLauncherSearchView);
 
@@ -58,10 +55,11 @@
   void OnActiveAppListModelsChanged(AppListModel* model,
                                     SearchModel* search_model) override;
 
-  // Overridden from SearchBoxModelObserver:
-  void Update() override;
-  void SearchEngineChanged() override;
-  void ShowAssistantChanged() override;
+  // Called when the app list search query changes and new search is about to
+  // start or cleared.
+  // `search_active` - whether search update will result in a new search. This
+  // will be false when the search is about to be cleared using an empty query.
+  void UpdateForNewSearch(bool search_active);
 
   // Returns true if there are search results that can be keyboard selected.
   bool CanSelectSearchResults();
@@ -144,9 +142,6 @@
 
   // The last reported number of search results shown by all containers.
   int last_search_result_count_ = 0;
-
-  base::ScopedObservation<SearchBoxModel, SearchBoxModelObserver>
-      search_box_model_observer_{this};
 };
 
 }  // namespace ash
diff --git a/ash/app_list/views/search_box_view.cc b/ash/app_list/views/search_box_view.cc
index 8d71383..c4bedc8d 100644
--- a/ash/app_list/views/search_box_view.cc
+++ b/ash/app_list/views/search_box_view.cc
@@ -368,6 +368,7 @@
       // Set 'user_initiated_model_update_time_' when initiating a new query.
       user_initiated_model_update_time_ = current_time;
     } else if (!current_query_.empty() && query.empty()) {
+      base::RecordAction(base::UserMetricsAction("AppList_LeaveSearch"));
       // Reset 'user_initiated_model_update_time_' when clearing the search_box.
       user_initiated_model_update_time_ = base::TimeTicks();
     } else if (query != current_query_ &&
@@ -395,15 +396,6 @@
   if (query_empty_changed)
     SchedulePaint();
 
-  // Temporarily remove from observer to ignore notifications caused by us.
-  search_box_model_observer_.Reset();
-
-  SearchBoxModel* const search_box_model =
-      AppListModelProvider::Get()->search_model()->search_box();
-
-  search_box_model->Update(query, initiated_by_user);
-  search_box_model_observer_.Observe(search_box_model);
-
   delegate_->QueryChanged(trimmed_query, initiated_by_user);
 
   // Don't reinitiate zero state search if the previous query was already empty
@@ -1251,16 +1243,6 @@
   }
 }
 
-void SearchBoxView::Update() {
-  const std::u16string text =
-      AppListModelProvider::Get()->search_model()->search_box()->text();
-  search_box()->SetText(text);
-  UpdateButtonsVisibility();
-  std::u16string trimmed_text;
-  base::TrimWhitespace(text, base::TrimPositions::TRIM_ALL, &trimmed_text);
-  delegate_->QueryChanged(trimmed_text, false);
-}
-
 void SearchBoxView::SearchEngineChanged() {
   UpdateSearchIcon();
 }
diff --git a/ash/app_list/views/search_box_view.h b/ash/app_list/views/search_box_view.h
index 41d56dce..836b270a 100644
--- a/ash/app_list/views/search_box_view.h
+++ b/ash/app_list/views/search_box_view.h
@@ -165,6 +165,8 @@
     highlight_range_ = range;
   }
 
+  const std::u16string& current_query() const { return current_query_; }
+
   // Update search box view background when result container visibility changes.
   void OnResultContainerVisibilityChanged(bool visible);
 
@@ -218,7 +220,6 @@
                           const ui::GestureEvent& gesture_event) override;
 
   // Overridden from SearchBoxModelObserver:
-  void Update() override;
   void SearchEngineChanged() override;
   void ShowAssistantChanged() override;
 
diff --git a/ash/app_list/views/search_box_view_delegate.h b/ash/app_list/views/search_box_view_delegate.h
index f41da54..750a5fd 100644
--- a/ash/app_list/views/search_box_view_delegate.h
+++ b/ash/app_list/views/search_box_view_delegate.h
@@ -17,7 +17,8 @@
 
 class SearchBoxViewDelegate {
  public:
-  // Invoked when query text in the search box changes.
+  // Invoked when query text in the search box changes, just before initiating
+  // search request for the query.
   // `trimmed_query` - the search boxt textfiled contents with whitespace
   // trimmed (which will generally match the query sent to search providers).
   // `initiated_by_user` - whether the query changed as a result of user input
diff --git a/ash/app_list/views/search_box_view_unittest.cc b/ash/app_list/views/search_box_view_unittest.cc
index c85d3fd..1f7ab64 100644
--- a/ash/app_list/views/search_box_view_unittest.cc
+++ b/ash/app_list/views/search_box_view_unittest.cc
@@ -1,4 +1,3 @@
-
 // Copyright 2014 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.
@@ -22,7 +21,9 @@
 #include "ash/app_list/views/productivity_launcher_search_view.h"
 #include "ash/app_list/views/result_selection_controller.h"
 #include "ash/app_list/views/search_box_view_delegate.h"
+#include "ash/app_list/views/search_result_list_view.h"
 #include "ash/app_list/views/search_result_page_view.h"
+#include "ash/app_list/views/search_result_tile_item_list_view.h"
 #include "ash/constants/ash_features.h"
 #include "ash/public/cpp/app_list/app_list_color_provider.h"
 #include "ash/public/cpp/app_list/app_list_features.h"
@@ -139,32 +140,21 @@
       // Initialize SearchBoxView like clamshell productivity launcher.
       view = std::make_unique<SearchBoxView>(this, &view_delegate_,
                                              /*app_list_view=*/nullptr);
-    } else {
-      // Initialize SearchBoxView like peeking launcher.
-      app_list_view_ = new AppListView(&view_delegate_);
-      app_list_view_->InitView(GetContext());
-      view = std::make_unique<SearchBoxView>(this, &view_delegate_,
-                                             app_list_view_);
-    }
-
-    if (IsProductivityLauncherEnabled())
       view->InitializeForBubbleLauncher();
-    else
-      view->InitializeForFullscreenLauncher();
-    view_ = widget_->GetContentsView()->AddChildView(std::move(view));
+      view_ = widget_->GetContentsView()->AddChildView(std::move(view));
 
-    if (IsProductivityLauncherEnabled()) {
       productivity_launcher_search_view_ =
           widget_->GetContentsView()->AddChildView(
               std::make_unique<ProductivityLauncherSearchView>(
                   &view_delegate_, /*dialog_controller=*/nullptr, view_));
+      widget_->Show();
     } else {
-      counter_view_ = widget_->GetContentsView()->AddChildView(
-          std::make_unique<KeyPressCounterView>(app_list_view_));
-      counter_view_->Init();
-      SetContentsView(counter_view_);
+      // Initialize SearchBoxView like peeking launcher.
+      app_list_view_ = new AppListView(&view_delegate_);
+      app_list_view_->InitView(GetContext());
+      view_ = app_list_view_->search_box_view();
+      app_list_view_->Show(AppListViewState::kPeeking, false);
     }
-    widget_->Show();
   }
 
   void TearDown() override {
@@ -238,16 +228,20 @@
 
   SearchModel::SearchResults* results() { return GetSearchModel()->results(); }
 
+  SearchResultPageView* GetSearchResultPageView() {
+    DCHECK(!IsProductivityLauncherEnabled());
+    return app_list_view()
+        ->app_list_main_view()
+        ->contents_view()
+        ->search_result_page_view();
+  }
   SearchResultBaseView* GetFirstResultView() {
     if (IsProductivityLauncherEnabled()) {
       return productivity_launcher_search_view_
           ->result_container_views_for_test()[kBestMatchIndex]
           ->GetFirstResultView();
     }
-    return view()
-        ->contents_view()
-        ->search_result_page_view()
-        ->first_result_view();
+    return GetSearchResultPageView()->first_result_view();
   }
 
   ResultSelectionController* GetResultSelectionController() {
@@ -255,32 +249,32 @@
       return productivity_launcher_search_view_
           ->result_selection_controller_for_test();
     }
-    return view()
-        ->contents_view()
-        ->search_result_page_view()
-        ->result_selection_controller();
+    return GetSearchResultPageView()->result_selection_controller();
   }
 
   void OnSearchResultContainerResultsChanged() {
     if (IsProductivityLauncherEnabled()) {
       productivity_launcher_search_view_
           ->OnSearchResultContainerResultsChanged();
+    } else {
+      return GetSearchResultPageView()->OnSearchResultContainerResultsChanged();
     }
-    view()
-        ->contents_view()
-        ->search_result_page_view()
-        ->OnSearchResultContainerResultsChanged();
   }
 
   void SimulateQuery(const std::u16string& query) {
     view()->search_box()->InsertText(
-        u"test",
+        query,
         ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorAfterText);
   }
 
   // Overridden from SearchBoxViewDelegate:
   void QueryChanged(const std::u16string& trimmed_query,
-                    bool initiated_by_user) override {}
+                    bool initiated_by_user) override {
+    if (IsProductivityLauncherEnabled()) {
+      productivity_launcher_search_view_->UpdateForNewSearch(
+          !trimmed_query.empty());
+    }
+  }
   void AssistantButtonPressed() override {}
   void CloseButtonPressed() override {}
   void ActiveChanged(SearchBoxViewBase* sender) override {}
@@ -302,6 +296,18 @@
 // Run search box view tests with and without productivity launcher enabled.
 INSTANTIATE_TEST_SUITE_P(All, SearchBoxViewTest, testing::Bool());
 
+class SearchBoxViewWithSuggestedContentTest : public SearchBoxViewTest {
+ public:
+  void SetUp() override {
+    view_delegate_.SetShouldShowSuggestedContentInfo(true);
+    SearchBoxViewTest::SetUp();
+  }
+};
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         SearchBoxViewWithSuggestedContentTest,
+                         testing::Values(false));
+
 TEST_P(SearchBoxViewTest, SearchBoxTextUsesAppListSearchBoxTextColor) {
   EXPECT_EQ(view()->search_box()->GetTextColor(),
             AppListColorProvider::Get()->GetSearchBoxTextColor(
@@ -424,6 +430,7 @@
   const SearchResultBaseView* selection =
       GetResultSelectionController()->selected_result();
 
+  ASSERT_TRUE(selection);
   EXPECT_EQ(GetFirstResultView(), selection);
   ASSERT_TRUE(selection->result());
   EXPECT_EQ(u"tester", selection->result()->title());
@@ -715,20 +722,9 @@
   EXPECT_EQ(2, user_action_tester.GetActionCount("AppList_SearchQueryStarted"));
 }
 
-TEST_P(SearchBoxViewTest, NavigateSuggestedContentInfo) {
-  // PrivacyContainerViews are deprecated for productivity launcher.
-  if (IsProductivityLauncherEnabled())
-    return;
-  // Create a new contents view that contains a suggested content info.
-  view_delegate()->SetShouldShowSuggestedContentInfo(true);
-  auto* contents_view = widget()->GetContentsView()->AddChildView(
-      std::make_unique<KeyPressCounterView>(app_list_view()));
-  contents_view->Init();
-  SetContentsView(contents_view);
-
+TEST_P(SearchBoxViewWithSuggestedContentTest, NavigateSuggestedContentInfo) {
   PrivacyContainerView* const privacy_container_view =
-      contents_view->search_result_page_view()
-          ->GetPrivacyContainerViewForTest();
+      GetSearchResultPageView()->GetPrivacyContainerViewForTest();
   ASSERT_TRUE(privacy_container_view);
 
   // Set up the search box.
@@ -771,20 +767,10 @@
   EXPECT_FALSE(selection);
 }
 
-TEST_P(SearchBoxViewTest, KeyboardEventClosesSuggestedContentInfo) {
-  // PrivacyContainerViews are deprecated for productivity launcher.
-  if (IsProductivityLauncherEnabled())
-    return;
-  // Create a new contents view that contains a suggested content info.
-  view_delegate()->SetShouldShowSuggestedContentInfo(true);
-  auto* contents_view = widget()->GetContentsView()->AddChildView(
-      std::make_unique<KeyPressCounterView>(app_list_view()));
-  contents_view->Init();
-  SetContentsView(contents_view);
-
+TEST_P(SearchBoxViewWithSuggestedContentTest,
+       KeyboardEventClosesSuggestedContentInfo) {
   PrivacyContainerView* const privacy_container_view =
-      contents_view->search_result_page_view()
-          ->GetPrivacyContainerViewForTest();
+      GetSearchResultPageView()->GetPrivacyContainerViewForTest();
   ASSERT_TRUE(privacy_container_view);
 
   // Set up the search box.
@@ -793,7 +779,7 @@
                      std::u16string());
   base::RunLoop().RunUntilIdle();
 
-  EXPECT_EQ(contents_view->search_result_page_view()
+  EXPECT_EQ(GetSearchResultPageView()
                 ->result_selection_controller()
                 ->selected_result(),
             privacy_container_view->GetResultViewAt(0));
@@ -805,20 +791,10 @@
   EXPECT_FALSE(view_delegate()->ShouldShowSuggestedContentInfo());
 }
 
-TEST_P(SearchBoxViewTest, SuggestedContentActionNotOverriddenByNewResults) {
-  // PrivacyContainerView is being deprecated for Productivity Launcher.
-  if (IsProductivityLauncherEnabled())
-    return;
-  // Create a new contents view that contains a privacy notice.
-  view_delegate()->SetShouldShowSuggestedContentInfo(true);
-  auto* contents_view = widget()->GetContentsView()->AddChildView(
-      std::make_unique<KeyPressCounterView>(app_list_view()));
-  contents_view->Init();
-  SetContentsView(contents_view);
-
+TEST_P(SearchBoxViewWithSuggestedContentTest,
+       SuggestedContentActionNotOverriddenByNewResults) {
   PrivacyContainerView* const privacy_container_view =
-      contents_view->search_result_page_view()
-          ->GetPrivacyContainerViewForTest();
+      GetSearchResultPageView()->GetPrivacyContainerViewForTest();
   ASSERT_TRUE(privacy_container_view);
 
   // Set up the search box.
@@ -828,7 +804,7 @@
   base::RunLoop().RunUntilIdle();
 
   ResultSelectionController* const selection_controller =
-      contents_view->search_result_page_view()->result_selection_controller();
+      GetResultSelectionController();
   const SearchResultBaseView* selection =
       selection_controller->selected_result();
   EXPECT_EQ(selection, privacy_container_view->GetResultViewAt(0));
@@ -851,20 +827,10 @@
   EXPECT_EQ(selection->result()->title(), u"test");
 }
 
-TEST_P(SearchBoxViewTest, SuggestedContentSelectionDoesNotChangeSearchBoxText) {
-  // PrivacyContainerView is being deprecated for Productivity Launcher.
-  if (IsProductivityLauncherEnabled())
-    return;
-  // Create a new contents view that contains a suggested content info.
-  view_delegate()->SetShouldShowSuggestedContentInfo(true);
-  auto* contents_view = widget()->GetContentsView()->AddChildView(
-      std::make_unique<KeyPressCounterView>(app_list_view()));
-  contents_view->Init();
-  SetContentsView(contents_view);
-
+TEST_P(SearchBoxViewWithSuggestedContentTest,
+       SuggestedContentSelectionDoesNotChangeSearchBoxText) {
   PrivacyContainerView* const privacy_container_view =
-      contents_view->search_result_page_view()
-          ->GetPrivacyContainerViewForTest();
+      GetSearchResultPageView()->GetPrivacyContainerViewForTest();
   ASSERT_TRUE(privacy_container_view);
 
   // Set up the search box.
@@ -874,7 +840,7 @@
   base::RunLoop().RunUntilIdle();
 
   ResultSelectionController* const selection_controller =
-      contents_view->search_result_page_view()->result_selection_controller();
+      GetResultSelectionController();
   EXPECT_EQ(selection_controller->selected_result(),
             privacy_container_view->GetResultViewAt(0));
   EXPECT_TRUE(view()->search_box()->GetText().empty());
@@ -949,14 +915,13 @@
   // Sets up the test by creating a SearchResult and displaying an autocomplete
   // suggestion.
   void SetupAutocompleteBehaviorTest() {
+    // Send H, E to the SearchBoxView textfield, then trigger an autocomplete.
+    KeyPress(ui::VKEY_H);
+    KeyPress(ui::VKEY_E);
     // Add a search result with a non-empty title field.
     CreateSearchResult(ash::SearchResultDisplayType::kList, 1.0,
                        u"hello world!", std::u16string());
     base::RunLoop().RunUntilIdle();
-
-    // Send H, E to the SearchBoxView textfield, then trigger an autocomplete.
-    KeyPress(ui::VKEY_H);
-    KeyPress(ui::VKEY_E);
     ProcessAutocomplete();
   }
 };
@@ -972,6 +937,9 @@
   // Search result tile tests are not relevant for productivity launcher.
   if (IsProductivityLauncherEnabled())
     return;
+
+  SimulateQuery(u"he");
+
   // Add two SearchResults, one tile and one list result. Initialize their title
   // field to a non-empty string.
   CreateSearchResult(ash::SearchResultDisplayType::kList, 1.0, u"hello list",
@@ -980,11 +948,7 @@
                      std::u16string());
   base::RunLoop().RunUntilIdle();
 
-  // Send H, E to the SearchBoxView textfield, then trigger an autocomplete.
-  KeyPress(ui::VKEY_H);
-  KeyPress(ui::VKEY_E);
   ProcessAutocomplete();
-
   EXPECT_EQ(view()->search_box()->GetText(), u"hello tile");
   EXPECT_EQ(view()->search_box()->GetSelectedText(), u"llo tile");
 }
@@ -996,6 +960,9 @@
   // Search result tile tests are not relevant for productivity launcher.
   if (IsProductivityLauncherEnabled())
     return;
+
+  SimulateQuery(u"he");
+
   // Add two SearchResults, one tile and one list result. Initialize their title
   // field to a non-empty string.
   CreateSearchResult(ash::SearchResultDisplayType::kTile, 1.0, u"hello tile",
@@ -1004,9 +971,6 @@
                      std::u16string());
   base::RunLoop().RunUntilIdle();
 
-  // Send H, E to the SearchBoxView textfield, then trigger an autocomplete.
-  KeyPress(ui::VKEY_H);
-  KeyPress(ui::VKEY_E);
   ProcessAutocomplete();
   EXPECT_EQ(view()->search_box()->GetText(), u"hello tile");
   EXPECT_EQ(view()->search_box()->GetSelectedText(), u"llo tile");
@@ -1019,6 +983,9 @@
   // Search result tile tests are not relevant for productivity launcher.
   if (IsProductivityLauncherEnabled())
     return;
+
+  SimulateQuery(u"he");
+
   // Add two SearchResults, one tile and one list result. The tile should
   // display first, despite having a lower score. Initialize their details field
   // to a non-empty string.
@@ -1028,9 +995,6 @@
                      u"hello tile");
   base::RunLoop().RunUntilIdle();
 
-  // Send H, E to the SearchBoxView textfield, then trigger an autocomplete.
-  KeyPress(ui::VKEY_H);
-  KeyPress(ui::VKEY_E);
   ProcessAutocomplete();
   EXPECT_EQ(view()->search_box()->GetText(), u"hello tile");
   EXPECT_EQ(view()->search_box()->GetSelectedText(), u"llo tile");
@@ -1043,6 +1007,9 @@
   // Search result tile tests are not relevant for productivity launcher.
   if (IsProductivityLauncherEnabled())
     return;
+
+  SimulateQuery(u"he");
+
   // Add two SearchResults, one tile and one list result. Initialize their
   // details field to a non-empty string.
   CreateSearchResult(ash::SearchResultDisplayType::kTile, 1.0, std::u16string(),
@@ -1051,9 +1018,6 @@
                      u"hello list");
   base::RunLoop().RunUntilIdle();
 
-  // Send H, E to the SearchBoxView textfield, then trigger an autocomplete.
-  KeyPress(ui::VKEY_H);
-  KeyPress(ui::VKEY_E);
   ProcessAutocomplete();
   EXPECT_EQ(view()->search_box()->GetText(), u"hello tile");
   EXPECT_EQ(view()->search_box()->GetSelectedText(), u"llo tile");
@@ -1063,13 +1027,12 @@
 // result title or details do not have a matching prefix.
 TEST_P(SearchBoxViewAutocompleteTest,
        SearchBoxDoesNotAutocompleteWrongCharacter) {
+  // Send Z to the SearchBoxView textfield, then trigger an autocomplete.
+  KeyPress(ui::VKEY_Z);
   // Add a search result with non-empty details and title fields.
   CreateSearchResult(ash::SearchResultDisplayType::kList, 1.0, u"title",
                      u"details");
   base::RunLoop().RunUntilIdle();
-
-  // Send Z to the SearchBoxView textfield, then trigger an autocomplete.
-  KeyPress(ui::VKEY_Z);
   ProcessAutocomplete();
   // The text should not be autocompleted.
   EXPECT_EQ(view()->search_box()->GetText(), u"z");
@@ -1078,14 +1041,11 @@
 // Tests that autocomplete suggestion will remain if next key in the suggestion
 // is typed.
 TEST_P(SearchBoxViewAutocompleteTest, SearchBoxAutocompletesAcceptsNextChar) {
+  SimulateQuery(u"he");
   // Add a search result with a non-empty title field.
   CreateSearchResult(ash::SearchResultDisplayType::kList, 1.0, u"hello world!",
                      std::u16string());
   base::RunLoop().RunUntilIdle();
-
-  // Send H, E to the SearchBoxView textfield, then trigger an autocomplete.
-  KeyPress(ui::VKEY_H);
-  KeyPress(ui::VKEY_E);
   ProcessAutocomplete();
 
   // After typing L, the highlighted text will be replaced by L.
@@ -1118,7 +1078,7 @@
   // Search box autocomplete suggestion is accepted, but it should not
   // trigger another query, thus it is not reflected in Search Model.
   EXPECT_EQ(u"hello world!", view()->search_box()->GetText());
-  EXPECT_EQ(u"he", GetSearchModel()->search_box()->text());
+  EXPECT_EQ(u"he", view()->current_query());
 }
 
 TEST_P(SearchBoxViewAutocompleteTest, SearchBoxAcceptsAutocompleteForTap) {
@@ -1137,20 +1097,20 @@
   // Search box autocomplete suggestion is accepted, but it should not
   // trigger another query, thus it is not reflected in Search Model.
   EXPECT_EQ(u"hello world!", view()->search_box()->GetText());
-  EXPECT_EQ(u"he", GetSearchModel()->search_box()->text());
+  EXPECT_EQ(u"he", view()->current_query());
 }
 
 // Tests that autocomplete is not handled if IME is using composition text.
 TEST_P(SearchBoxViewAutocompleteTest, SearchBoxAutocompletesNotHandledForIME) {
+  // Simulate uncomposited text. The autocomplete should be handled.
+  KeyPress(ui::VKEY_H);
+  KeyPress(ui::VKEY_E);
+  view()->set_highlight_range_for_test(gfx::Range(2, 2));
   // Add a search result with a non-empty title field.
   CreateSearchResult(ash::SearchResultDisplayType::kList, 1.0, u"hello world!",
                      std::u16string());
   base::RunLoop().RunUntilIdle();
 
-  // Simulate uncomposited text. The autocomplete should be handled.
-  KeyPress(ui::VKEY_H);
-  KeyPress(ui::VKEY_E);
-  view()->set_highlight_range_for_test(gfx::Range(2, 2));
   ProcessAutocomplete();
 
   std::u16string selected_text = view()->search_box()->GetSelectedText();
diff --git a/ash/app_list/views/search_result_page_view.cc b/ash/app_list/views/search_result_page_view.cc
index 47deac9..1535e9b 100644
--- a/ash/app_list/views/search_result_page_view.cc
+++ b/ash/app_list/views/search_result_page_view.cc
@@ -251,7 +251,6 @@
 
   AppListModelProvider* const model_provider = AppListModelProvider::Get();
   model_provider->AddObserver(this);
-  search_box_observation_.Observe(model_provider->search_model()->search_box());
 }
 
 SearchResultPageView::~SearchResultPageView() {
@@ -369,8 +368,8 @@
     node_data->role = ax::mojom::Role::kListBox;
 
   std::u16string value;
-  std::u16string query =
-      AppListModelProvider::Get()->search_model()->search_box()->text();
+  SearchBoxView* search_box = AppListPage::contents_view()->GetSearchBoxView();
+  const std::u16string& query = search_box->current_query();
   if (!query.empty()) {
     if (last_search_result_count_ == 1) {
       value = l10n_util::GetStringFUTF16(
@@ -402,6 +401,14 @@
   AppListPage::OnThemeChanged();
 }
 
+void SearchResultPageView::UpdateForNewSearch() {
+  notify_a11y_results_changed_timer_.Stop();
+  if (productivity_launcher_search_view_) {
+    productivity_launcher_search_view_->UpdateForNewSearch(
+        ShouldShowSearchResultView());
+  }
+}
+
 void SearchResultPageView::UpdateResultContainersVisibility() {
   bool should_show_page_view = false;
   if (features::IsProductivityLauncherEnabled()) {
@@ -683,8 +690,6 @@
 void SearchResultPageView::OnActiveAppListModelsChanged(
     AppListModel* model,
     SearchModel* search_model) {
-  search_box_observation_.Reset();
-  search_box_observation_.Observe(search_model->search_box());
   for (auto* container : result_container_views_)
     container->SetResults(search_model->results());
 }
@@ -742,14 +747,6 @@
       first_result_view_);
 }
 
-void SearchResultPageView::Update() {
-  notify_a11y_results_changed_timer_.Stop();
-}
-
-void SearchResultPageView::SearchEngineChanged() {}
-
-void SearchResultPageView::ShowAssistantChanged() {}
-
 bool SearchResultPageView::CanSelectSearchResults() const {
   if (!GetVisible())
     return false;
@@ -781,11 +778,9 @@
 }
 
 bool SearchResultPageView::ShouldShowSearchResultView() const {
-  SearchModel* search_model = AppListModelProvider::Get()->search_model();
+  SearchBoxView* search_box = AppListPage::contents_view()->GetSearchBoxView();
   return (!features::IsProductivityLauncherEnabled() ||
-          !base::TrimWhitespace(search_model->search_box()->text(),
-                                base::TrimPositions::TRIM_ALL)
-               .empty());
+          search_box->HasValidQuery());
 }
 
 void SearchResultPageView::OnHidden() {
diff --git a/ash/app_list/views/search_result_page_view.h b/ash/app_list/views/search_result_page_view.h
index 16abd7df..3d93bba 100644
--- a/ash/app_list/views/search_result_page_view.h
+++ b/ash/app_list/views/search_result_page_view.h
@@ -11,8 +11,6 @@
 
 #include "ash/app_list/app_list_model_provider.h"
 #include "ash/app_list/model/app_list_model.h"
-#include "ash/app_list/model/search/search_box_model.h"
-#include "ash/app_list/model/search/search_box_model_observer.h"
 #include "ash/app_list/views/app_list_page.h"
 #include "ash/app_list/views/result_selection_controller.h"
 #include "ash/app_list/views/search_result_container_view.h"
@@ -37,8 +35,7 @@
 class ASH_EXPORT SearchResultPageView
     : public AppListPage,
       public AppListModelProvider::Observer,
-      public SearchResultContainerView::Delegate,
-      public SearchBoxModelObserver {
+      public SearchResultContainerView::Delegate {
  public:
   SearchResultPageView();
 
@@ -96,11 +93,6 @@
   void OnSearchResultContainerResultsChanging() override;
   void OnSearchResultContainerResultsChanged() override;
 
-  // Overridden from SearchBoxModelObserver:
-  void Update() override;
-  void SearchEngineChanged() override;
-  void ShowAssistantChanged() override;
-
   // Whether any results are available for selection within the search result
   // UI.
   bool CanSelectSearchResults() const;
@@ -126,6 +118,10 @@
   // Hide zero state search result view when ProductivityLauncher is enabled.
   bool ShouldShowSearchResultView() const;
 
+  // Called when the app list search query changes and new search is about to
+  // start or cleared.
+  void UpdateForNewSearch();
+
   // Sets visibility of result container and separator views so only containers
   // that contain some results are shown.
   void UpdateResultContainersVisibility();
@@ -248,9 +244,6 @@
 
   // The controller that manages dialogs modal to the search results page.
   std::unique_ptr<SearchResultPageDialogController> dialog_controller_;
-
-  base::ScopedObservation<SearchBoxModel, SearchBoxModelObserver>
-      search_box_observation_{this};
 };
 
 }  // namespace ash
diff --git a/ash/ash_prefs.cc b/ash/ash_prefs.cc
index bb86572a..51b37511 100644
--- a/ash/ash_prefs.cc
+++ b/ash/ash_prefs.cc
@@ -60,6 +60,7 @@
 #include "ash/wm/window_cycle/window_cycle_controller.h"
 #include "chromeos/ash/services/assistant/public/cpp/assistant_prefs.h"
 #include "chromeos/components/quick_answers/public/cpp/quick_answers_prefs.h"
+#include "chromeos/ui/wm/fullscreen/pref_names.h"
 #include "components/language/core/browser/pref_names.h"
 #include "components/live_caption/pref_names.h"
 #include "components/soda/constants.h"
@@ -128,6 +129,8 @@
     registry->RegisterBooleanPref(chromeos::prefs::kSuggestedContentEnabled,
                                   true);
     registry->RegisterBooleanPref(::prefs::kLiveCaptionEnabled, false);
+    registry->RegisterListPref(
+        chromeos::prefs::kKeepFullscreenWithoutNotificationUrlAllowList);
     registry->RegisterStringPref(::prefs::kLiveCaptionLanguageCode,
                                  speech::kUsEnglishLocale);
     registry->RegisterStringPref(language::prefs::kApplicationLocale,
diff --git a/ash/components/arc/enterprise/arc_data_snapshotd_manager.cc b/ash/components/arc/enterprise/arc_data_snapshotd_manager.cc
index 098ebf7..f6aee65 100644
--- a/ash/components/arc/enterprise/arc_data_snapshotd_manager.cc
+++ b/ash/components/arc/enterprise/arc_data_snapshotd_manager.cc
@@ -123,32 +123,30 @@
   UpdateCreationDate(base::Time::Now());
 }
 
-ArcDataSnapshotdManager::SnapshotInfo::SnapshotInfo(const base::Value* value,
-                                                    bool is_last)
+ArcDataSnapshotdManager::SnapshotInfo::SnapshotInfo(
+    const base::Value::Dict& dict,
+    bool is_last)
     : is_last_(is_last) {
-  const base::DictionaryValue* dict;
-  if (!value || !value->GetAsDictionary(&dict) || !dict)
-    return;
   {
-    auto* found = dict->FindStringPath(kOsVersion);
+    auto* found = dict.FindString(kOsVersion);
     if (found)
       os_version_ = *found;
   }
   {
-    auto* found = dict->FindPath(kCreationDate);
+    auto* found = dict.Find(kCreationDate);
     if (found && base::ValueToTime(found).has_value()) {
       auto parsed_time = base::ValueToTime(found).value();
       UpdateCreationDate(parsed_time);
     }
   }
   {
-    auto found = dict->FindBoolPath(kVerified);
+    auto found = dict.FindBool(kVerified);
     if (found.has_value())
       verified_ = found.value();
   }
 
   {
-    auto found = dict->FindBoolPath(kUpdated);
+    auto found = dict.FindBool(kUpdated);
     if (found.has_value())
       updated_ = found.value();
   }
@@ -168,17 +166,14 @@
       os_version, creation_date, verified, updated, is_last));
 }
 
-void ArcDataSnapshotdManager::SnapshotInfo::Sync(base::Value* dict) {
-  if (!dict)
-    return;
+void ArcDataSnapshotdManager::SnapshotInfo::Sync(base::Value::Dict& dict) {
+  base::Value::Dict value;
+  value.Set(kOsVersion, os_version_);
+  value.Set(kCreationDate, base::TimeToValue(creation_date_));
+  value.Set(kVerified, verified_);
+  value.Set(kUpdated, updated_);
 
-  base::DictionaryValue value;
-  value.SetStringKey(kOsVersion, os_version_);
-  value.SetKey(kCreationDate, base::TimeToValue(creation_date_));
-  value.SetBoolKey(kVerified, verified_);
-  value.SetBoolKey(kUpdated, updated_);
-
-  dict->SetKey(GetDictPath(), std::move(value));
+  dict.Set(GetDictPath(), std::move(value));
 }
 
 bool ArcDataSnapshotdManager::SnapshotInfo::IsExpired() const {
@@ -257,41 +252,39 @@
 }
 
 void ArcDataSnapshotdManager::Snapshot::Parse() {
-  const base::Value* dict =
-      local_state_->GetDictionary(arc::prefs::kArcSnapshotInfo);
-  if (!dict)
-    return;
+  const base::Value::Dict& dict =
+      local_state_->GetValueDict(arc::prefs::kArcSnapshotInfo);
   {
-    const auto* found = dict->FindDictPath(kPrevious);
+    const auto* found = dict.FindDict(kPrevious);
     if (found)
-      previous_snapshot_ = std::make_unique<SnapshotInfo>(found, false);
+      previous_snapshot_ = std::make_unique<SnapshotInfo>(*found, false);
   }
   {
-    const auto* found = dict->FindDictPath(kLast);
+    const auto* found = dict.FindDict(kLast);
     if (found)
-      last_snapshot_ = std::make_unique<SnapshotInfo>(found, true);
+      last_snapshot_ = std::make_unique<SnapshotInfo>(*found, true);
   }
   {
-    auto found = dict->FindBoolPath(kBlockedUiReboot);
+    auto found = dict.FindBool(kBlockedUiReboot);
     if (found.has_value())
       blocked_ui_mode_ = found.value();
   }
   {
-    auto found = dict->FindBoolPath(kStarted);
+    auto found = dict.FindBool(kStarted);
     if (found.has_value())
       started_ = found.value();
   }
 }
 
 void ArcDataSnapshotdManager::Snapshot::Sync() {
-  base::DictionaryValue dict;
+  base::Value::Dict dict;
   if (previous_snapshot_)
-    previous_snapshot_->Sync(&dict);
+    previous_snapshot_->Sync(dict);
   if (last_snapshot_)
-    last_snapshot_->Sync(&dict);
-  dict.SetBoolKey(kBlockedUiReboot, blocked_ui_mode_);
-  dict.SetBoolKey(kStarted, started_);
-  local_state_->Set(arc::prefs::kArcSnapshotInfo, std::move(dict));
+    last_snapshot_->Sync(dict);
+  dict.Set(kBlockedUiReboot, blocked_ui_mode_);
+  dict.Set(kStarted, started_);
+  local_state_->SetDict(arc::prefs::kArcSnapshotInfo, std::move(dict));
 }
 
 void ArcDataSnapshotdManager::Snapshot::Sync(base::OnceClosure callback) {
diff --git a/ash/components/arc/enterprise/arc_data_snapshotd_manager.h b/ash/components/arc/enterprise/arc_data_snapshotd_manager.h
index c13fee8..8db2c04 100644
--- a/ash/components/arc/enterprise/arc_data_snapshotd_manager.h
+++ b/ash/components/arc/enterprise/arc_data_snapshotd_manager.h
@@ -104,7 +104,7 @@
    public:
     // Creates new snapshot with current parameters.
     explicit SnapshotInfo(bool is_last);
-    SnapshotInfo(const base::Value* value, bool is_last);
+    SnapshotInfo(const base::Value::Dict& value, bool is_last);
     SnapshotInfo(const SnapshotInfo&) = delete;
     SnapshotInfo& operator=(const SnapshotInfo&) = delete;
     ~SnapshotInfo();
@@ -119,7 +119,7 @@
         bool is_last);
 
     // Syncs stored snapshot info to dictionaty |value|.
-    void Sync(base::Value* value);
+    void Sync(base::Value::Dict& value);
 
     // Returns true if snapshot is expired.
     bool IsExpired() const;
diff --git a/ash/components/arc/metrics/stability_metrics_manager.cc b/ash/components/arc/metrics/stability_metrics_manager.cc
index 39d63df3..eb2bddc4 100644
--- a/ash/components/arc/metrics/stability_metrics_manager.cc
+++ b/ash/components/arc/metrics/stability_metrics_manager.cc
@@ -47,11 +47,12 @@
 
 void StabilityMetricsManager::RecordMetricsToUMA() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  // GetDictionary() should never return null, but since this may be called
-  // early on browser startup, be paranoid here to prevent going into a crash
-  // loop.
-  if (!local_state_->GetDictionary(prefs::kStabilityMetrics)) {
-    NOTREACHED() << "Local state unavailable, not recording stabiltiy metrics.";
+  // FindPreference(prefs::kStabilityMetrics) should never return null, but
+  // since this may be called early on browser startup, be paranoid here to
+  // prevent going into a crash loop.
+  if (const auto* pref = local_state_->FindPreference(prefs::kStabilityMetrics);
+      !pref || pref->GetType() != base::Value::Type::DICT) {
+    NOTREACHED() << "Local state unavailable, not recording stability metrics.";
     return;
   }
 
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index afeb36b..c2fc8e5 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -839,6 +839,7 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsProjectorUpdateIndexableTextEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS)
 bool IsProjectorUseOAuthForGetVideoInfoEnabled();
+COMPONENT_EXPORT(ASH_CONSTANTS) bool IsQsRevampEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsQuickDimEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS)
 bool IsQuickSettingsNetworkRevampEnabled();
diff --git a/ash/constants/ash_pref_names.cc b/ash/constants/ash_pref_names.cc
index 68c49b07..29d9f53 100644
--- a/ash/constants/ash_pref_names.cc
+++ b/ash/constants/ash_pref_names.cc
@@ -447,11 +447,6 @@
 // A boolean pref that enable fullscreen alert bubble.
 // TODO(zxdan): Change to an allowlist in M89.
 const char kFullscreenAlertEnabled[] = "ash.fullscreen_alert_enabled";
-// A list of URLs that are allowed to continue full screen mode after session
-// unlock without a notification. To prevent fake login screens, the device
-// normally exits full screen mode before locking a session.
-const char kKeepFullscreenWithoutNotificationUrlAllowList[] =
-    "ash.keep_fullscreen_without_notification_url_allow_list";
 
 // A boolean pref storing whether the gesture education notification has ever
 // been shown to the user, which we use to stop showing it again.
diff --git a/ash/constants/ash_pref_names.h b/ash/constants/ash_pref_names.h
index b3b2d4d..e336523 100644
--- a/ash/constants/ash_pref_names.h
+++ b/ash/constants/ash_pref_names.h
@@ -214,8 +214,6 @@
 extern const char kAllowMGSToStoreDisplayProperties[];
 
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kFullscreenAlertEnabled[];
-COMPONENT_EXPORT(ASH_CONSTANTS)
-extern const char kKeepFullscreenWithoutNotificationUrlAllowList[];
 
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const char kGestureEducationNotificationShown[];
diff --git a/ash/display/display_prefs.cc b/ash/display/display_prefs.cc
index 245fdce..b723907d 100644
--- a/ash/display/display_prefs.cc
+++ b/ash/display/display_prefs.cc
@@ -72,13 +72,13 @@
 // This kind of boilerplates should be done by base::JSONValueConverter but it
 // doesn't support classes like gfx::Insets for now.
 // TODO(mukai): fix base::JSONValueConverter and use it here.
-bool ValueToInsets(const base::DictionaryValue& value, gfx::Insets* insets) {
+bool ValueToInsets(const base::Value::Dict& dict, gfx::Insets* insets) {
   DCHECK(insets);
 
-  absl::optional<int> top = value.FindIntKey(kInsetsTopKey);
-  absl::optional<int> left = value.FindIntKey(kInsetsLeftKey);
-  absl::optional<int> bottom = value.FindIntKey(kInsetsBottomKey);
-  absl::optional<int> right = value.FindIntKey(kInsetsRightKey);
+  absl::optional<int> top = dict.FindInt(kInsetsTopKey);
+  absl::optional<int> left = dict.FindInt(kInsetsLeftKey);
+  absl::optional<int> bottom = dict.FindInt(kInsetsBottomKey);
+  absl::optional<int> right = dict.FindInt(kInsetsRightKey);
   if (top && left && bottom && right) {
     *insets = gfx::Insets::TLBR(*top, *left, *bottom, *right);
     return true;
@@ -86,12 +86,11 @@
   return false;
 }
 
-void InsetsToValue(const gfx::Insets& insets, base::Value& value) {
-  DCHECK(value.is_dict());
-  value.SetIntKey(kInsetsTopKey, insets.top());
-  value.SetIntKey(kInsetsLeftKey, insets.left());
-  value.SetIntKey(kInsetsBottomKey, insets.bottom());
-  value.SetIntKey(kInsetsRightKey, insets.right());
+void InsetsToValue(const gfx::Insets& insets, base::Value::Dict& dict) {
+  dict.Set(kInsetsTopKey, insets.top());
+  dict.Set(kInsetsLeftKey, insets.left());
+  dict.Set(kInsetsBottomKey, insets.bottom());
+  dict.Set(kInsetsRightKey, insets.right());
 }
 
 // Unmarshalls the string containing CalibrationPointPairQuad and populates
@@ -125,20 +124,20 @@
 
 // Retrieves touch calibration associated data from the dictionary and stores
 // it in an instance of TouchCalibrationData struct.
-bool ValueToTouchData(const base::DictionaryValue& value,
+bool ValueToTouchData(const base::Value::Dict& dict,
                       display::TouchCalibrationData* touch_calibration_data) {
   display::TouchCalibrationData::CalibrationPointPairQuad* point_pair_quad =
       &(touch_calibration_data->point_pairs);
 
-  const std::string* str = value.FindStringKey(kTouchCalibrationPointPairs);
+  const std::string* str = dict.FindString(kTouchCalibrationPointPairs);
   if (!str)
     return false;
 
   if (!ParseTouchCalibrationStringValue(*str, point_pair_quad))
     return false;
 
-  absl::optional<int> width = value.FindIntKey(kTouchCalibrationWidth);
-  absl::optional<int> height = value.FindIntKey(kTouchCalibrationHeight);
+  absl::optional<int> width = dict.FindInt(kTouchCalibrationWidth);
+  absl::optional<int> height = dict.FindInt(kTouchCalibrationHeight);
   if (!width || !height) {
     return false;
   }
@@ -149,8 +148,7 @@
 // Stores the touch calibration data into the dictionary.
 void TouchDataToValue(
     const display::TouchCalibrationData& touch_calibration_data,
-    base::Value& value) {
-  DCHECK(value.is_dict());
+    base::Value::Dict& dict) {
   std::string str;
   for (std::size_t row = 0; row < touch_calibration_data.point_pairs.size();
        row++) {
@@ -168,11 +166,9 @@
     if (row != touch_calibration_data.point_pairs.size() - 1)
       str += " ";
   }
-  value.SetStringKey(kTouchCalibrationPointPairs, str);
-  value.SetIntKey(kTouchCalibrationWidth,
-                  touch_calibration_data.bounds.width());
-  value.SetIntKey(kTouchCalibrationHeight,
-                  touch_calibration_data.bounds.height());
+  dict.Set(kTouchCalibrationPointPairs, str);
+  dict.Set(kTouchCalibrationWidth, touch_calibration_data.bounds.width());
+  dict.Set(kTouchCalibrationHeight, touch_calibration_data.bounds.height());
 }
 
 display::DisplayManager* GetDisplayManager() {
@@ -225,8 +221,8 @@
 
 void LoadDisplayProperties(PrefService* local_state) {
   for (const auto it : local_state->GetValueDict(prefs::kDisplayProperties)) {
-    const base::DictionaryValue* dict_value = nullptr;
-    if (!it.second.GetAsDictionary(&dict_value) || dict_value == nullptr)
+    const base::Value::Dict* dict_value = it.second.GetIfDict();
+    if (!dict_value)
       continue;
     int64_t id = display::kInvalidDisplayId;
     if (!base::StringToInt64(it.first, &id) ||
@@ -236,18 +232,17 @@
     const gfx::Insets* insets_to_set = nullptr;
 
     display::Display::Rotation rotation = display::Display::ROTATE_0;
-    if (absl::optional<int> rotation_value =
-            dict_value->FindIntKey("rotation")) {
+    if (absl::optional<int> rotation_value = dict_value->FindInt("rotation")) {
       rotation = static_cast<display::Display::Rotation>(*rotation_value);
     }
 
-    int width = dict_value->FindIntKey("width").value_or(0);
-    int height = dict_value->FindIntKey("height").value_or(0);
+    int width = dict_value->FindInt("width").value_or(0);
+    int height = dict_value->FindInt("height").value_or(0);
     gfx::Size resolution_in_pixels(width, height);
 
     float device_scale_factor = 1.0;
     if (absl::optional<int> dsf_value =
-            dict_value->FindIntKey("device-scale-factor")) {
+            dict_value->FindInt("device-scale-factor")) {
       device_scale_factor = static_cast<float>(*dsf_value) / 1000.0f;
     }
 
@@ -258,9 +253,9 @@
     bool is_interlaced = false;
     if (display::features::IsListAllDisplayModesEnabled()) {
       refresh_rate =
-          dict_value->FindDoubleKey("refresh-rate").value_or(refresh_rate);
+          dict_value->FindDouble("refresh-rate").value_or(refresh_rate);
       absl::optional<bool> is_interlaced_opt =
-          dict_value->FindBoolKey("interlaced");
+          dict_value->FindBool("interlaced");
       is_interlaced = is_interlaced_opt.value_or(false);
     }
 
@@ -268,7 +263,7 @@
     if (ValueToInsets(*dict_value, &insets))
       insets_to_set = &insets;
 
-    double display_zoom = dict_value->FindDoubleKey(kDisplayZoom).value_or(1.0);
+    double display_zoom = dict_value->FindDouble(kDisplayZoom).value_or(1.0);
 
     GetDisplayManager()->RegisterDisplayProperty(
         id, rotation, insets_to_set, resolution_in_pixels, device_scale_factor,
@@ -303,23 +298,22 @@
         identifier, display::TouchDeviceManager::AssociationInfoMap());
     if (!item.second.is_dict())
       continue;
-    for (const auto association_info_item : item.second.DictItems()) {
+    for (const auto association_info_item : item.second.GetDict()) {
       display::TouchDeviceManager::TouchAssociationInfo info;
       int64_t display_id;
       if (!base::StringToInt64(association_info_item.first, &display_id))
         continue;
-      auto* value =
-          association_info_item.second.FindKey(kTouchAssociationTimestamp);
-      if (!value->is_double())
+      absl::optional<double> value =
+          association_info_item.second.GetDict().FindDouble(
+              kTouchAssociationTimestamp);
+      if (!value)
         continue;
-      info.timestamp = base::Time().FromDoubleT(value->GetDouble());
+      info.timestamp = base::Time().FromDoubleT(*value);
 
-      value = association_info_item.second.FindKey(
-          kTouchAssociationCalibrationData);
-      if (!value->is_dict())
-        continue;
-      const base::DictionaryValue* calibration_data_dict = nullptr;
-      if (!value->GetAsDictionary(&calibration_data_dict))
+      const base::Value::Dict* calibration_data_dict =
+          association_info_item.second.GetDict().FindDict(
+              kTouchAssociationCalibrationData);
+      if (!calibration_data_dict)
         continue;
       ValueToTouchData(*calibration_data_dict, &info.calibration_data);
       touch_associations.at(identifier).emplace(display_id, info);
@@ -331,8 +325,8 @@
   const display::TouchDeviceIdentifier& fallback_identifier =
       display::TouchDeviceIdentifier::GetFallbackTouchDeviceIdentifier();
   for (const auto it : local_state->GetValueDict(prefs::kDisplayProperties)) {
-    const base::DictionaryValue* dict_value = nullptr;
-    if (!it.second.GetAsDictionary(&dict_value) || dict_value == nullptr)
+    const base::Value::Dict* dict_value = it.second.GetIfDict();
+    if (!dict_value)
       continue;
     int64_t id = display::kInvalidDisplayId;
     if (!base::StringToInt64(it.first, &id) ||
@@ -369,20 +363,21 @@
       continue;
 
     // Retrieve the touch device identifier that identifies the touch device.
-    auto* value = item.second.FindKey(kTouchDeviceIdentifier);
-    if (!value->is_string())
+    const std::string* value =
+        item.second.GetDict().FindString(kTouchDeviceIdentifier);
+    if (!value)
       continue;
     uint32_t identifier_raw;
-    if (!base::StringToUint(value->GetString(), &identifier_raw))
+    if (!base::StringToUint(*value, &identifier_raw))
       continue;
 
     // Retrieve the display that the touch device identified by |identifier_raw|
     // was associated with.
-    value = item.second.FindKey(kPortAssociationDisplayId);
-    if (!value->is_string())
+    value = item.second.GetDict().FindString(kPortAssociationDisplayId);
+    if (!value)
       continue;
     int64_t display_id;
-    if (!base::StringToInt64(value->GetString(), &display_id))
+    if (!base::StringToInt64(*value, &display_id))
       continue;
 
     port_associations.emplace(
@@ -464,13 +459,10 @@
   std::string name = display::DisplayIdListToString(list);
 
   DictionaryPrefUpdate update(pref_service, prefs::kSecondaryDisplays);
-  base::Value* pref_data = update.Get();
-  base::Value layout_value(base::Value::Type::DICTIONARY);
-  if (base::Value* value = pref_data->FindKey(name)) {
-    layout_value = value->Clone();
-  }
-  if (display::DisplayLayoutToJson(display_layout, &layout_value))
-    pref_data->SetPath(name, std::move(layout_value));
+  base::Value::Dict& pref_data = update->GetDict();
+  base::Value::Dict* layout_dict = pref_data.EnsureDict(name);
+  // This call modifies `layout_dict` in place.
+  display::DisplayLayoutToJson(display_layout, *layout_dict);
 }
 
 void StoreCurrentDisplayLayoutPrefs(PrefService* pref_service) {
@@ -499,7 +491,7 @@
   display::DisplayManager* display_manager = GetDisplayManager();
 
   DictionaryPrefUpdate update(pref_service, prefs::kDisplayProperties);
-  base::Value* pref_data = update.Get();
+  base::Value::Dict& pref_data = update->GetDict();
 
   // Pre-process data related to legacy touch calibration to opitmize lookup.
   const display::TouchDeviceIdentifier& fallback_identifier =
@@ -519,7 +511,7 @@
     int64_t id = display.id();
     display::ManagedDisplayInfo info = display_manager->GetDisplayInfo(id);
 
-    base::Value property_value(base::Value::Type::DICTIONARY);
+    base::Value::Dict property_value;
     // Don't save the display preference in unified mode because its
     // size and modes can change depending on the combination of displays.
     if (display_manager->IsInUnifiedMode())
@@ -530,34 +522,33 @@
     // But we should keep any original value so that it can be restored when
     // exiting tablet mode.
     if (Shell::Get()->tablet_mode_controller()->InTabletMode()) {
-      const base::Value* original_property =
-          pref_data->FindDictKey(base::NumberToString(id));
+      const base::Value::Dict* original_property =
+          pref_data.FindDict(base::NumberToString(id));
       if (original_property) {
         absl::optional<int> original_rotation =
-            original_property->FindIntKey("rotation");
+            original_property->FindInt("rotation");
         if (original_rotation) {
-          property_value.SetIntKey("rotation", *original_rotation);
+          property_value.Set("rotation", *original_rotation);
         }
       }
     } else {
-      property_value.SetIntKey("rotation",
-                               static_cast<int>(info.GetRotation(
-                                   display::Display::RotationSource::USER)));
+      property_value.Set("rotation",
+                         static_cast<int>(info.GetRotation(
+                             display::Display::RotationSource::USER)));
     }
 
     display::ManagedDisplayMode mode;
     if (!display.IsInternal() &&
         display_manager->GetSelectedModeForDisplayId(id, &mode) &&
         !mode.native()) {
-      property_value.SetIntKey("width", mode.size().width());
-      property_value.SetIntKey("height", mode.size().height());
-      property_value.SetIntKey(
-          "device-scale-factor",
-          static_cast<int>(mode.device_scale_factor() * 1000));
+      property_value.Set("width", mode.size().width());
+      property_value.Set("height", mode.size().height());
+      property_value.Set("device-scale-factor",
+                         static_cast<int>(mode.device_scale_factor() * 1000));
 
       if (display::features::IsListAllDisplayModesEnabled()) {
-        property_value.SetBoolKey("interlaced", mode.is_interlaced());
-        property_value.SetDoubleKey("refresh-rate", mode.refresh_rate());
+        property_value.Set("interlaced", mode.is_interlaced());
+        property_value.Set("refresh-rate", mode.refresh_rate());
       }
     }
     if (!info.overscan_insets_in_dip().IsEmpty())
@@ -569,9 +560,9 @@
       TouchDataToValue(legacy_data_map.at(id).calibration_data, property_value);
     }
 
-    property_value.SetDoubleKey(kDisplayZoom, info.zoom_factor());
+    property_value.Set(kDisplayZoom, info.zoom_factor());
 
-    pref_data->SetKey(base::NumberToString(id), std::move(property_value));
+    pref_data.Set(base::NumberToString(id), std::move(property_value));
   }
 }
 
@@ -621,9 +612,9 @@
                                display::Display::Rotation rotation,
                                bool rotation_lock) {
   DictionaryPrefUpdate update(pref_service, prefs::kDisplayRotationLock);
-  base::Value* pref_data = update.Get();
-  pref_data->SetBoolKey("lock", rotation_lock);
-  pref_data->SetIntKey("orientation", static_cast<int>(rotation));
+  base::Value::Dict& pref_data = update->GetDict();
+  pref_data.Set("lock", rotation_lock);
+  pref_data.Set("orientation", static_cast<int>(rotation));
 }
 
 void StoreCurrentDisplayRotationLockPrefs(PrefService* pref_service) {
@@ -644,58 +635,57 @@
       GetDisplayManager()->touch_device_manager();
 
   DictionaryPrefUpdate update(pref_service, prefs::kDisplayTouchAssociations);
-  base::Value* pref_data = update.Get();
-  pref_data->DictClear();
+  base::Value::Dict& pref_data = update->GetDict();
+  pref_data.clear();
 
   const display::TouchDeviceManager::TouchAssociationMap& touch_associations =
       touch_device_manager->touch_associations();
 
   for (const auto& association : touch_associations) {
-    base::Value association_info_map_value(base::Value::Type::DICTIONARY);
+    base::Value::Dict association_info_map_value;
     for (const auto& association_info : association.second) {
       // Iteration for each pair of <Display ID, TouchAssociationInfo>.
-      base::Value association_info_value(base::Value::Type::DICTIONARY);
+      base::Value::Dict association_info_value;
 
       // Parsing each member of TouchAssociationInfo and storing them in
       // |association_info_value|.
 
       // Serialize timestamp.
-      association_info_value.SetDoubleKey(
-          kTouchAssociationTimestamp,
-          association_info.second.timestamp.ToDoubleT());
+      association_info_value.Set(kTouchAssociationTimestamp,
+                                 association_info.second.timestamp.ToDoubleT());
 
       // Serialize TouchCalibrationData.
-      base::Value calibration_data_value(base::Value::Type::DICTIONARY);
+      base::Value::Dict calibration_data_value;
       TouchDataToValue(association_info.second.calibration_data,
                        calibration_data_value);
-      association_info_value.SetKey(kTouchAssociationCalibrationData,
-                                    std::move(calibration_data_value));
+      association_info_value.Set(kTouchAssociationCalibrationData,
+                                 std::move(calibration_data_value));
 
       // Move the searialzed TouchAssociationInfo stored in
       // |association_info_value| to |association_info_map_value| against the
       // display id as key. This is a 1 to 1 mapping of a single entry from
       // AssociationInfoMap to its serialized form.
-      association_info_map_value.SetKey(
+      association_info_map_value.Set(
           base::NumberToString(association_info.first),
           std::move(association_info_value));
     }
-    if (association_info_map_value.DictEmpty())
+    if (association_info_map_value.empty())
       continue;
 
     // Move the already serialized entry of AssociationInfoMap from
     // |association_info_map_value| to |pref_data| against the
     // TouchDeviceIdentifier as key. This is a 1 to 1 mapping of a single entry
     // from TouchAssociationMap to its serialized form.
-    pref_data->SetKey(association.first.ToString(),
-                      std::move(association_info_map_value));
+    pref_data.Set(association.first.ToString(),
+                  std::move(association_info_map_value));
   }
 
   // Store the port mappings. What display a touch device connected to a
   // particular port is associated with.
   DictionaryPrefUpdate update_port(pref_service,
                                    prefs::kDisplayTouchPortAssociations);
-  pref_data = update_port.Get();
-  update_port->DictClear();
+  base::Value::Dict& port_pref_data = update_port->GetDict();
+  port_pref_data.clear();
 
   const display::TouchDeviceManager::PortAssociationMap& port_associations =
       touch_device_manager->port_associations();
@@ -703,26 +693,26 @@
   // For each port identified by the secondary id of TouchDeviceIdentifier,
   // we store the touch device and the display associated with it.
   for (const auto& association : port_associations) {
-    base::Value association_info_value(base::Value::Type::DICTIONARY);
-    association_info_value.SetStringKey(kTouchDeviceIdentifier,
-                                        association.first.ToString());
-    association_info_value.SetStringKey(
-        kPortAssociationDisplayId, base::NumberToString(association.second));
+    base::Value::Dict association_info_value;
+    association_info_value.Set(kTouchDeviceIdentifier,
+                               association.first.ToString());
+    association_info_value.Set(kPortAssociationDisplayId,
+                               base::NumberToString(association.second));
 
-    pref_data->SetKey(association.first.SecondaryIdToString(),
-                      std::move(association_info_value));
+    port_pref_data.Set(association.first.SecondaryIdToString(),
+                       std::move(association_info_value));
   }
 }
 
 // Stores mirror info for each external display.
 void StoreExternalDisplayMirrorInfo(PrefService* pref_service) {
   ListPrefUpdate update(pref_service, prefs::kExternalDisplayMirrorInfo);
-  base::Value* pref_data = update.Get();
-  pref_data->ClearList();
+  base::Value::List& pref_data = update->GetList();
+  pref_data.clear();
   const std::set<int64_t>& external_display_mirror_info =
       GetDisplayManager()->external_display_mirror_info();
   for (const auto& id : external_display_mirror_info)
-    pref_data->Append(base::NumberToString(id));
+    pref_data.Append(base::NumberToString(id));
 }
 
 // Stores mixed mirror mode parameters. Clear the preferences if
@@ -732,21 +722,21 @@
     const absl::optional<display::MixedMirrorModeParams>& mixed_params) {
   DictionaryPrefUpdate update(pref_service,
                               prefs::kDisplayMixedMirrorModeParams);
-  base::Value* pref_data = update.Get();
-  pref_data->DictClear();
+  base::Value::Dict& pref_data = update->GetDict();
+  pref_data.clear();
 
   if (!mixed_params)
     return;
 
-  pref_data->SetStringKey(kMirroringSourceId,
-                          base::NumberToString(mixed_params->source_id));
+  pref_data.Set(kMirroringSourceId,
+                base::NumberToString(mixed_params->source_id));
 
-  base::Value mirroring_destination_ids_value(base::Value::Type::LIST);
+  base::Value::List mirroring_destination_ids_list;
   for (const auto& id : mixed_params->destination_ids) {
-    mirroring_destination_ids_value.Append(base::NumberToString(id));
+    mirroring_destination_ids_list.Append(base::NumberToString(id));
   }
-  pref_data->SetKey(kMirroringDestinationIds,
-                    std::move(mirroring_destination_ids_value));
+  pref_data.Set(kMirroringDestinationIds,
+                std::move(mirroring_destination_ids_list));
 }
 
 void StoreCurrentDisplayMixedMirrorModeParams(PrefService* pref_service) {
@@ -888,11 +878,10 @@
     int64_t display_id,
     const display::TouchCalibrationData& data) {
   DictionaryPrefUpdate update(local_state_, prefs::kDisplayProperties);
-  base::Value* pref_data = update.Get();
-  base::Value property_value(base::Value::Type::DICTIONARY);
+  base::Value::Dict& pref_data = update->GetDict();
+  base::Value::Dict property_value;
   TouchDataToValue(data, property_value);
-  pref_data->SetKey(base::NumberToString(display_id),
-                    std::move(property_value));
+  pref_data.Set(base::NumberToString(display_id), std::move(property_value));
 }
 
 bool DisplayPrefs::ParseTouchCalibrationStringForTest(
diff --git a/ash/display/display_prefs_unittest.cc b/ash/display/display_prefs_unittest.cc
index d829387..38503d1 100644
--- a/ash/display/display_prefs_unittest.cc
+++ b/ash/display/display_prefs_unittest.cc
@@ -162,12 +162,9 @@
 
     DCHECK(!name.empty());
 
-    base::Value* pref_data = update.Get();
-    base::Value layout_value(base::Value::Type::DICTIONARY);
-    if (const base::Value* value = pref_data->FindKey(name))
-      layout_value = value->Clone();
-    if (display::DisplayLayoutToJson(display_layout, &layout_value))
-      pref_data->SetPath(name, std::move(layout_value));
+    base::Value::Dict& pref_data = update->GetDict();
+    base::Value::Dict* layout_dict = pref_data.EnsureDict(name);
+    display::DisplayLayoutToJson(display_layout, *layout_dict);
   }
 
   void StoreDisplayPropertyForList(const display::DisplayIdList& list,
@@ -176,14 +173,13 @@
     std::string name = display::DisplayIdListToString(list);
 
     DictionaryPrefUpdate update(local_state(), prefs::kSecondaryDisplays);
-    base::Value* pref_data = update.Get();
-
-    if (base::Value* existing_layout_value = pref_data->FindKey(name)) {
-      existing_layout_value->SetKey(key, std::move(value));
+    base::Value::Dict& pref_data = update->GetDict();
+    if (base::Value::Dict* existing_layout_value = pref_data.FindDict(name)) {
+      existing_layout_value->Set(key, std::move(value));
     } else {
-      base::Value layout_value(base::Value::Type::DICTIONARY);
-      layout_value.SetBoolKey(key, true);
-      pref_data->SetPath(name, std::move(layout_value));
+      base::Value::Dict layout_dict;
+      layout_dict.Set(key, true);
+      pref_data.SetByDottedPath(name, std::move(layout_dict));
     }
   }
 
diff --git a/ash/public/cpp/external_arc/message_center/arc_notification_item_impl.cc b/ash/public/cpp/external_arc/message_center/arc_notification_item_impl.cc
index 06100db..f34f52e4 100644
--- a/ash/public/cpp/external_arc/message_center/arc_notification_item_impl.cc
+++ b/ash/public/cpp/external_arc/message_center/arc_notification_item_impl.cc
@@ -11,6 +11,7 @@
 #include "ash/public/cpp/external_arc/message_center/arc_notification_content_view.h"
 #include "ash/public/cpp/external_arc/message_center/arc_notification_delegate.h"
 #include "ash/public/cpp/external_arc/message_center/arc_notification_view.h"
+#include "ash/public/cpp/external_arc/message_center/metrics_utils.h"
 #include "ash/public/cpp/message_center/arc_notification_constants.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/string_util.h"
@@ -131,9 +132,13 @@
   if (expand_state_ != ArcNotificationExpandState::FIXED_SIZE &&
       data->expand_state != ArcNotificationExpandState::FIXED_SIZE &&
       expand_state_ != data->expand_state) {
-    // Assuming changing the expand status on Android-side is manually tiggered
+    // Assuming changing the expand status on Android-side is manually triggered
     // by user.
     manually_expanded_or_collapsed_ = true;
+    metrics_utils::LogArcNotificationExpandState(
+        data->expand_state == ArcNotificationExpandState::EXPANDED
+            ? metrics_utils::ArcNotificationExpandState::kExpanded
+            : metrics_utils::ArcNotificationExpandState::kCollapsed);
   }
 
   type_ = data->type;
diff --git a/ash/public/cpp/external_arc/message_center/arc_notification_manager.cc b/ash/public/cpp/external_arc/message_center/arc_notification_manager.cc
index 5445a64d..6c1dd50 100644
--- a/ash/public/cpp/external_arc/message_center/arc_notification_manager.cc
+++ b/ash/public/cpp/external_arc/message_center/arc_notification_manager.cc
@@ -33,6 +33,7 @@
 using arc::mojom::ArcNotificationData;
 using arc::mojom::ArcNotificationDataPtr;
 using arc::mojom::ArcNotificationEvent;
+using arc::mojom::ArcNotificationExpandState;
 using arc::mojom::ArcNotificationPriority;
 using arc::mojom::MessageCenterVisibility;
 using arc::mojom::NotificationConfiguration;
@@ -227,6 +228,10 @@
     metrics_utils::LogArcNotificationActionEnabled(data->is_action_enabled);
     metrics_utils::LogArcNotificationInlineReplyEnabled(
         data->is_inline_reply_enabled);
+    metrics_utils::LogArcNotificationExpandState(
+        data->expand_state == ArcNotificationExpandState::FIXED_SIZE
+            ? metrics_utils::ArcNotificationExpandState::kFixedSize
+            : metrics_utils::ArcNotificationExpandState::kExpandable);
   }
 
   std::string app_id =
diff --git a/ash/public/cpp/external_arc/message_center/arc_notification_manager_unittest.cc b/ash/public/cpp/external_arc/message_center/arc_notification_manager_unittest.cc
index 75b9a70..b0938b3 100644
--- a/ash/public/cpp/external_arc/message_center/arc_notification_manager_unittest.cc
+++ b/ash/public/cpp/external_arc/message_center/arc_notification_manager_unittest.cc
@@ -30,6 +30,7 @@
 constexpr char kDummyNotificationKey[] = "DUMMY_NOTIFICATION_KEY";
 constexpr char kHistogramNameActionEnabled[] =
     "Arc.Notifications.ActionEnabled";
+constexpr char kHistogramNameExpandState[] = "Arc.Notifications.ExpandState";
 constexpr char kHistogramNameStyle[] = "Arc.Notifications.Style";
 constexpr char kHistogramNameInlineReplyEnabled[] =
     "Arc.Notifications.InlineReplyEnabled";
@@ -334,18 +335,21 @@
        UmaMeticsPublishedOnlyWhenNotificationCreated) {
   base::HistogramTester histogram_tester;
   histogram_tester.ExpectTotalCount(kHistogramNameActionEnabled, 0);
+  histogram_tester.ExpectTotalCount(kHistogramNameExpandState, 0);
   histogram_tester.ExpectTotalCount(kHistogramNameStyle, 0);
   histogram_tester.ExpectTotalCount(kHistogramNameInlineReplyEnabled, 0);
 
   // Create notification
   std::string key = CreateNotification();
   histogram_tester.ExpectTotalCount(kHistogramNameActionEnabled, 1);
+  histogram_tester.ExpectTotalCount(kHistogramNameExpandState, 1);
   histogram_tester.ExpectTotalCount(kHistogramNameStyle, 1);
   histogram_tester.ExpectTotalCount(kHistogramNameInlineReplyEnabled, 1);
 
   // Update notification
   CreateNotificationWithKey(key);
   histogram_tester.ExpectTotalCount(kHistogramNameActionEnabled, 1);
+  histogram_tester.ExpectTotalCount(kHistogramNameExpandState, 1);
   histogram_tester.ExpectTotalCount(kHistogramNameStyle, 1);
   histogram_tester.ExpectTotalCount(kHistogramNameInlineReplyEnabled, 1);
 }
diff --git a/ash/public/cpp/external_arc/message_center/metrics_utils.cc b/ash/public/cpp/external_arc/message_center/metrics_utils.cc
index b8584b3..dfb0d14c 100644
--- a/ash/public/cpp/external_arc/message_center/metrics_utils.cc
+++ b/ash/public/cpp/external_arc/message_center/metrics_utils.cc
@@ -12,6 +12,10 @@
   base::UmaHistogramBoolean("Arc.Notifications.ActionEnabled", action_enabled);
 }
 
+void LogArcNotificationExpandState(ArcNotificationExpandState state) {
+  base::UmaHistogramEnumeration("Arc.Notifications.ExpandState", state);
+}
+
 void LogArcNotificationInlineReplyEnabled(bool inline_reply_enabled) {
   base::UmaHistogramBoolean("Arc.Notifications.InlineReplyEnabled",
                             inline_reply_enabled);
diff --git a/ash/public/cpp/external_arc/message_center/metrics_utils.h b/ash/public/cpp/external_arc/message_center/metrics_utils.h
index e6aa819..f90d2a3f 100644
--- a/ash/public/cpp/external_arc/message_center/metrics_utils.h
+++ b/ash/public/cpp/external_arc/message_center/metrics_utils.h
@@ -9,9 +9,25 @@
 
 namespace ash::metrics_utils {
 
+// Note to keep in sync with enum in tools/metrics/histograms/enums.xml.
+enum class ArcNotificationExpandState {
+  // No expand button is available, the size of the notification is fixed
+  kFixedSize = 0,
+  // Expand button is available to expand the notification
+  kExpandable = 1,
+  // The state after the user expands the notification
+  kExpanded = 2,
+  // The state after the user collapses the notification
+  kCollapsed = 3,
+  kMaxValue = kCollapsed,
+};
+
 // Logs if action button is enabled for Arc notification.
 void LogArcNotificationActionEnabled(bool action_enabled);
 
+// Logs the expand state for Arc notification.
+void LogArcNotificationExpandState(ArcNotificationExpandState state);
+
 // Logs if inline reply is enabled for Arc notification.
 void LogArcNotificationInlineReplyEnabled(bool inline_reply_enabled);
 
diff --git a/ash/session/fullscreen_controller.cc b/ash/session/fullscreen_controller.cc
index 1640d45..d394eff 100644
--- a/ash/session/fullscreen_controller.cc
+++ b/ash/session/fullscreen_controller.cc
@@ -6,6 +6,7 @@
 
 #include <limits>
 
+#include "ash/constants/app_types.h"
 #include "ash/constants/ash_features.h"
 #include "ash/constants/ash_pref_names.h"
 #include "ash/session/fullscreen_notification_bubble.h"
@@ -18,13 +19,48 @@
 #include "base/check.h"
 #include "chromeos/dbus/power_manager/backlight.pb.h"
 #include "chromeos/dbus/power_manager/idle.pb.h"
+#include "chromeos/ui/wm/fullscreen/keep_fullscreen_for_url_checker.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
-#include "components/url_matcher/url_matcher.h"
-#include "components/url_matcher/url_util.h"
+#include "ui/aura/client/aura_constants.h"
+#include "url/gurl.h"
 
 namespace ash {
 
+namespace {
+
+// Exits full screen to avoid the web page or app mimicking the lock screen if
+// the active window is in full screen mode and the shelf is not visible. Do not
+// exit fullscreen if the shelf is visible while in fullscreen because the shelf
+// makes it harder for a web page or app to mimic the lock screen.
+void ExitFullscreenIfActive() {
+  WindowState* active_window_state = WindowState::ForActiveWindow();
+  if (!active_window_state || !active_window_state->IsFullscreen())
+    return;
+
+  Shelf* shelf = Shelf::ForWindow(active_window_state->window());
+  const bool shelf_visible =
+      shelf->GetVisibilityState() == ShelfVisibilityState::SHELF_VISIBLE;
+
+  if (shelf_visible && !active_window_state->GetHideShelfWhenFullscreen())
+    return;
+
+  const WMEvent event(WM_EVENT_TOGGLE_FULLSCREEN);
+  active_window_state->OnWMEvent(&event);
+}
+
+// Receives the result from the request to Lacros and exits full screen, if
+// required. |callback| will be invoked to signal readiness for session lock.
+void OnShouldExitFullscreenResult(base::OnceClosure callback,
+                                  bool should_exit_fullscreen) {
+  if (should_exit_fullscreen)
+    ExitFullscreenIfActive();
+
+  std::move(callback).Run();
+}
+
+}  // namespace
+
 FullscreenController::FullscreenController(
     SessionControllerImpl* session_controller)
     : session_controller_(session_controller) {
@@ -43,24 +79,63 @@
 }
 
 // static
-void FullscreenController::MaybeExitFullscreen() {
-  // If the active window is fullscreen, exit fullscreen to avoid the web page
-  // or app mimicking the lock screen. Do not exit fullscreen if the shelf is
-  // visible while in fullscreen because the shelf makes it harder for a web
-  // page or app to mimic the lock screen.
+void FullscreenController::RegisterProfilePrefs(PrefRegistrySimple* registry) {
+  registry->RegisterBooleanPref(prefs::kFullscreenAlertEnabled, true,
+                                PrefRegistry::PUBLIC);
+}
+
+void FullscreenController::MaybeExitFullscreenBeforeLock(
+    base::OnceClosure callback) {
+  // Check whether it is allowed to keep full screen on unlock.
+  if (!features::IsFullscreenAfterUnlockAllowed()) {
+    ExitFullscreenIfActive();
+    std::move(callback).Run();
+    return;
+  }
+
+  // Nothing to do if the active window is not in full screen mode.
   WindowState* active_window_state = WindowState::ForActiveWindow();
-  if (!active_window_state || !active_window_state->IsFullscreen())
+  if (!active_window_state || !active_window_state->IsFullscreen()) {
+    std::move(callback).Run();
     return;
+  }
 
-  Shelf* shelf = Shelf::ForWindow(active_window_state->window());
-  const bool shelf_visible =
-      shelf->GetVisibilityState() == ShelfVisibilityState::SHELF_VISIBLE;
+  if (!keep_fullscreen_checker_) {
+    keep_fullscreen_checker_ =
+        std::make_unique<chromeos::KeepFullscreenForUrlChecker>(
+            Shell::Get()->session_controller()->GetPrimaryUserPrefService());
+  }
 
-  if (shelf_visible && !active_window_state->GetHideShelfWhenFullscreen())
+  // Always exit full screen if the allowlist policy is unset.
+  if (!keep_fullscreen_checker_
+           ->IsKeepFullscreenWithoutNotificationPolicySet()) {
+    ExitFullscreenIfActive();
+    std::move(callback).Run();
     return;
+  }
 
-  const WMEvent event(WM_EVENT_TOGGLE_FULLSCREEN);
-  active_window_state->OnWMEvent(&event);
+  // Try to get the URL of the active window from the shell delegate.
+  const GURL& url =
+      Shell::Get()->shell_delegate()->GetLastCommittedURLForWindowIfAny(
+          active_window_state->window());
+
+  // If the chrome shell delegate did not return a URL for the active window, it
+  // could be a Lacros window and it should check with Lacros whether the
+  // FullscreenController should exit full screen mode.
+  if (url.is_empty() &&
+      active_window_state->window()->GetProperty(aura::client::kAppType) ==
+          static_cast<int>(AppType::LACROS)) {
+    auto should_exit_fullscreen_callback =
+        base::BindOnce(&OnShouldExitFullscreenResult, std::move(callback));
+    Shell::Get()->shell_delegate()->ShouldExitFullscreenBeforeLock(
+        std::move(should_exit_fullscreen_callback));
+    return;
+  }
+
+  // Check if it is allowed by user pref to keep full screen for the window URL.
+  if (keep_fullscreen_checker_->ShouldExitFullscreenForUrl(url))
+    ExitFullscreenIfActive();
+  std::move(callback).Run();
 }
 
 void FullscreenController::MaybeShowNotification() {
@@ -99,21 +174,12 @@
   bubble_->ShowForWindowState(active_window_state);
 }
 
-// static
-void FullscreenController::RegisterProfilePrefs(PrefRegistrySimple* registry) {
-  registry->RegisterBooleanPref(prefs::kFullscreenAlertEnabled, true,
-                                PrefRegistry::PUBLIC);
-  registry->RegisterListPref(
-      prefs::kKeepFullscreenWithoutNotificationUrlAllowList,
-      PrefRegistry::PUBLIC);
-}
-
 void FullscreenController::SuspendImminent(
     power_manager::SuspendImminent::Reason reason) {
   if (session_controller_->login_status() != LoginStatus::GUEST)
     return;
 
-  MaybeExitFullscreen();
+  ExitFullscreenIfActive();
 }
 
 void FullscreenController::ScreenIdleStateChanged(
@@ -122,7 +188,7 @@
     return;
 
   if (proto.off() || proto.dimmed())
-    MaybeExitFullscreen();
+    ExitFullscreenIfActive();
 }
 
 void FullscreenController::ScreenBrightnessChanged(
@@ -151,33 +217,4 @@
     MaybeShowNotification();
 }
 
-// static
-bool FullscreenController::ShouldExitFullscreenBeforeLock() {
-  // Check whether it is allowed to keep full screen on unlock.
-  if (!features::IsFullscreenAfterUnlockAllowed())
-    return true;
-
-  // Nothing to do if the active window is not in full screen mode.
-  WindowState* active_window_state = WindowState::ForActiveWindow();
-  if (!active_window_state || !active_window_state->IsFullscreen())
-    return false;
-
-  // Always exit full screen if the allowlist policy is unset.
-  auto* prefs = Shell::Get()->session_controller()->GetPrimaryUserPrefService();
-  const auto& url_allow_list = prefs->GetValueList(
-      prefs::kKeepFullscreenWithoutNotificationUrlAllowList);
-  if (url_allow_list.size() == 0)
-    return true;
-
-  // Get the URL of the active window from the shell delegate.
-  const GURL& url =
-      Shell::Get()->shell_delegate()->GetLastCommittedURLForWindowIfAny(
-          active_window_state->window());
-
-  // Check if it is allowed by user pref to keep full screen for the active URL.
-  url_matcher::URLMatcher url_matcher;
-  url_matcher::util::AddAllowFilters(&url_matcher, url_allow_list);
-  return url_matcher.MatchURL(url).empty();
-}
-
 }  // namespace ash
diff --git a/ash/session/fullscreen_controller.h b/ash/session/fullscreen_controller.h
index 6c63761..d918194 100644
--- a/ash/session/fullscreen_controller.h
+++ b/ash/session/fullscreen_controller.h
@@ -12,6 +12,10 @@
 
 class PrefRegistrySimple;
 
+namespace chromeos {
+class KeepFullscreenForUrlChecker;
+}  // namespace chromeos
+
 namespace ash {
 
 class SessionControllerImpl;
@@ -25,13 +29,17 @@
 
   ~FullscreenController() override;
 
-  static void MaybeExitFullscreen();
-
   static void RegisterProfilePrefs(PrefRegistrySimple* registry);
 
-  static bool ShouldExitFullscreenBeforeLock();
+  // Checks the window state, user pref and, if required, sends a request to
+  // Lacros to determine whether it should exit full screen mode before the
+  // session is locked. |callback| will be invoked to signal readiness for
+  // session lock.
+  void MaybeExitFullscreenBeforeLock(base::OnceClosure callback);
 
  private:
+  void MaybeShowNotification();
+
   // chromeos::PowerManagerClient::Observer:
   void SuspendImminent(power_manager::SuspendImminent::Reason reason) override;
   void ScreenIdleStateChanged(
@@ -41,12 +49,13 @@
   void LidEventReceived(chromeos::PowerManagerClient::LidState state,
                         base::TimeTicks timestamp) override;
 
-  void MaybeShowNotification();
-
   const SessionControllerImpl* const session_controller_;
 
   std::unique_ptr<FullscreenNotificationBubble> bubble_;
 
+  std::unique_ptr<chromeos::KeepFullscreenForUrlChecker>
+      keep_fullscreen_checker_;
+
   // Whether the screen brightness is low enough to make display dark.
   bool device_in_dark_ = false;
 };
diff --git a/ash/session/fullscreen_controller_unittest.cc b/ash/session/fullscreen_controller_unittest.cc
index 4294924..4d9cf02261 100644
--- a/ash/session/fullscreen_controller_unittest.cc
+++ b/ash/session/fullscreen_controller_unittest.cc
@@ -6,13 +6,14 @@
 
 #include <memory>
 
-#include "ash/constants/ash_pref_names.h"
+#include "ash/constants/app_types.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
 #include "ash/test_shell_delegate.h"
 #include "ash/wm/window_state.h"
 #include "base/run_loop.h"
+#include "chromeos/ui/wm/fullscreen/pref_names.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/window.h"
@@ -27,7 +28,8 @@
 constexpr char kMatchingPattern[] = "test.com";
 constexpr char kWildcardPattern[] = "*";
 
-class FullscreenControllerTest : public AshTestBase {
+class FullscreenControllerTest : public AshTestBase,
+                                 public testing::WithParamInterface<bool> {
  public:
   FullscreenControllerTest() {}
 
@@ -38,10 +40,9 @@
 
   // AshTestBase:
   void SetUp() override {
-    // Create a shell delegate and set the active URL.
+    // Create a test shell delegate which can return fake responses.
     auto test_shell_delegate = std::make_unique<TestShellDelegate>();
     test_shell_delegate_ = test_shell_delegate.get();
-    test_shell_delegate->SetLastCommittedURLForWindow(kActiveUrl);
 
     AshTestBase::SetUp(std::move(test_shell_delegate));
 
@@ -57,6 +58,10 @@
     window_ = CreateTestWindow();
     window_->SetProperty(aura::client::kShowStateKey,
                          ui::SHOW_STATE_FULLSCREEN);
+    if (is_lacros_window_) {
+      window_->SetProperty(aura::client::kAppType,
+                           static_cast<int>(AppType::LACROS));
+    }
     window_state_ = WindowState::Get(window_.get());
   }
 
@@ -65,10 +70,24 @@
     base::Value list(base::Value::Type::LIST);
     list.Append(base::Value(pattern));
     Shell::Get()->session_controller()->GetPrimaryUserPrefService()->Set(
-        prefs::kKeepFullscreenWithoutNotificationUrlAllowList, list);
+        chromeos::prefs::kKeepFullscreenWithoutNotificationUrlAllowList, list);
+  }
+
+  void SetUpShellDelegate(bool should_exit_fullscreen, GURL url = kActiveUrl) {
+    // The shell delegate will only retrieve the active URL for ash-chrome
+    // windows and return the empty URL for lacros-chrome windows.
+    if (is_lacros_window_) {
+      test_shell_delegate_->SetLastCommittedURLForWindow(kEmptyUrl);
+      test_shell_delegate_->SetShouldExitFullscreenBeforeLock(
+          should_exit_fullscreen);
+    } else {
+      test_shell_delegate_->SetLastCommittedURLForWindow(url);
+    }
   }
 
  protected:
+  bool is_lacros_window_ = GetParam();
+
   std::unique_ptr<aura::Window> window_;
 
   WindowState* window_state_ = nullptr;
@@ -78,7 +97,7 @@
 
 // Test that full screen is exited after session unlock if the allow list pref
 // is unset.
-TEST_F(FullscreenControllerTest, ExitFullscreenIfUnsetPref) {
+TEST_P(FullscreenControllerTest, ExitFullscreenIfUnsetPref) {
   EXPECT_TRUE(window_state_->IsFullscreen());
 
   base::RunLoop run_loop;
@@ -92,7 +111,10 @@
 
 // Test that full screen is exited after session unlock if the URL of the active
 // window does not match any patterns from the allow list.
-TEST_F(FullscreenControllerTest, ExitFullscreenIfNonMatchingPref) {
+TEST_P(FullscreenControllerTest, ExitFullscreenIfNonMatchingPref) {
+  bool should_exit_fullscreen = true;
+  SetUpShellDelegate(should_exit_fullscreen);
+
   SetKeepFullscreenWithoutNotificationAllowList(kNonMatchingPattern);
 
   EXPECT_TRUE(window_state_->IsFullscreen());
@@ -103,18 +125,21 @@
   GetSessionControllerClient()->UnlockScreen();
   run_loop.Run();
 
-  EXPECT_FALSE(window_state_->IsFullscreen());
+  EXPECT_EQ(window_state_->IsFullscreen(), !should_exit_fullscreen);
 }
 
 // Test that full screen is not exited after session unlock if the URL of the
 // active window matches a pattern from the allow list.
-TEST_F(FullscreenControllerTest, KeepFullscreenIfMatchingPref) {
+TEST_P(FullscreenControllerTest, KeepFullscreenIfMatchingPref) {
+  bool should_exit_fullscreen = false;
+  SetUpShellDelegate(should_exit_fullscreen);
+
   // Set up the URL exempt list with one matching and one non-matching pattern.
   base::Value list(base::Value::Type::LIST);
   list.Append(base::Value(kNonMatchingPattern));
   list.Append(base::Value(kMatchingPattern));
   Shell::Get()->session_controller()->GetPrimaryUserPrefService()->Set(
-      prefs::kKeepFullscreenWithoutNotificationUrlAllowList, list);
+      chromeos::prefs::kKeepFullscreenWithoutNotificationUrlAllowList, list);
 
   EXPECT_TRUE(window_state_->IsFullscreen());
 
@@ -124,12 +149,15 @@
   GetSessionControllerClient()->UnlockScreen();
   run_loop.Run();
 
-  EXPECT_TRUE(window_state_->IsFullscreen());
+  EXPECT_EQ(window_state_->IsFullscreen(), !should_exit_fullscreen);
 }
 
 // Test that full screen is not exited after session unlock if the allow list
 // includes the wildcard character.
-TEST_F(FullscreenControllerTest, KeepFullscreenIfWildcardPref) {
+TEST_P(FullscreenControllerTest, KeepFullscreenIfWildcardPref) {
+  bool should_exit_fullscreen = false;
+  SetUpShellDelegate(should_exit_fullscreen);
+
   SetKeepFullscreenWithoutNotificationAllowList(kWildcardPattern);
 
   EXPECT_TRUE(window_state_->IsFullscreen());
@@ -140,13 +168,14 @@
   GetSessionControllerClient()->UnlockScreen();
   run_loop.Run();
 
-  EXPECT_TRUE(window_state_->IsFullscreen());
+  EXPECT_EQ(window_state_->IsFullscreen(), !should_exit_fullscreen);
 }
 
 // Test that full screen is exited after session unlock if the URL is not
 // available.
-TEST_F(FullscreenControllerTest, ExitFullscreenIfUnsetUrlUnsetPref) {
-  test_shell_delegate_->SetLastCommittedURLForWindow(kEmptyUrl);
+TEST_P(FullscreenControllerTest, ExitFullscreenIfUnsetUrlUnsetPref) {
+  bool should_exit_fullscreen = true;
+  SetUpShellDelegate(should_exit_fullscreen, kEmptyUrl);
 
   EXPECT_TRUE(window_state_->IsFullscreen());
 
@@ -156,13 +185,14 @@
   GetSessionControllerClient()->UnlockScreen();
   run_loop.Run();
 
-  EXPECT_FALSE(window_state_->IsFullscreen());
+  EXPECT_EQ(window_state_->IsFullscreen(), !should_exit_fullscreen);
 }
 
 // Test that full screen is not exited after session unlock if the allow list
 // includes the wildcard character and the URL is not available.
-TEST_F(FullscreenControllerTest, KeepFullscreenIfUnsetUrlWildcardPref) {
-  test_shell_delegate_->SetLastCommittedURLForWindow(kEmptyUrl);
+TEST_P(FullscreenControllerTest, KeepFullscreenIfUnsetUrlWildcardPref) {
+  bool should_exit_fullscreen = false;
+  SetUpShellDelegate(should_exit_fullscreen, kEmptyUrl);
 
   SetKeepFullscreenWithoutNotificationAllowList(kWildcardPattern);
 
@@ -174,8 +204,10 @@
   GetSessionControllerClient()->UnlockScreen();
   run_loop.Run();
 
-  EXPECT_TRUE(window_state_->IsFullscreen());
+  EXPECT_EQ(window_state_->IsFullscreen(), !should_exit_fullscreen);
 }
 
+INSTANTIATE_TEST_SUITE_P(All, FullscreenControllerTest, testing::Bool());
+
 }  // namespace
 }  // namespace ash
diff --git a/ash/session/session_controller_impl.cc b/ash/session/session_controller_impl.cc
index f173000ca..be9fb4c 100644
--- a/ash/session/session_controller_impl.cc
+++ b/ash/session/session_controller_impl.cc
@@ -369,10 +369,7 @@
 }
 
 void SessionControllerImpl::PrepareForLock(PrepareForLockCallback callback) {
-  if (FullscreenController::ShouldExitFullscreenBeforeLock())
-    FullscreenController::MaybeExitFullscreen();
-
-  std::move(callback).Run();
+  fullscreen_controller_->MaybeExitFullscreenBeforeLock(std::move(callback));
 }
 
 void SessionControllerImpl::StartLock(StartLockCallback callback) {
diff --git a/ash/shell_delegate.cc b/ash/shell_delegate.cc
index 48cd1a31..193f496 100644
--- a/ash/shell_delegate.cc
+++ b/ash/shell_delegate.cc
@@ -35,4 +35,9 @@
   return GURL::EmptyGURL();
 }
 
+void ShellDelegate::ShouldExitFullscreenBeforeLock(
+    ShellDelegate::ShouldExitFullscreenCallback callback) {
+  std::move(callback).Run(false);
+}
+
 }  // namespace ash
diff --git a/ash/shell_delegate.h b/ash/shell_delegate.h
index f383d2d70..bd2024d7 100644
--- a/ash/shell_delegate.h
+++ b/ash/shell_delegate.h
@@ -152,6 +152,12 @@
 
   // Retrieves the official Chrome version string e.g. 105.0.5178.0.
   virtual std::string GetVersionString() = 0;
+
+  // Forwards the ShouldExitFullscreenBeforeLock() call to the crosapi browser
+  // manager.
+  using ShouldExitFullscreenCallback = base::OnceCallback<void(bool)>;
+  virtual void ShouldExitFullscreenBeforeLock(
+      ShouldExitFullscreenCallback callback);
 };
 
 }  // namespace ash
diff --git a/ash/test_shell_delegate.cc b/ash/test_shell_delegate.cc
index 0cd0493..0f834ba 100644
--- a/ash/test_shell_delegate.cc
+++ b/ash/test_shell_delegate.cc
@@ -67,6 +67,11 @@
   tab_scrubber_enabled_ = enabled;
 }
 
+void TestShellDelegate::ShouldExitFullscreenBeforeLock(
+    ShouldExitFullscreenCallback callback) {
+  std::move(callback).Run(should_exit_fullscreen_before_lock_);
+}
+
 bool TestShellDelegate::ShouldWaitForTouchPressAck(gfx::NativeWindow window) {
   return should_wait_for_touch_ack_;
 }
@@ -86,6 +91,11 @@
   can_go_back_ = can_go_back;
 }
 
+void TestShellDelegate::SetShouldExitFullscreenBeforeLock(
+    bool should_exit_fullscreen_before_lock) {
+  should_exit_fullscreen_before_lock_ = should_exit_fullscreen_before_lock;
+}
+
 void TestShellDelegate::SetShouldWaitForTouchAck(
     bool should_wait_for_touch_ack) {
   should_wait_for_touch_ack_ = should_wait_for_touch_ack;
diff --git a/ash/test_shell_delegate.h b/ash/test_shell_delegate.h
index db9404ea..d3af456 100644
--- a/ash/test_shell_delegate.h
+++ b/ash/test_shell_delegate.h
@@ -49,6 +49,8 @@
   GetGeolocationUrlLoaderFactory() const override;
   bool CanGoBack(gfx::NativeWindow window) const override;
   void SetTabScrubberChromeOSEnabled(bool enabled) override;
+  void ShouldExitFullscreenBeforeLock(
+      ShouldExitFullscreenCallback callback) override;
   bool ShouldWaitForTouchPressAck(gfx::NativeWindow window) override;
   int GetBrowserWebUITabStripHeight() override;
   void BindMultiDeviceSetup(
@@ -61,6 +63,8 @@
       const std::vector<aura::Window*>& windows) override {}
 
   void SetCanGoBack(bool can_go_back);
+  void SetShouldExitFullscreenBeforeLock(
+      bool should_exit_fullscreen_before_lock);
   void SetShouldWaitForTouchAck(bool should_wait_for_touch_ack);
   void SetSessionRestoreInProgress(bool in_progress);
   bool IsLoggingRedirectDisabled() const override;
@@ -83,6 +87,9 @@
   // True if the tab scrubber is enabled.
   bool tab_scrubber_enabled_ = true;
 
+  // False if it is allowed by policy to keep fullscreen after unlock.
+  bool should_exit_fullscreen_before_lock_ = true;
+
   // True if when performing back gesture on the top window, we should handle
   // the event after the touch ack is received. Please refer to
   // |BackGestureEventHandler::should_wait_for_touch_ack_| for detailed
diff --git a/ash/webui/media_app_ui/media_app_page_handler.cc b/ash/webui/media_app_ui/media_app_page_handler.cc
index 4814f96e..34ab1684 100644
--- a/ash/webui/media_app_ui/media_app_page_handler.cc
+++ b/ash/webui/media_app_ui/media_app_page_handler.cc
@@ -53,6 +53,12 @@
   std::move(callback).Run();
 }
 
+void MediaAppPageHandler::MaybeTriggerPdfHats(
+    MaybeTriggerPdfHatsCallback callback) {
+  media_app_ui_->delegate()->MaybeTriggerPdfHats();
+  std::move(callback).Run();
+}
+
 void MediaAppPageHandler::IsFileArcWritable(
     mojo::PendingRemote<blink::mojom::FileSystemAccessTransferToken> token,
     IsFileArcWritableCallback callback) {
diff --git a/ash/webui/media_app_ui/media_app_page_handler.h b/ash/webui/media_app_ui/media_app_page_handler.h
index 512a1b2..16bf7d2 100644
--- a/ash/webui/media_app_ui/media_app_page_handler.h
+++ b/ash/webui/media_app_ui/media_app_page_handler.h
@@ -31,6 +31,7 @@
   void OpenFeedbackDialog(OpenFeedbackDialogCallback callback) override;
   void ToggleBrowserFullscreenMode(
       ToggleBrowserFullscreenModeCallback callback) override;
+  void MaybeTriggerPdfHats(MaybeTriggerPdfHatsCallback callback) override;
   void IsFileArcWritable(
       mojo::PendingRemote<blink::mojom::FileSystemAccessTransferToken> token,
       IsFileArcWritableCallback callback) override;
diff --git a/ash/webui/media_app_ui/media_app_ui.mojom b/ash/webui/media_app_ui/media_app_ui.mojom
index 9ddcffa..21e2ef5 100644
--- a/ash/webui/media_app_ui/media_app_ui.mojom
+++ b/ash/webui/media_app_ui/media_app_ui.mojom
@@ -20,6 +20,8 @@
   OpenFeedbackDialog() => (string? error_message);
   // Toggles "browser" fullscreen mode for the window.
   ToggleBrowserFullscreenMode() => ();
+  // Indicate that a trigger for displaying the PDF HaTS survey has occurred.
+  MaybeTriggerPdfHats() => ();
   // Checks if the file path for the file represented by the provided transfer
   // token is in a filesystem that ARC is able to write to. Returns false if
   // |token| can't be resolved to a path.
diff --git a/ash/webui/media_app_ui/media_app_ui_delegate.h b/ash/webui/media_app_ui/media_app_ui_delegate.h
index cdb40b0..b8e1d1b 100644
--- a/ash/webui/media_app_ui/media_app_ui_delegate.h
+++ b/ash/webui/media_app_ui/media_app_ui_delegate.h
@@ -28,6 +28,9 @@
   // Toggles fullscreen mode on the Browser* hosting this MediaApp instance.
   virtual void ToggleBrowserFullscreenMode() = 0;
 
+  // Indicate that a trigger for displaying the PDF HaTS survey has occurred.
+  virtual void MaybeTriggerPdfHats() = 0;
+
   // Checks whether file represented by the provided transfer token is within a
   // filesystem that ARC is able to write to.
   virtual void IsFileArcWritable(
diff --git a/ash/webui/media_app_ui/resources/js/launch.js b/ash/webui/media_app_ui/resources/js/launch.js
index 76c4929..19ba4510 100644
--- a/ash/webui/media_app_ui/resources/js/launch.js
+++ b/ash/webui/media_app_ui/resources/js/launch.js
@@ -243,6 +243,10 @@
   window.location.reload();
 });
 
+guestMessagePipe.registerHandler(Message.MAYBE_TRIGGER_PDF_HATS, () => {
+  mediaAppPageHandler.maybeTriggerPdfHats();
+});
+
 guestMessagePipe.registerHandler(Message.EDIT_IN_PHOTOS, message => {
   const editInPhotosMsg = /** @type {!EditInPhotosMessage} */ (message);
   const fileHandle = fileHandleForToken(editInPhotosMsg.token);
diff --git a/ash/webui/media_app_ui/resources/js/media_app.externs.js b/ash/webui/media_app_ui/resources/js/media_app.externs.js
index 0794dd0..21c885d 100644
--- a/ash/webui/media_app_ui/resources/js/media_app.externs.js
+++ b/ash/webui/media_app_ui/resources/js/media_app.externs.js
@@ -255,6 +255,12 @@
  * @type {function()|undefined}
  */
 mediaApp.ClientApiDelegate.prototype.reloadMainFrame = function() {};
+/**
+ * Indicates to the WebUI Controller that a trigger for displaying the PDF HaTS
+ * survey has occurred.
+ * @type {function()|undefined}
+ */
+mediaApp.ClientApiDelegate.prototype.maybeTriggerPdfHats = function() {};
 
 /**
  * The client Api for interacting with the media app instance.
diff --git a/ash/webui/media_app_ui/resources/js/message_types.js b/ash/webui/media_app_ui/resources/js/message_types.js
index 13b7c66..a2c673b 100644
--- a/ash/webui/media_app_ui/resources/js/message_types.js
+++ b/ash/webui/media_app_ui/resources/js/message_types.js
@@ -19,6 +19,7 @@
   IS_FILE_BROWSER_WRITABLE: 'is-file-browser-writable',
   LOAD_EXTRA_FILES: 'load-extra-files',
   LOAD_FILES: 'load-files',
+  MAYBE_TRIGGER_PDF_HATS: 'maybe-trigger-pdf-hats',
   NAVIGATE: 'navigate',
   NOTIFY_CURRENT_FILE: 'notify-current-file',
   OPEN_ALLOWED_FILE: 'open-allowed-file',
diff --git a/ash/webui/media_app_ui/resources/js/receiver.js b/ash/webui/media_app_ui/resources/js/receiver.js
index 785b454..3da033d 100644
--- a/ash/webui/media_app_ui/resources/js/receiver.js
+++ b/ash/webui/media_app_ui/resources/js/receiver.js
@@ -381,6 +381,9 @@
   reloadMainFrame() {
     parentMessagePipe.sendMessage(Message.RELOAD_MAIN_FRAME);
   },
+  maybeTriggerPdfHats() {
+    parentMessagePipe.sendMessage(Message.MAYBE_TRIGGER_PDF_HATS);
+  },
   // TODO(b/219631600): Implement openUrlInBrowserTab() for LacrOS if needed.
 };
 
diff --git a/ash/webui/projector_app/projector_message_handler.h b/ash/webui/projector_app/projector_message_handler.h
index d136a8d2..1bb1ad72 100644
--- a/ash/webui/projector_app/projector_message_handler.h
+++ b/ash/webui/projector_app/projector_message_handler.h
@@ -45,6 +45,7 @@
   base::WeakPtr<ProjectorMessageHandler> GetWeakPtr();
 
   // content::WebUIMessageHandler:
+  // TODO(b/237337607): chrome.send() is banned on ash. Migrate to Mojo instead.
   void RegisterMessages() override;
 
   // ProjectorAppClient:Observer:
diff --git a/ash/webui/shortcut_customization_ui/BUILD.gn b/ash/webui/shortcut_customization_ui/BUILD.gn
index 609df0a..39a1d0e 100644
--- a/ash/webui/shortcut_customization_ui/BUILD.gn
+++ b/ash/webui/shortcut_customization_ui/BUILD.gn
@@ -18,6 +18,7 @@
     "//ash/webui/resources:shortcut_customization_app_resources",
     "//ash/webui/shortcut_customization_ui/backend",
     "//ash/webui/shortcut_customization_ui/mojom",
+    "//ash/webui/system_apps/public:system_web_app_config",
     "//content/public/browser",
     "//ui/resources:webui_generated_resources_grd_grit",
     "//ui/webui",
diff --git a/ash/webui/shortcut_customization_ui/shortcut_customization_app_ui.h b/ash/webui/shortcut_customization_ui/shortcut_customization_app_ui.h
index 9414f51..c7dca02 100644
--- a/ash/webui/shortcut_customization_ui/shortcut_customization_app_ui.h
+++ b/ash/webui/shortcut_customization_ui/shortcut_customization_app_ui.h
@@ -9,6 +9,8 @@
 
 #include "ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider.h"
 #include "ash/webui/shortcut_customization_ui/mojom/shortcut_customization.mojom.h"
+#include "ash/webui/shortcut_customization_ui/url_constants.h"
+#include "ash/webui/system_apps/public/system_web_app_ui_config.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "ui/webui/mojo_web_ui_controller.h"
 
@@ -18,6 +20,17 @@
 
 namespace ash {
 
+class ShortcutCustomizationAppUI;
+
+// The WebUIConfig for chrome://shortcut-customization.
+class ShortcutCustomizationAppUIConfig
+    : public SystemWebAppUIConfig<ShortcutCustomizationAppUI> {
+ public:
+  ShortcutCustomizationAppUIConfig()
+      : SystemWebAppUIConfig(kChromeUIShortcutCustomizationAppHost,
+                             SystemWebAppType::SHORTCUT_CUSTOMIZATION) {}
+};
+
 class ShortcutCustomizationAppUI : public ui::MojoWebUIController {
  public:
   explicit ShortcutCustomizationAppUI(content::WebUI* web_ui);
@@ -36,4 +49,4 @@
 
 }  // namespace ash
 
-#endif  // ASH_WEBUI_SHORTCUT_CUSTOMIZATION_UI_SHORTCUT_CUSTOMIZATION_APP_UI_H_
\ No newline at end of file
+#endif  // ASH_WEBUI_SHORTCUT_CUSTOMIZATION_UI_SHORTCUT_CUSTOMIZATION_APP_UI_H_
diff --git a/ash/webui/system_extensions_internals_ui/mojom/system_extensions_internals_ui.mojom b/ash/webui/system_extensions_internals_ui/mojom/system_extensions_internals_ui.mojom
index 5af20556..bf57d33 100644
--- a/ash/webui/system_extensions_internals_ui/mojom/system_extensions_internals_ui.mojom
+++ b/ash/webui/system_extensions_internals_ui/mojom/system_extensions_internals_ui.mojom
@@ -12,4 +12,12 @@
   // a folder located at the top level of the default Downloads directory.
   InstallSystemExtensionFromDownloadsDir(
     mojo_base.mojom.SafeBaseName system_extension_dir_name) => (bool success);
+
+  // Returns whether a system extension is installed or not. Note that we
+  // currently only support one system extension being installed at a time, so
+  // a boolean is enough.
+  IsSystemExtensionInstalled() => (bool is_installed);
+
+  // Uninstalls the currently installed System Extension.
+  UninstallSystemExtension() => ();
 };
diff --git a/ash/webui/system_extensions_internals_ui/resources/index.html b/ash/webui/system_extensions_internals_ui/resources/index.html
index ad99177c..db875df 100644
--- a/ash/webui/system_extensions_internals_ui/resources/index.html
+++ b/ash/webui/system_extensions_internals_ui/resources/index.html
@@ -8,9 +8,11 @@
 </head>
 <p>Choose a directory with a System Extension to sideload it. The System Extension directory must be in Downloads for ChromeOS to be able to find it.</p>
 <button id="choose-directory">Choose directory</button>
+<button id="uninstall" >Uninstall System Extension</button>
 <dialog id="result-dialog">
   <p>System Extension installed</p>
 </dialog>
+<p id="installed-status"></p>
 <!-- Needed for js browser tests -->
 <script src="chrome://resources/mojo/mojo/public/js/mojo_bindings_lite.js"></script>
 <script type="module" src="index.js"></script>
diff --git a/ash/webui/system_extensions_internals_ui/resources/index.js b/ash/webui/system_extensions_internals_ui/resources/index.js
index b1dc57b..9182204 100644
--- a/ash/webui/system_extensions_internals_ui/resources/index.js
+++ b/ash/webui/system_extensions_internals_ui/resources/index.js
@@ -4,13 +4,26 @@
 
 import {pageHandler} from './page_handler.js';
 
+const installedStatus = document.querySelector('#installed-status');
+async function updateIsInstallStatus() {
+  const {isInstalled} = await pageHandler.isSystemExtensionInstalled();
+  if (isInstalled) {
+    installedStatus.textContent = 'System Extension is installed';
+  } else {
+    installedStatus.textContent = 'System Extension is not installed';
+  }
+}
+
+updateIsInstallStatus();
+
 const chooseDirButton = document.querySelector('#choose-directory');
 const resultDialog = document.querySelector('#result-dialog');
-
 chooseDirButton.addEventListener('click', async event => {
   const directory = await window.showDirectoryPicker({startIn: 'downloads'});
   const {success} = await pageHandler.installSystemExtensionFromDownloadsDir(
       {path: {path: directory.name}});
+  updateIsInstallStatus();
+
   if (success) {
     resultDialog.textContent =
         `System Extension in '${directory.name}' was successfully installed.`;
@@ -20,3 +33,9 @@
   }
   resultDialog.showModal();
 });
+
+const uninstallButton = document.querySelector('#uninstall');
+uninstallButton.addEventListener('click', async event => {
+  await pageHandler.uninstallSystemExtension();
+  updateIsInstallStatus();
+});
diff --git a/base/allocator/partition_allocator/partition_alloc_unittest.cc b/base/allocator/partition_allocator/partition_alloc_unittest.cc
index a324e8c..c9f912e 100644
--- a/base/allocator/partition_allocator/partition_alloc_unittest.cc
+++ b/base/allocator/partition_allocator/partition_alloc_unittest.cc
@@ -2795,6 +2795,58 @@
       allocator.root()->Alloc(requested_size - kExtraAllocSize, type_name);
   memset(ptr1, 'A', requested_size - kExtraAllocSize);
   memset(ptr2, 'A', requested_size - kExtraAllocSize);
+  allocator.root()->Free(ptr1);
+  allocator.root()->Free(ptr2);
+  {
+    MockPartitionStatsDumper dumper;
+    allocator.root()->DumpStats("mock_allocator", false /* detailed dump */,
+                                &dumper);
+    EXPECT_TRUE(dumper.IsMemoryAllocationRecorded());
+
+    const PartitionBucketMemoryStats* stats =
+        dumper.GetBucketStats(requested_size);
+    EXPECT_TRUE(stats);
+    EXPECT_TRUE(stats->is_valid);
+    EXPECT_EQ(0u, stats->decommittable_bytes);
+#if BUILDFLAG(IS_WIN)
+    EXPECT_EQ(3 * SystemPageSize(), stats->discardable_bytes);
+#else
+    EXPECT_EQ(4 * SystemPageSize(), stats->discardable_bytes);
+#endif
+    EXPECT_EQ(requested_size * 2, stats->active_bytes);
+    EXPECT_EQ(10 * SystemPageSize(), stats->resident_bytes);
+  }
+  CHECK_PAGE_IN_CORE(ptr1 - kPointerOffset, true);
+  CHECK_PAGE_IN_CORE(ptr1 - kPointerOffset + SystemPageSize(), true);
+  CHECK_PAGE_IN_CORE(ptr1 - kPointerOffset + (SystemPageSize() * 2), true);
+  CHECK_PAGE_IN_CORE(ptr1 - kPointerOffset + (SystemPageSize() * 3), true);
+  CHECK_PAGE_IN_CORE(ptr1 - kPointerOffset + (SystemPageSize() * 4), true);
+  allocator.root()->PurgeMemory(PurgeFlags::kDiscardUnusedSystemPages);
+  // Except for Windows, the first page is discardable because the freelist
+  // pointer on this page is nullptr. Note that CHECK_PAGE_IN_CORE only executes
+  // checks for Linux and ChromeOS, not for Windows.
+  CHECK_PAGE_IN_CORE(ptr1 - kPointerOffset, false);
+  CHECK_PAGE_IN_CORE(ptr1 - kPointerOffset + SystemPageSize(), false);
+  CHECK_PAGE_IN_CORE(ptr1 - kPointerOffset + (SystemPageSize() * 2), true);
+  CHECK_PAGE_IN_CORE(ptr1 - kPointerOffset + (SystemPageSize() * 3), false);
+  CHECK_PAGE_IN_CORE(ptr1 - kPointerOffset + (SystemPageSize() * 4), false);
+
+  allocator.root()->Free(ptr3);
+  allocator.root()->Free(ptr4);
+}
+
+TEST_P(PartitionAllocTest, PurgeDiscardableNonPageSizedAllocOnSlotBoundary) {
+  const size_t requested_size = 2.5 * SystemPageSize();
+  char* ptr1 = static_cast<char*>(
+      allocator.root()->Alloc(requested_size - kExtraAllocSize, type_name));
+  void* ptr2 =
+      allocator.root()->Alloc(requested_size - kExtraAllocSize, type_name);
+  void* ptr3 =
+      allocator.root()->Alloc(requested_size - kExtraAllocSize, type_name);
+  void* ptr4 =
+      allocator.root()->Alloc(requested_size - kExtraAllocSize, type_name);
+  memset(ptr1, 'A', requested_size - kExtraAllocSize);
+  memset(ptr2, 'A', requested_size - kExtraAllocSize);
   allocator.root()->Free(ptr2);
   allocator.root()->Free(ptr1);
   {
@@ -2808,7 +2860,11 @@
     EXPECT_TRUE(stats);
     EXPECT_TRUE(stats->is_valid);
     EXPECT_EQ(0u, stats->decommittable_bytes);
+#if BUILDFLAG(IS_WIN)
     EXPECT_EQ(3 * SystemPageSize(), stats->discardable_bytes);
+#else
+    EXPECT_EQ(4 * SystemPageSize(), stats->discardable_bytes);
+#endif
     EXPECT_EQ(requested_size * 2, stats->active_bytes);
     EXPECT_EQ(10 * SystemPageSize(), stats->resident_bytes);
   }
@@ -2820,7 +2876,10 @@
   allocator.root()->PurgeMemory(PurgeFlags::kDiscardUnusedSystemPages);
   CHECK_PAGE_IN_CORE(ptr1 - kPointerOffset, true);
   CHECK_PAGE_IN_CORE(ptr1 - kPointerOffset + SystemPageSize(), false);
-  CHECK_PAGE_IN_CORE(ptr1 - kPointerOffset + (SystemPageSize() * 2), true);
+  // Except for Windows, the third page is discardable because the freelist
+  // pointer on this page is nullptr. Note that CHECK_PAGE_IN_CORE only executes
+  // checks for Linux and ChromeOS, not for Windows.
+  CHECK_PAGE_IN_CORE(ptr1 - kPointerOffset + (SystemPageSize() * 2), false);
   CHECK_PAGE_IN_CORE(ptr1 - kPointerOffset + (SystemPageSize() * 3), false);
   CHECK_PAGE_IN_CORE(ptr1 - kPointerOffset + (SystemPageSize() * 4), false);
 
diff --git a/base/allocator/partition_allocator/partition_page.h b/base/allocator/partition_allocator/partition_page.h
index 26b0f6f..157b7a8 100644
--- a/base/allocator/partition_allocator/partition_page.h
+++ b/base/allocator/partition_allocator/partition_page.h
@@ -25,6 +25,7 @@
 #include "base/allocator/partition_allocator/partition_bucket.h"
 #include "base/allocator/partition_allocator/partition_freelist_entry.h"
 #include "base/allocator/partition_allocator/partition_tag_bitmap.h"
+#include "base/allocator/partition_allocator/partition_tag_types.h"
 #include "base/allocator/partition_allocator/reservation_offset_table.h"
 #include "base/allocator/partition_allocator/starscan/state_bitmap.h"
 #include "base/allocator/partition_allocator/tagging.h"
@@ -221,6 +222,10 @@
   PA_ALWAYS_INLINE void SetRawSize(size_t raw_size);
   PA_ALWAYS_INLINE size_t GetRawSize() const;
 
+  // Only meaningful when `this` refers to a slot span in a direct map
+  // bucket.
+  PA_ALWAYS_INLINE PartitionTag* DirectMapMTETag();
+
   PA_ALWAYS_INLINE PartitionFreelistEntry* get_freelist_head() const {
     return freelist_head;
   }
@@ -334,6 +339,13 @@
   //   the first one is used to store slot information, but the second one is
   //   available for extra information)
   size_t raw_size;
+
+  // Specific to when `this` is used in a direct map bucket. Since direct
+  // maps don't have as many tags as the typical normal bucket slot span,
+  // we can get away with just hiding the sole tag in here.
+  //
+  // See `//base/memory/mtecheckedptr.md` for details.
+  PartitionTag direct_map_tag;
 };
 
 // Each partition page has metadata associated with it. The metadata of the
@@ -679,6 +691,14 @@
 }
 
 template <bool thread_safe>
+PA_ALWAYS_INLINE PartitionTag*
+SlotSpanMetadata<thread_safe>::DirectMapMTETag() {
+  PA_DCHECK(bucket->is_direct_mapped());
+  auto* the_next_page = reinterpret_cast<PartitionPage<thread_safe>*>(this) + 1;
+  return &the_next_page->subsequent_page_metadata.direct_map_tag;
+}
+
+template <bool thread_safe>
 PA_ALWAYS_INLINE void SlotSpanMetadata<thread_safe>::SetFreelistHead(
     PartitionFreelistEntry* new_head) {
 #if BUILDFLAG(PA_DCHECK_IS_ON)
diff --git a/base/allocator/partition_allocator/partition_root.cc b/base/allocator/partition_allocator/partition_root.cc
index 8585f02c..bc5bc128f 100644
--- a/base/allocator/partition_allocator/partition_root.cc
+++ b/base/allocator/partition_allocator/partition_root.cc
@@ -337,10 +337,10 @@
   // slots are not in use.
   for (PartitionFreelistEntry* entry = slot_span->get_freelist_head(); entry;
        /**/) {
-    size_t slot_index =
-        (SlotStartPtr2Addr(entry) - slot_span_start) / slot_size;
-    PA_DCHECK(slot_index < num_slots);
-    slot_usage[slot_index] = 0;
+    size_t slot_number =
+        bucket->GetSlotNumber(SlotStartPtr2Addr(entry) - slot_span_start);
+    PA_DCHECK(slot_number < num_slots);
+    slot_usage[slot_number] = 0;
 #if !BUILDFLAG(IS_WIN)
     // If we have a slot where the encoded next pointer is 0, we can actually
     // discard that entry because touching a discarded page is guaranteed to
@@ -348,7 +348,7 @@
     // effective on big-endian machines because the masking function is
     // negation.)
     if (entry->IsEncodedNextPtrZero())
-      last_slot = slot_index;
+      last_slot = slot_number;
 #endif
     entry = entry->GetNext(slot_size);
   }
@@ -415,25 +415,51 @@
     }
   }
 
-  // Next, walk the slots and for any not in use, consider where the system page
-  // boundaries occur. We can release any system pages back to the system as
-  // long as we don't interfere with a freelist pointer or an adjacent slot.
+  // Next, walk the slots and for any not in use, consider which system pages
+  // are no longer needed. We can release any system pages back to the system as
+  // long as we don't interfere with a freelist pointer or an adjacent used
+  // slot.
   for (size_t i = 0; i < num_slots; ++i) {
-    if (slot_usage[i])
+    if (slot_usage[i]) {
       continue;
+    }
+
     // The first address we can safely discard is just after the freelist
     // pointer. There's one quirk: if the freelist pointer is actually nullptr,
     // we can discard that pointer value too.
     uintptr_t begin_addr = slot_span_start + (i * slot_size);
     uintptr_t end_addr = begin_addr + slot_size;
+
+    bool can_discard_free_list_pointer = false;
 #if !BUILDFLAG(IS_WIN)
-    if (i != last_slot)
+    if (i != last_slot) {
       begin_addr += sizeof(internal::PartitionFreelistEntry);
+    } else {
+      can_discard_free_list_pointer = true;
+    }
 #else
     begin_addr += sizeof(internal::PartitionFreelistEntry);
 #endif
-    begin_addr = RoundUpToSystemPage(begin_addr);
+
+    uintptr_t rounded_up_begin_addr = RoundUpToSystemPage(begin_addr);
+    uintptr_t rounded_down_begin_addr = RoundDownToSystemPage(begin_addr);
     end_addr = RoundDownToSystemPage(end_addr);
+
+    // |rounded_up_begin_addr| could be greater than |end_addr| only if slot
+    // size was less than system page size, or if free list pointer crossed the
+    // page boundary. Neither is possible here.
+    PA_DCHECK(rounded_up_begin_addr <= end_addr);
+
+    if (rounded_down_begin_addr < rounded_up_begin_addr && i != 0 &&
+        !slot_usage[i - 1] && can_discard_free_list_pointer) {
+      // This slot contains a partial page in the beginning. The rest of that
+      // page is contained in the slot[i-1], which is also discardable.
+      // Therefore we can discard this page.
+      begin_addr = rounded_down_begin_addr;
+    } else {
+      begin_addr = rounded_up_begin_addr;
+    }
+
     if (begin_addr < end_addr) {
       size_t partial_slot_bytes = end_addr - begin_addr;
       discardable_bytes += partial_slot_bytes;
@@ -443,6 +469,7 @@
       }
     }
   }
+
   return discardable_bytes;
 }
 
diff --git a/base/allocator/partition_allocator/partition_tag_types.h b/base/allocator/partition_allocator/partition_tag_types.h
index e67de3b..b7f42da 100644
--- a/base/allocator/partition_allocator/partition_tag_types.h
+++ b/base/allocator/partition_allocator/partition_tag_types.h
@@ -8,7 +8,7 @@
 #include <cstdint>
 
 // This header defines the types for MTECheckedPtr. Canonical
-// documentation available at `//base/memory/mtecheckedptr.md`.
+// documentation available at `//base/memory/raw_ptr_mtecheckedptr.md`.
 
 namespace partition_alloc {
 
diff --git a/base/memory/mtecheckedptr.md b/base/memory/raw_ptr_mtecheckedptr.md
similarity index 71%
rename from base/memory/mtecheckedptr.md
rename to base/memory/raw_ptr_mtecheckedptr.md
index 5420900..8c9634da 100644
--- a/base/memory/mtecheckedptr.md
+++ b/base/memory/raw_ptr_mtecheckedptr.md
@@ -20,6 +20,14 @@
 reading.
 ***
 
+*** aside
+MTECheckedPtr is one particular incarnation of `raw_ptr`, and so the
+primary documentation is kept here in `//base/memory/`. However, the
+implementation is woven deeply into PartitionAlloc, and inevitably
+some dirty PA-internal details may bubble up here when discussing
+how MTECheckedPtr works.
+***
+
 MTECheckedPtr is a Chromium-specific implementation of ARM's
 [MTE concept][arm-mte]. When MTECheckedPtr is enabled,
 
@@ -84,4 +92,22 @@
 both of the above degrade the `raw_ptr<T, D>` into the no-op version
 of `raw_ptr`.
 
+## Appendix: PA-Internal Tag Locations
+
+[The top-level PartitionAlloc documentation][pa-readme]
+mentions the space in which
+MTECheckedPtr's tags reside - in the space labeled "Bitmaps(?)" in the
+super page diagram, before the first usable slot span. This diagram
+only applies to *normal* buckets and not to *direct map* buckets.
+
+While direct map super pages also cordon off the first partition page
+and offer access to the core metadata within, reservations are always
+permissible immediately after, and there are no bitmaps (whether
+from *Scan or MTECheckedPtr) following that first partition page.
+In implementing MTECheckedPtr support for direct maps, we decided
+not to add this extra headroom for bitmaps; instead, the tag is
+placed directly in `SubsequentPageMetadata`, colocated with the core
+metadata in the first partition page.
+
 [arm-mte]: https://community.arm.com/arm-community-blogs/b/architectures-and-processors-blog/posts/enhancing-memory-safety
+[pa-readme]: ../allocator/partition_allocator/PartitionAlloc.md#layout-in-memory
diff --git a/base/task/thread_pool/job_task_source.cc b/base/task/thread_pool/job_task_source.cc
index 8529e31..605cbf5 100644
--- a/base/task/thread_pool/job_task_source.cc
+++ b/base/task/thread_pool/job_task_source.cc
@@ -358,6 +358,15 @@
          GetMaxConcurrency(state_before_sub.worker_count() - 1);
 }
 
+// This is a no-op and should always return true.
+bool JobTaskSource::WillReEnqueue(TimeTicks now,
+                                  TaskSource::Transaction* /*transaction*/) {
+  return true;
+}
+
+// This is a no-op.
+void JobTaskSource::OnBecomeReady() {}
+
 TaskSourceSortKey JobTaskSource::GetSortKey(
     bool disable_fair_scheduling) const {
   if (disable_fair_scheduling) {
diff --git a/base/task/thread_pool/job_task_source.h b/base/task/thread_pool/job_task_source.h
index 9cb03b6fc..cb06210 100644
--- a/base/task/thread_pool/job_task_source.h
+++ b/base/task/thread_pool/job_task_source.h
@@ -193,6 +193,9 @@
   Task TakeTask(TaskSource::Transaction* transaction) override;
   Task Clear(TaskSource::Transaction* transaction) override;
   bool DidProcessTask(TaskSource::Transaction* transaction) override;
+  bool WillReEnqueue(TimeTicks now,
+                     TaskSource::Transaction* transaction) override;
+  void OnBecomeReady() override;
 
   // Synchronizes access to workers state.
   mutable CheckedLock worker_lock_{UniversalSuccessor()};
diff --git a/base/task/thread_pool/pooled_single_thread_task_runner_manager.cc b/base/task/thread_pool/pooled_single_thread_task_runner_manager.cc
index 12f46cec..ed562938 100644
--- a/base/task/thread_pool/pooled_single_thread_task_runner_manager.cc
+++ b/base/task/thread_pool/pooled_single_thread_task_runner_manager.cc
@@ -131,8 +131,12 @@
 
   void DidProcessTask(RegisteredTaskSource task_source) override {
     if (task_source) {
-      EnqueueTaskSource(TransactionWithRegisteredTaskSource::FromTaskSource(
-          std::move(task_source)));
+      auto task_source_with_transaction =
+          TransactionWithRegisteredTaskSource::FromTaskSource(
+              std::move(task_source));
+      task_source_with_transaction.task_source.WillReEnqueue(
+          TimeTicks::Now(), &task_source_with_transaction.transaction);
+      EnqueueTaskSource(std::move(task_source_with_transaction));
     }
   }
 
@@ -143,7 +147,7 @@
 
     // |task| will be pushed to |sequence|, and |sequence| will be queued
     // to |priority_queue_| iff |sequence_should_be_queued| is true.
-    const bool sequence_should_be_queued = transaction.WillPushTask();
+    const bool sequence_should_be_queued = transaction.ShouldBeQueued();
     RegisteredTaskSource task_source;
     if (sequence_should_be_queued) {
       task_source = task_tracker_->RegisterTaskSource(sequence);
@@ -153,7 +157,7 @@
     }
     if (!task_tracker_->WillPostTaskNow(task, transaction.traits().priority()))
       return false;
-    transaction.PushTask(std::move(task));
+    transaction.PushImmediateTask(std::move(task));
     if (task_source) {
       bool should_wakeup =
           EnqueueTaskSource({std::move(task_source), std::move(transaction)});
@@ -355,7 +359,7 @@
       if (task_tracker()->WillPostTask(
               &pump_message_task, TaskShutdownBehavior::SKIP_ON_SHUTDOWN)) {
         auto transaction = message_pump_sequence_->BeginTransaction();
-        const bool sequence_should_be_queued = transaction.WillPushTask();
+        const bool sequence_should_be_queued = transaction.ShouldBeQueued();
         DCHECK(sequence_should_be_queued)
             << "GetWorkFromWindowsMessageQueue() does not expect "
                "queueing of pump tasks.";
@@ -363,7 +367,7 @@
             std::move(message_pump_sequence_));
         if (!registered_task_source)
           return nullptr;
-        transaction.PushTask(std::move(pump_message_task));
+        transaction.PushImmediateTask(std::move(pump_message_task));
         return registered_task_source;
       }
     }
diff --git a/base/task/thread_pool/priority_queue_unittest.cc b/base/task/thread_pool/priority_queue_unittest.cc
index fe8ab50..a976d37 100644
--- a/base/task/thread_pool/priority_queue_unittest.cc
+++ b/base/task/thread_pool/priority_queue_unittest.cc
@@ -43,7 +43,7 @@
     task_environment.FastForwardBy(Microseconds(1));
     scoped_refptr<Sequence> sequence = MakeRefCounted<Sequence>(
         traits, nullptr, TaskSourceExecutionMode::kParallel);
-    sequence->BeginTransaction().PushTask(
+    sequence->BeginTransaction().PushImmediateTask(
         Task(FROM_HERE, DoNothing(), TimeTicks::Now(), TimeDelta()));
     return sequence;
   }
diff --git a/base/task/thread_pool/sequence.cc b/base/task/thread_pool/sequence.cc
index 6ebde82..b4892bf 100644
--- a/base/task/thread_pool/sequence.cc
+++ b/base/task/thread_pool/sequence.cc
@@ -24,39 +24,50 @@
 
 Sequence::Transaction::~Transaction() = default;
 
-bool Sequence::Transaction::WillPushTask() const {
-  // A sequence should be queued if it's not already in the queue and the pool
-  // is not running any task from it. Otherwise, one of these must be true:
-  // - The Sequence is already queued, or,
-  // - A thread is running a Task from the Sequence. It is expected to reenqueue
-  //   the Sequence once it's done running the Task.
-  // Access to |current_location_| can get racy between calls to WillRunTask()
-  // and WillPushTask(). WillRunTask() updates |current_location_| from
-  // kImmediateQueue to kInWorker, it can only be called on sequence when
-  // sequence is already in immediate queue so this behavior is always
-  // guaranteed. Hence, WillPushTask behavior won't be affected no matter if
-  // WillRunTask runs before or after it's called since it returns false
-  // whether |current_location_| is set to kImmediateQueue or kInWorker.
+bool Sequence::Transaction::ShouldBeQueued() const {
+  // A sequence should be queued to the immediate queue after receiving a new
+  // immediate Task, or queued to or updated in the delayed queue after
+  // receiving a new delayed Task, if it's not already in the immediate queue
+  // and the pool is not running any task from it. WillRunTask() can racily
+  // modify |current_location_|, but always from |kImmediateQueue| to
+  // |kInWorker|. In that case, ShouldBeQueued() returns false whether
+  // WillRunTask() runs immediately before or after.
+  // When pushing a delayed task, a sequence can become ready at any time,
+  // triggering OnBecomeReady() which racily modifies |current_location_|
+  // from kDelayedQueue to kImmediateQueue. In that case this function may
+  // return true which immediately becomes incorrect. This race is resolved
+  // outside of this class. See my comment on ShouldBeQueued() in the header
+  // file.
   auto current_location =
       sequence()->current_location_.load(std::memory_order_relaxed);
-  if (current_location == Sequence::SequenceLocation::kImmediateQueue) {
-    return false;
-  }
-
-  if (current_location == Sequence::SequenceLocation::kInWorker) {
+  if (current_location == Sequence::SequenceLocation::kImmediateQueue ||
+      current_location == Sequence::SequenceLocation::kInWorker) {
     return false;
   }
 
   return true;
 }
 
-void Sequence::Transaction::PushTask(Task task) {
+bool Sequence::Transaction::TopDelayedTaskWillChange(Task& delayed_task) const {
+  if (sequence()->IsEmpty())
+    return true;
+  return delayed_task.latest_delayed_run_time() <
+         sequence()->delayed_queue_.top().latest_delayed_run_time();
+}
+
+void Sequence::Transaction::PushImmediateTask(Task task) {
   // Use CHECK instead of DCHECK to crash earlier. See http://crbug.com/711167
   // for details.
   CHECK(task.task);
   DCHECK(!task.queue_time.is_null());
 
-  bool should_be_queued = WillPushTask();
+  auto current_location =
+      sequence()->current_location_.load(std::memory_order_relaxed);
+  bool was_unretained =
+      sequence()->IsEmpty() &&
+      current_location != Sequence::SequenceLocation::kInWorker;
+  bool queue_was_empty = sequence()->queue_.empty();
+
   task.task = sequence()->traits_.shutdown_behavior() ==
                       TaskShutdownBehavior::BLOCK_SHUTDOWN
                   ? MakeCriticalClosure(
@@ -64,56 +75,156 @@
                         /*is_immediate=*/task.delayed_run_time.is_null())
                   : std::move(task.task);
 
-  if (sequence()->queue_.empty()) {
-    sequence()->ready_time_.store(task.GetDesiredExecutionTime(),
-                                  std::memory_order_relaxed);
-  }
   sequence()->queue_.push(std::move(task));
 
-  if (should_be_queued)
+  if (queue_was_empty) {
+    sequence()->ready_time_.store(sequence()->GetNextReadyTime(),
+                                  std::memory_order_relaxed);
+  }
+
+  if (current_location == Sequence::SequenceLocation::kDelayedQueue ||
+      current_location == Sequence::SequenceLocation::kNone) {
     sequence()->current_location_.store(
         Sequence::SequenceLocation::kImmediateQueue, std::memory_order_relaxed);
+  }
 
   // AddRef() matched by manual Release() when the sequence has no more tasks
   // to run (in DidProcessTask() or Clear()).
-  if (should_be_queued && sequence()->task_runner())
+  if (was_unretained && sequence()->task_runner())
     sequence()->task_runner()->AddRef();
 }
 
+void Sequence::Transaction::PushDelayedTask(Task task) {
+  // Use CHECK instead of DCHECK to crash earlier. See http://crbug.com/711167
+  // for details.
+  CHECK(task.task);
+  DCHECK(!task.queue_time.is_null());
+  DCHECK(!task.delayed_run_time.is_null());
+
+  auto current_location =
+      sequence()->current_location_.load(std::memory_order_relaxed);
+  bool was_unretained =
+      sequence()->IsEmpty() &&
+      current_location != Sequence::SequenceLocation::kInWorker;
+
+  task.task =
+      sequence()->traits_.shutdown_behavior() ==
+              TaskShutdownBehavior::BLOCK_SHUTDOWN
+          ? MakeCriticalClosure(task.posted_from, std::move(task.task), false)
+          : std::move(task.task);
+
+  sequence()->delayed_queue_.insert(std::move(task));
+
+  if (sequence()->queue_.empty()) {
+    sequence()->ready_time_.store(sequence()->GetNextReadyTime(),
+                                  std::memory_order_relaxed);
+  }
+
+  auto expected_location = Sequence::SequenceLocation::kNone;
+  sequence()->current_location_.compare_exchange_strong(
+      expected_location, Sequence::SequenceLocation::kDelayedQueue,
+      std::memory_order_relaxed);
+
+  // AddRef() matched by manual Release() when the sequence has no more tasks
+  // to run (in DidProcessTask() or Clear()).
+  if (was_unretained && sequence()->task_runner())
+    sequence()->task_runner()->AddRef();
+}
+
+// Delayed tasks are ordered by latest_delayed_run_time(). The top task may
+// not be the first task eligible to run, but tasks will always become ripe
+// before their latest_delayed_run_time().
+bool Sequence::DelayedTaskGreater::operator()(const Task& lhs,
+                                              const Task& rhs) const {
+  TimeTicks lhs_latest_delayed_run_time = lhs.latest_delayed_run_time();
+  TimeTicks rhs_latest_delayed_run_time = rhs.latest_delayed_run_time();
+  return std::tie(lhs_latest_delayed_run_time, lhs.sequence_num) >
+         std::tie(rhs_latest_delayed_run_time, rhs.sequence_num);
+}
+
 TaskSource::RunStatus Sequence::WillRunTask() {
   // There should never be a second call to WillRunTask() before DidProcessTask
   // since the RunStatus is always marked a saturated.
-  DCHECK(current_location_.load(std::memory_order_relaxed) !=
-         Sequence::SequenceLocation::kInWorker);
+
+  DCHECK_EQ(current_location_.load(std::memory_order_relaxed),
+            Sequence::SequenceLocation::kImmediateQueue);
 
   // It's ok to access |current_location_| outside of a Transaction since
   // WillRunTask() is externally synchronized, always called in sequence with
-  // TakeTask() and DidProcessTask() and only called if sequence is in immediate
-  // queue. Even though it can get racy with WillPushTask()/PushTask(), the
-  // behavior of each function is not affected as explained in WillPushTask().
+  // OnBecomeReady(), TakeTask(), WillReEnqueue() and DidProcessTask() and only
+  // called if sequence is in immediate queue. Even though it can get racy with
+  // ShouldBeQueued()/PushImmediateTask()/PushDelayedTask(), the behavior of
+  // each function is not affected as explained in ShouldBeQueued().
   current_location_.store(Sequence::SequenceLocation::kInWorker,
                           std::memory_order_relaxed);
 
   return RunStatus::kAllowedSaturated;
 }
 
+void Sequence::OnBecomeReady() {
+  // This should always be called from a worker thread at a time and it will be
+  // called only before WillRunTask().
+  DCHECK(current_location_.load(std::memory_order_relaxed) ==
+         Sequence::SequenceLocation::kDelayedQueue);
+
+  // It's ok to access |current_location_| outside of a Transaction since
+  // OnBecomeReady() is externally synchronized and always called in sequence
+  // with WillRunTask(). This can get racy with
+  // ShouldBeQueued()/PushDelayedTask(). See comment in
+  // ShouldBeQueued() to see how races with this function are resolved.
+  current_location_.store(Sequence::SequenceLocation::kImmediateQueue,
+                          std::memory_order_relaxed);
+}
+
 size_t Sequence::GetRemainingConcurrency() const {
   return 1;
 }
 
+Task Sequence::TakeNextImmediateTask() {
+  Task next_task = std::move(queue_.front());
+  queue_.pop();
+  return next_task;
+}
+
+Task Sequence::TakeEarliestTask() {
+  if (queue_.empty())
+    return delayed_queue_.take_top();
+
+  if (delayed_queue_.empty())
+    return TakeNextImmediateTask();
+
+  // Both queues contain at least a task. Decide from which one the task should
+  // be taken.
+  if (queue_.front().queue_time <=
+      delayed_queue_.top().latest_delayed_run_time())
+    return TakeNextImmediateTask();
+
+  return delayed_queue_.take_top();
+}
+
+TimeTicks Sequence::GetNextReadyTime() {
+  if (queue_.empty())
+    return delayed_queue_.top().latest_delayed_run_time();
+
+  if (delayed_queue_.empty())
+    return queue_.front().queue_time;
+
+  return std::min(queue_.front().queue_time,
+                  delayed_queue_.top().latest_delayed_run_time());
+}
+
 Task Sequence::TakeTask(TaskSource::Transaction* transaction) {
   CheckedAutoLockMaybe auto_lock(transaction ? nullptr : &lock_);
 
   DCHECK(current_location_.load(std::memory_order_relaxed) ==
          Sequence::SequenceLocation::kInWorker);
-  DCHECK(!queue_.empty());
-  DCHECK(queue_.front().task);
+  DCHECK(!queue_.empty() || !delayed_queue_.empty());
 
-  auto next_task = std::move(queue_.front());
-  queue_.pop();
-  if (!queue_.empty()) {
-    ready_time_.store(queue_.front().queue_time, std::memory_order_relaxed);
-  }
+  auto next_task = TakeEarliestTask();
+
+  if (!IsEmpty())
+    ready_time_.store(GetNextReadyTime(), std::memory_order_relaxed);
+
   return next_task;
 }
 
@@ -125,21 +236,57 @@
          Sequence::SequenceLocation::kInWorker);
 
   // See comment on TaskSource::task_runner_ for lifetime management details.
-  if (queue_.empty()) {
+  if (IsEmpty()) {
     ReleaseTaskRunner();
     current_location_.store(Sequence::SequenceLocation::kNone,
                             std::memory_order_relaxed);
     return false;
   }
 
-  current_location_.store(Sequence::SequenceLocation::kImmediateQueue,
-                          std::memory_order_relaxed);
   // Let the caller re-enqueue this non-empty Sequence regardless of
   // |run_result| so it can continue churning through this Sequence's tasks and
   // skip/delete them in the proper scope.
   return true;
 }
 
+bool Sequence::WillReEnqueue(TimeTicks now,
+                             TaskSource::Transaction* transaction) {
+  CheckedAutoLockMaybe auto_lock(transaction ? nullptr : &lock_);
+  // This should always be called from a worker thread and it will be
+  // called after DidProcessTask().
+  DCHECK(current_location_.load(std::memory_order_relaxed) ==
+         Sequence::SequenceLocation::kInWorker);
+
+  bool has_ready_tasks = HasReadyTasks(now);
+  if (has_ready_tasks) {
+    current_location_.store(Sequence::SequenceLocation::kImmediateQueue,
+                            std::memory_order_relaxed);
+  } else {
+    current_location_.store(Sequence::SequenceLocation::kDelayedQueue,
+                            std::memory_order_relaxed);
+  }
+
+  return has_ready_tasks;
+}
+
+bool Sequence::HasReadyTasks(TimeTicks now) const {
+  return HasRipeDelayedTasks(now) || HasImmediateTasks();
+}
+
+bool Sequence::HasRipeDelayedTasks(TimeTicks now) const {
+  if (delayed_queue_.empty())
+    return false;
+
+  if (!delayed_queue_.top().task.MaybeValid())
+    return true;
+
+  return delayed_queue_.top().earliest_delayed_run_time() <= now;
+}
+
+bool Sequence::HasImmediateTasks() const {
+  return !queue_.empty();
+}
+
 TaskSourceSortKey Sequence::GetSortKey(
     bool /* disable_fair_scheduling */) const {
   return TaskSourceSortKey(priority_racy(),
@@ -149,19 +296,24 @@
 Task Sequence::Clear(TaskSource::Transaction* transaction) {
   CheckedAutoLockMaybe auto_lock(transaction ? nullptr : &lock_);
   // See comment on TaskSource::task_runner_ for lifetime management details.
-  if (!queue_.empty() && current_location_.load(std::memory_order_relaxed) !=
-                             Sequence::SequenceLocation::kInWorker) {
+  if (!IsEmpty() && current_location_.load(std::memory_order_relaxed) !=
+                        Sequence::SequenceLocation::kInWorker) {
     ReleaseTaskRunner();
   }
 
-  return Task(FROM_HERE,
-              base::BindOnce(
-                  [](base::queue<Task> queue) {
-                    while (!queue.empty())
-                      queue.pop();
-                  },
-                  std::move(queue_)),
-              TimeTicks(), TimeDelta());
+  return Task(
+      FROM_HERE,
+      base::BindOnce(
+          [](base::queue<Task> queue,
+             base::IntrusiveHeap<Task, DelayedTaskGreater> delayed_queue) {
+            while (!queue.empty())
+              queue.pop();
+
+            while (!delayed_queue.empty())
+              delayed_queue.pop();
+          },
+          std::move(queue_), std::move(delayed_queue_)),
+      TimeTicks(), TimeDelta());
 }
 
 void Sequence::ReleaseTaskRunner() {
@@ -191,5 +343,9 @@
   return current_location_.load(std::memory_order_relaxed);
 }
 
+bool Sequence::IsEmpty() const {
+  return queue_.empty() && delayed_queue_.empty();
+}
+
 }  // namespace internal
 }  // namespace base
diff --git a/base/task/thread_pool/sequence.h b/base/task/thread_pool/sequence.h
index 8e65d695..c0694ca0 100644
--- a/base/task/thread_pool/sequence.h
+++ b/base/task/thread_pool/sequence.h
@@ -8,6 +8,7 @@
 #include <stddef.h>
 
 #include "base/base_export.h"
+#include "base/containers/intrusive_heap.h"
 #include "base/containers/queue.h"
 #include "base/sequence_token.h"
 #include "base/task/task_traits.h"
@@ -20,8 +21,15 @@
 namespace base {
 namespace internal {
 
-// A Sequence holds slots each containing up to a single Task that must be
-// executed in posting order.
+// A Sequence is intended to hold delayed tasks and immediate tasks.
+// Delayed tasks are held in a prority_queue until they are ripe and
+// immediate tasks in a simple fifo queue.
+// Sequence::PushTask is responsible for putting a task into the right
+// queue depending on its nature.
+// When Sequence::TakeTask is called, we select the next appropriate task
+// from both queues and return it.
+// Each queue holds slots each containing up to a single Task that must be
+// executed in posting/runtime order.
 //
 // In comments below, an "empty Sequence" is a Sequence with no slot.
 //
@@ -34,10 +42,9 @@
 // dangling reference cycle would only occur should they release their reference
 // to it while it's not empty. In other words, it is only correct for them to
 // release it after PopTask() returns false to indicate it was made empty by
-// that call (in which case the next PushTask() will return true to indicate to
-// the caller that the Sequence should be re-enqueued for execution).
-//
-// This class is thread-safe.
+// that call (in which case the next PushImmediateTask() will return true to
+// indicate to the caller that the Sequence should be re-enqueued for
+// execution). This class is thread-safe.
 class BASE_EXPORT Sequence : public TaskSource {
  public:
   // A Transaction can perform multiple operations atomically on a
@@ -51,13 +58,26 @@
     Transaction& operator=(const Transaction&) = delete;
     ~Transaction();
 
-    // Returns true if the sequence would need to be queued after receiving a
-    // new Task.
-    [[nodiscard]] bool WillPushTask() const;
+    // Returns true if the sequence would need to be queued in the
+    // immediate/delayed queue after receiving a new immediate/delayed Task.
+    // Thread-safe but the returned value may immediately be obsolete when
+    // pushing a delayed task since a sequence can become ready at any time;
+    // therefore it must be externally synchronized to prevent races against
+    // OnBecomeReady().
+    [[nodiscard]] bool ShouldBeQueued() const;
 
-    // Adds |task| in a new slot at the end of the Sequence. This must only be
-    // called after invoking WillPushTask().
-    void PushTask(Task task);
+    // Returns true if the task to be posted will change the sequence
+    // delayed_queue top.
+    bool TopDelayedTaskWillChange(Task& delayed_task) const;
+
+    // Adds immediate |task| to the end of this sequence. This must only
+    // be called after invoking ShouldBeQueued().
+    void PushImmediateTask(Task task);
+
+    // Adds a delayed |task| in this sequence to be prioritized based on it's
+    // delayed run time. This must only be called after invoking
+    // TopDelayedTaskWillChange()/ShouldBeQueued().
+    void PushDelayedTask(Task task);
 
     Sequence* sequence() const { return static_cast<Sequence*>(task_source()); }
 
@@ -110,22 +130,54 @@
 
   SequenceLocation GetCurrentLocationForTesting();
 
+  void OnBecomeReady() override;
+
  private:
   ~Sequence() override;
 
+  struct DelayedTaskGreater {
+    bool operator()(const Task& lhs, const Task& rhs) const;
+  };
+
   // TaskSource:
   RunStatus WillRunTask() override;
   Task TakeTask(TaskSource::Transaction* transaction) override;
   Task Clear(TaskSource::Transaction* transaction) override;
   bool DidProcessTask(TaskSource::Transaction* transaction) override;
+  bool WillReEnqueue(TimeTicks now,
+                     TaskSource::Transaction* transaction) override;
+
+  // Selects the earliest task to run, either from immediate or
+  // delayed queue and return it.
+  // Expects this sequence to have at least one task that can run
+  // immediately.
+  Task TakeEarliestTask();
+
+  // Get and return next task from immediate queue
+  Task TakeNextImmediateTask();
+
+  // Determine next ready time and set ready time to it
+  TimeTicks GetNextReadyTime();
+
+  // Returns true if there are immediate tasks
+  bool HasImmediateTasks() const;
+
+  // Returns true if there are tasks ripe for execution in the delayed queue
+  bool HasRipeDelayedTasks(TimeTicks now) const;
+
+  // Returns true if tasks ready to be executed
+  bool HasReadyTasks(TimeTicks now) const;
+
+  bool IsEmpty() const;
 
   // Releases reference to TaskRunner.
   void ReleaseTaskRunner();
 
   const SequenceToken token_ = SequenceToken::Create();
 
-  // Queue of tasks to execute.
+  // Queues of tasks to execute.
   base::queue<Task> queue_;
+  base::IntrusiveHeap<Task, DelayedTaskGreater> delayed_queue_;
 
   std::atomic<TimeTicks> ready_time_{TimeTicks()};
 
diff --git a/base/task/thread_pool/sequence_unittest.cc b/base/task/thread_pool/sequence_unittest.cc
index bddc48b0..8772fcd 100644
--- a/base/task/thread_pool/sequence_unittest.cc
+++ b/base/task/thread_pool/sequence_unittest.cc
@@ -25,9 +25,16 @@
   MOCK_METHOD0(Run, void());
 };
 
-Task CreateTask(MockTask* mock_task) {
-  return Task(FROM_HERE, BindOnce(&MockTask::Run, Unretained(mock_task)),
-              TimeTicks::Now(), TimeDelta());
+Task CreateTask(MockTask* mock_task, TimeTicks now = TimeTicks::Now()) {
+  return Task(FROM_HERE, BindOnce(&MockTask::Run, Unretained(mock_task)), now,
+              TimeDelta());
+}
+
+Task CreateDelayedTask(MockTask* mock_task,
+                       TimeDelta delay,
+                       TimeTicks now = TimeTicks::Now()) {
+  return Task(FROM_HERE, BindOnce(&MockTask::Run, Unretained(mock_task)), now,
+              delay);
 }
 
 void ExpectMockTask(MockTask* mock_task, Task* task) {
@@ -50,19 +57,19 @@
                                TaskSourceExecutionMode::kParallel);
   Sequence::Transaction sequence_transaction(sequence->BeginTransaction());
 
-  // Push task A in the sequence. PushTask() should return true since it's the
-  // first task->
-  EXPECT_TRUE(sequence_transaction.WillPushTask());
-  sequence_transaction.PushTask(CreateTask(&mock_task_a));
+  // Push task A in the sequence. PushImmediateTask() should return true since
+  // it's the first task->
+  EXPECT_TRUE(sequence_transaction.ShouldBeQueued());
+  sequence_transaction.PushImmediateTask(CreateTask(&mock_task_a));
 
-  // Push task B, C and D in the sequence. PushTask() should return false
-  // since there is already a task in a sequence.
-  EXPECT_FALSE(sequence_transaction.WillPushTask());
-  sequence_transaction.PushTask(CreateTask(&mock_task_b));
-  EXPECT_FALSE(sequence_transaction.WillPushTask());
-  sequence_transaction.PushTask(CreateTask(&mock_task_c));
-  EXPECT_FALSE(sequence_transaction.WillPushTask());
-  sequence_transaction.PushTask(CreateTask(&mock_task_d));
+  // Push task B, C and D in the sequence. PushImmediateTask() should return
+  // false since there is already a task in a sequence.
+  EXPECT_FALSE(sequence_transaction.ShouldBeQueued());
+  sequence_transaction.PushImmediateTask(CreateTask(&mock_task_b));
+  EXPECT_FALSE(sequence_transaction.ShouldBeQueued());
+  sequence_transaction.PushImmediateTask(CreateTask(&mock_task_c));
+  EXPECT_FALSE(sequence_transaction.ShouldBeQueued());
+  sequence_transaction.PushImmediateTask(CreateTask(&mock_task_d));
 
   // Take the task in front of the sequence. It should be task A.
   auto registered_task_source =
@@ -75,8 +82,10 @@
 
   // Remove the empty slot. Task B should now be in front.
   EXPECT_TRUE(registered_task_source.DidProcessTask(&sequence_transaction));
+  EXPECT_TRUE(registered_task_source.WillReEnqueue(TimeTicks::Now(),
+                                                   &sequence_transaction));
 
-  EXPECT_FALSE(sequence_transaction.WillPushTask());
+  EXPECT_FALSE(sequence_transaction.ShouldBeQueued());
   registered_task_source.WillRunTask();
   task = registered_task_source.TakeTask(&sequence_transaction);
   ExpectMockTask(&mock_task_b, &task.value());
@@ -84,8 +93,10 @@
 
   // Remove the empty slot. Task C should now be in front.
   EXPECT_TRUE(registered_task_source.DidProcessTask(&sequence_transaction));
+  EXPECT_TRUE(registered_task_source.WillReEnqueue(TimeTicks::Now(),
+                                                   &sequence_transaction));
 
-  EXPECT_FALSE(sequence_transaction.WillPushTask());
+  EXPECT_FALSE(sequence_transaction.ShouldBeQueued());
   registered_task_source.WillRunTask();
   task = registered_task_source.TakeTask(&sequence_transaction);
   ExpectMockTask(&mock_task_c, &task.value());
@@ -93,10 +104,12 @@
 
   // Remove the empty slot.
   EXPECT_TRUE(registered_task_source.DidProcessTask(&sequence_transaction));
+  EXPECT_TRUE(registered_task_source.WillReEnqueue(TimeTicks::Now(),
+                                                   &sequence_transaction));
 
   // Push task E in the sequence.
-  EXPECT_FALSE(sequence_transaction.WillPushTask());
-  sequence_transaction.PushTask(CreateTask(&mock_task_e));
+  EXPECT_FALSE(sequence_transaction.ShouldBeQueued());
+  sequence_transaction.PushImmediateTask(CreateTask(&mock_task_e));
 
   // Task D should be in front.
   registered_task_source.WillRunTask();
@@ -106,7 +119,9 @@
 
   // Remove the empty slot. Task E should now be in front.
   EXPECT_TRUE(registered_task_source.DidProcessTask(&sequence_transaction));
-  EXPECT_FALSE(sequence_transaction.WillPushTask());
+  EXPECT_TRUE(registered_task_source.WillReEnqueue(TimeTicks::Now(),
+                                                   &sequence_transaction));
+  EXPECT_FALSE(sequence_transaction.ShouldBeQueued());
   registered_task_source.WillRunTask();
   task = registered_task_source.TakeTask(&sequence_transaction);
   ExpectMockTask(&mock_task_e, &task.value());
@@ -114,7 +129,7 @@
 
   // Remove the empty slot. The sequence should now be empty.
   EXPECT_FALSE(registered_task_source.DidProcessTask(&sequence_transaction));
-  EXPECT_TRUE(sequence_transaction.WillPushTask());
+  EXPECT_TRUE(sequence_transaction.ShouldBeQueued());
 }
 
 // Verifies the sort key of a BEST_EFFORT sequence that contains one task.
@@ -126,7 +141,8 @@
                                TaskSourceExecutionMode::kParallel);
   Sequence::Transaction best_effort_sequence_transaction(
       best_effort_sequence->BeginTransaction());
-  best_effort_sequence_transaction.PushTask(std::move(best_effort_task));
+  best_effort_sequence_transaction.PushImmediateTask(
+      std::move(best_effort_task));
 
   // Get the sort key.
   const TaskSourceSortKey best_effort_sort_key =
@@ -160,7 +176,7 @@
                                TaskSourceExecutionMode::kParallel);
   Sequence::Transaction foreground_sequence_transaction(
       foreground_sequence->BeginTransaction());
-  foreground_sequence_transaction.PushTask(std::move(foreground_task));
+  foreground_sequence_transaction.PushImmediateTask(std::move(foreground_task));
 
   // Get the sort key.
   const TaskSourceSortKey foreground_sort_key =
@@ -189,7 +205,7 @@
   scoped_refptr<Sequence> sequence = MakeRefCounted<Sequence>(
       TaskTraits(), nullptr, TaskSourceExecutionMode::kParallel);
   Sequence::Transaction sequence_transaction(sequence->BeginTransaction());
-  sequence_transaction.PushTask(
+  sequence_transaction.PushImmediateTask(
       Task(FROM_HERE, DoNothing(), TimeTicks::Now(), TimeDelta()));
 
   auto registered_task_source =
@@ -205,7 +221,7 @@
   scoped_refptr<Sequence> sequence = MakeRefCounted<Sequence>(
       TaskTraits(), nullptr, TaskSourceExecutionMode::kParallel);
   Sequence::Transaction sequence_transaction(sequence->BeginTransaction());
-  sequence_transaction.PushTask(
+  sequence_transaction.PushImmediateTask(
       Task(FROM_HERE, DoNothing(), TimeTicks::Now(), TimeDelta()));
 
   auto registered_task_source =
@@ -249,23 +265,24 @@
 
   Sequence::Transaction sequence_transaction(sequence->BeginTransaction());
 
-  // Push task A in the sequence. WillPushTask() should return true
-  // since sequence is empty.
-  EXPECT_TRUE(sequence_transaction.WillPushTask());
-  sequence_transaction.PushTask(CreateTask(&mock_task_a));
+  // Push task A in the sequence. ShouldBeQueued() should return
+  // true since sequence is empty.
+  EXPECT_TRUE(sequence_transaction.ShouldBeQueued());
+  sequence_transaction.PushImmediateTask(CreateTask(&mock_task_a));
 
-  // WillPushTask is called when a new task is about to be pushed and sequence
-  // will be put in the priority queue or is already in it.
+  // ShouldBeQueued()is called when a new task is about to be
+  // pushed and sequence will be put in the priority queue or is already in it.
   EXPECT_EQ(sequence->GetCurrentLocationForTesting(),
             Sequence::SequenceLocation::kImmediateQueue);
 
-  // Push task B into the sequence. WillPushTask() should return false.
-  EXPECT_FALSE(sequence_transaction.WillPushTask());
-  sequence_transaction.PushTask(CreateTask(&mock_task_b));
+  // Push task B into the sequence. ShouldBeQueued() should
+  // return false.
+  EXPECT_FALSE(sequence_transaction.ShouldBeQueued());
+  sequence_transaction.PushImmediateTask(CreateTask(&mock_task_b));
 
-  // WillPushTask is called when a new task is about to be pushed and sequence
-  // will be put in the priority queue or is already in it. Sequence location
-  // should be kImmediateQueue.
+  // ShouldBeQueued()is called when a new task is about to be
+  // pushed and sequence will be put in the priority queue or is already in it.
+  // Sequence location should be kImmediateQueue.
   EXPECT_EQ(sequence->GetCurrentLocationForTesting(),
             Sequence::SequenceLocation::kImmediateQueue);
 
@@ -286,6 +303,9 @@
 
   // Remove the empty slot. Sequence still has task B. This should return true.
   EXPECT_TRUE(registered_task_source.DidProcessTask(&sequence_transaction));
+  // Sequence can run immediately.
+  EXPECT_TRUE(registered_task_source.WillReEnqueue(TimeTicks::Now(),
+                                                   &sequence_transaction));
 
   // Sequence is not empty so it will be returned to the priority queue and its
   // location should be updated to kImmediateQueue.
@@ -323,10 +343,10 @@
 
   Sequence::Transaction sequence_transaction(sequence->BeginTransaction());
 
-  // Push task A in the sequence. WillPushTask() should return true
-  // since sequence is empty.
-  EXPECT_TRUE(sequence_transaction.WillPushTask());
-  sequence_transaction.PushTask(CreateTask(&mock_task_a));
+  // Push task A in the sequence. ShouldBeQueued() should return
+  // true since sequence is empty.
+  EXPECT_TRUE(sequence_transaction.ShouldBeQueued());
+  sequence_transaction.PushImmediateTask(CreateTask(&mock_task_a));
 
   auto registered_task_source =
       RegisteredTaskSource::CreateForTesting(sequence);
@@ -343,9 +363,10 @@
   EXPECT_EQ(sequence->GetCurrentLocationForTesting(),
             Sequence::SequenceLocation::kInWorker);
 
-  // Push task B into the sequence. WillPushTask() should return false.
-  EXPECT_FALSE(sequence_transaction.WillPushTask());
-  sequence_transaction.PushTask(CreateTask(&mock_task_b));
+  // Push task B into the sequence. ShouldBeQueued() should
+  // return false.
+  EXPECT_FALSE(sequence_transaction.ShouldBeQueued());
+  sequence_transaction.PushImmediateTask(CreateTask(&mock_task_b));
 
   // Sequence is still being processed by a worker so pushing a new task
   // shouldn't change its location. We should expect it to still be in worker.
@@ -354,6 +375,9 @@
 
   // Remove the empty slot. Sequence still has task B. This should return true.
   EXPECT_TRUE(registered_task_source.DidProcessTask(&sequence_transaction));
+  // Sequence can run immediately.
+  EXPECT_TRUE(registered_task_source.WillReEnqueue(TimeTicks::Now(),
+                                                   &sequence_transaction));
 
   // Sequence is not empty so it will be returned to the priority queue and its
   // location should be updated to kImmediateQueue.
@@ -375,5 +399,345 @@
             Sequence::SequenceLocation::kNone);
 }
 
+// Verify that the sequence handle delayed tasks and sets locations
+// appropriately
+TEST(ThreadPoolSequenceTest, PushTakeRemoveDelayedTasks) {
+  TimeTicks now = TimeTicks::Now();
+
+  testing::StrictMock<MockTask> mock_task_a;
+  testing::StrictMock<MockTask> mock_task_b;
+  testing::StrictMock<MockTask> mock_task_c;
+  testing::StrictMock<MockTask> mock_task_d;
+
+  scoped_refptr<Sequence> sequence =
+      MakeRefCounted<Sequence>(TaskTraits(TaskPriority::BEST_EFFORT), nullptr,
+                               TaskSourceExecutionMode::kParallel);
+
+  Sequence::Transaction sequence_transaction(sequence->BeginTransaction());
+
+  // Push task A in the sequence.
+  auto delayed_task_a = CreateDelayedTask(&mock_task_a, Milliseconds(20), now);
+  // TopDelayedTaskWillChange(delayed_task_a) should return
+  // true since sequence is empty.
+  EXPECT_TRUE(sequence_transaction.TopDelayedTaskWillChange(delayed_task_a));
+  // ShouldBeQueued() should return true since sequence is empty.
+  EXPECT_TRUE(sequence_transaction.ShouldBeQueued());
+  // PushImmediateTask(...) should be used for immediate tasks only
+  // EXPECT_DCHECK_DEATH({
+  //   sequence_transaction.PushImmediateTask(delayed_task_a);
+  // });
+  sequence_transaction.PushDelayedTask(std::move(delayed_task_a));
+
+  // Sequence doesn't have immediate tasks so its location should be the delayed
+  // queue.
+  EXPECT_EQ(sequence->GetCurrentLocationForTesting(),
+            Sequence::SequenceLocation::kDelayedQueue);
+
+  // Push task B into the sequence.
+  auto delayed_task_b = CreateDelayedTask(&mock_task_b, Milliseconds(10), now);
+  // TopDelayedTaskWillChange(...) should return true since task b runtime is
+  // earlier than task a's.
+  EXPECT_TRUE(sequence_transaction.TopDelayedTaskWillChange(delayed_task_b));
+  // ShouldBeQueued() should return true since task B is earlier
+  // than task A.
+  EXPECT_TRUE(sequence_transaction.ShouldBeQueued());
+  sequence_transaction.PushDelayedTask(std::move(delayed_task_b));
+
+  // Sequence doesn't have immediate tasks so its location should be the delayed
+  // queue.
+  EXPECT_EQ(sequence->GetCurrentLocationForTesting(),
+            Sequence::SequenceLocation::kDelayedQueue);
+
+  // Time advances by 15s.
+  now += Milliseconds(15);
+
+  // Set sequence to ready
+  sequence->OnBecomeReady();
+
+  // Sequence is about to be run so its location should change to immediate
+  // queue.
+  EXPECT_EQ(sequence->GetCurrentLocationForTesting(),
+            Sequence::SequenceLocation::kImmediateQueue);
+
+  auto registered_task_source =
+      RegisteredTaskSource::CreateForTesting(sequence);
+  registered_task_source.WillRunTask();
+
+  // WillRunTask() has been called so sequence location should be kInWorker.
+  EXPECT_EQ(sequence->GetCurrentLocationForTesting(),
+            Sequence::SequenceLocation::kInWorker);
+
+  // Take the task in front of the sequence. It should be task B.
+  absl::optional<Task> task =
+      registered_task_source.TakeTask(&sequence_transaction);
+  ExpectMockTask(&mock_task_b, &task.value());
+  EXPECT_FALSE(task->queue_time.is_null());
+
+  // Remove the empty slot. Task A should now be in front. Sequence is not empty
+  // so this should return true.
+  EXPECT_TRUE(registered_task_source.DidProcessTask(&sequence_transaction));
+
+  // Task A is still not ready so this should return false and location
+  // should be set to delayed queue
+  EXPECT_FALSE(
+      registered_task_source.WillReEnqueue(now, &sequence_transaction));
+  EXPECT_EQ(sequence->GetCurrentLocationForTesting(),
+            Sequence::SequenceLocation::kDelayedQueue);
+
+  // Push task C into the sequence.
+  auto delayed_task_c = CreateDelayedTask(&mock_task_c, Milliseconds(1), now);
+  // TopDelayedTaskWillChange(...) should return true since task c runtime is
+  // earlier than task a's.
+  EXPECT_TRUE(sequence_transaction.TopDelayedTaskWillChange(delayed_task_c));
+  // ShouldBeQueued() should return true since task C is earlier
+  // than task A.
+  EXPECT_TRUE(sequence_transaction.ShouldBeQueued());
+  sequence_transaction.PushDelayedTask(std::move(delayed_task_c));
+
+  // Push task D into the sequence.
+  auto delayed_task_d = CreateDelayedTask(&mock_task_d, Milliseconds(1), now);
+  // TopDelayedTaskWillChange(...) should return false since task d queue time
+  // is later than task c's.
+  EXPECT_FALSE(sequence_transaction.TopDelayedTaskWillChange(delayed_task_d));
+  sequence_transaction.PushDelayedTask(std::move(delayed_task_d));
+
+  // Time advances by 2ms.
+  now += Milliseconds(2);
+  // Set sequence to ready
+  registered_task_source.OnBecomeReady();
+
+  registered_task_source.WillRunTask();
+
+  // This should return task C
+  task = registered_task_source.TakeTask(&sequence_transaction);
+  ExpectMockTask(&mock_task_c, &task.value());
+  EXPECT_FALSE(task->queue_time.is_null());
+
+  // Remove the empty slot. Task D should now be in front.
+  EXPECT_TRUE(registered_task_source.DidProcessTask(&sequence_transaction));
+
+  // Task D is ready so this should return true and location
+  // should be set to immediate queue
+  EXPECT_TRUE(registered_task_source.WillReEnqueue(now, &sequence_transaction));
+  EXPECT_EQ(sequence->GetCurrentLocationForTesting(),
+            Sequence::SequenceLocation::kImmediateQueue);
+
+  registered_task_source.WillRunTask();
+
+  // This should return task D
+  task = registered_task_source.TakeTask(&sequence_transaction);
+  ExpectMockTask(&mock_task_d, &task.value());
+  EXPECT_FALSE(task->queue_time.is_null());
+
+  // Remove the empty slot. Task A should now be in front.
+  EXPECT_TRUE(registered_task_source.DidProcessTask(&sequence_transaction));
+
+  // Time advances by 10ms.
+  now += Milliseconds(10);
+
+  // Task A is ready so this should return true and location
+  // should be set to immediate queue
+  EXPECT_TRUE(registered_task_source.WillReEnqueue(now, &sequence_transaction));
+  EXPECT_EQ(sequence->GetCurrentLocationForTesting(),
+            Sequence::SequenceLocation::kImmediateQueue);
+
+  registered_task_source.WillRunTask();
+
+  // This should return task A since it's ripe
+  task = registered_task_source.TakeTask(&sequence_transaction);
+  ExpectMockTask(&mock_task_a, &task.value());
+  EXPECT_FALSE(task->queue_time.is_null());
+
+  // Remove the empty slot. Sequence should be empty now.
+  EXPECT_FALSE(registered_task_source.DidProcessTask(&sequence_transaction));
+  EXPECT_EQ(sequence->GetCurrentLocationForTesting(),
+            Sequence::SequenceLocation::kNone);
+
+  // Sequence is empty so there should be no task to execute.
+  // This should return true
+  EXPECT_TRUE(sequence_transaction.ShouldBeQueued());
+}
+
+// Verify that the sequence handle delayed and immediate tasks and sets
+// locations appropriately
+TEST(ThreadPoolSequenceTest, PushTakeRemoveMixedTasks) {
+  TimeTicks now = TimeTicks::Now();
+
+  testing::StrictMock<MockTask> mock_task_a;
+  testing::StrictMock<MockTask> mock_task_b;
+  testing::StrictMock<MockTask> mock_task_c;
+  testing::StrictMock<MockTask> mock_task_d;
+
+  scoped_refptr<Sequence> sequence =
+      MakeRefCounted<Sequence>(TaskTraits(TaskPriority::BEST_EFFORT), nullptr,
+                               TaskSourceExecutionMode::kParallel);
+
+  Sequence::Transaction sequence_transaction(sequence->BeginTransaction());
+
+  // Starting with a delayed task
+  // Push task A in the sequence.
+  auto delayed_task_a = CreateDelayedTask(&mock_task_a, Milliseconds(20), now);
+  // TopDelayedTaskWillChange(delayed_task_a) should return
+  // true since sequence is empty.
+  EXPECT_TRUE(sequence_transaction.TopDelayedTaskWillChange(delayed_task_a));
+  // ShouldBeQueued() should return true since sequence is empty.
+  EXPECT_TRUE(sequence_transaction.ShouldBeQueued());
+  sequence_transaction.PushDelayedTask(std::move(delayed_task_a));
+
+  // Sequence doesn't have immediate tasks so its location should be the delayed
+  // queue.
+  EXPECT_EQ(sequence->GetCurrentLocationForTesting(),
+            Sequence::SequenceLocation::kDelayedQueue);
+
+  // Push an immediate task while a delayed task is already sitting in the
+  // delayed queue. This should prompt a move to the immediate queue.
+  // Push task B in the sequence.
+  auto task_b = CreateTask(&mock_task_b, now);
+  // ShouldBeQueued() should return true since sequence is in delayed queue.
+  EXPECT_TRUE(sequence_transaction.ShouldBeQueued());
+  sequence_transaction.PushImmediateTask(std::move(task_b));
+  // Sequence doesn't have immediate tasks so its location should will change
+  // to immediate queue.
+  EXPECT_EQ(sequence->GetCurrentLocationForTesting(),
+            Sequence::SequenceLocation::kImmediateQueue);
+
+  auto registered_task_source =
+      RegisteredTaskSource::CreateForTesting(sequence);
+
+  // Prepare to run a task.
+  registered_task_source.WillRunTask();
+
+  // WillRunTask() has been called so sequence location should be kInWorker.
+  EXPECT_EQ(sequence->GetCurrentLocationForTesting(),
+            Sequence::SequenceLocation::kInWorker);
+
+  // Take the task in front of the sequence. It should be task B.
+  absl::optional<Task> task =
+      registered_task_source.TakeTask(&sequence_transaction);
+  ExpectMockTask(&mock_task_b, &task.value());
+  EXPECT_FALSE(task->queue_time.is_null());
+
+  // Remove the empty slot. Task A should now be in front. Sequence is not empty
+  // so this should return true.
+  EXPECT_TRUE(registered_task_source.DidProcessTask(&sequence_transaction));
+
+  // Time advances by 21ms.
+  now += Milliseconds(21);
+
+  // Task A is ready so this should return true and location
+  // should be set to immediate queue.
+  EXPECT_TRUE(registered_task_source.WillReEnqueue(now, &sequence_transaction));
+  EXPECT_EQ(sequence->GetCurrentLocationForTesting(),
+            Sequence::SequenceLocation::kImmediateQueue);
+
+  registered_task_source.WillRunTask();
+
+  // WillRunTask() has been called so sequence location should be kInWorker.
+  EXPECT_EQ(sequence->GetCurrentLocationForTesting(),
+            Sequence::SequenceLocation::kInWorker);
+
+  // Push a delayed task while sequence is being run by a worker.
+  // Push task C in the sequence.
+  auto delayed_task_c = CreateDelayedTask(&mock_task_c, Milliseconds(5), now);
+  // TopDelayedTaskWillChange(delayed_task_c) should return
+  // false since task A is ripe and earlier than task C.
+  EXPECT_FALSE(sequence_transaction.TopDelayedTaskWillChange(delayed_task_c));
+  // ShouldBeQueued() should return false since sequence is in worker.
+  EXPECT_FALSE(sequence_transaction.ShouldBeQueued());
+  sequence_transaction.PushDelayedTask(std::move(delayed_task_c));
+
+  // Sequence is in worker.
+  EXPECT_EQ(sequence->GetCurrentLocationForTesting(),
+            Sequence::SequenceLocation::kInWorker);
+
+  // This should return task A
+  task = registered_task_source.TakeTask(&sequence_transaction);
+  ExpectMockTask(&mock_task_a, &task.value());
+  EXPECT_FALSE(task->queue_time.is_null());
+
+  // Remove the empty slot. Task C should now be in front.
+  EXPECT_TRUE(registered_task_source.DidProcessTask(&sequence_transaction));
+
+  // Time advances by 2ms.
+  now += Milliseconds(2);
+
+  // Task C is not ready so this should return false and location should be set
+  // to delayed queue.
+  EXPECT_FALSE(
+      registered_task_source.WillReEnqueue(now, &sequence_transaction));
+  EXPECT_EQ(sequence->GetCurrentLocationForTesting(),
+            Sequence::SequenceLocation::kDelayedQueue);
+
+  // Time advances by 4ms. Task C becomes ready.
+  now += Milliseconds(4);
+
+  // Set sequence to ready
+  registered_task_source.OnBecomeReady();
+  EXPECT_EQ(sequence->GetCurrentLocationForTesting(),
+            Sequence::SequenceLocation::kImmediateQueue);
+
+  // Push task D in the sequence while sequence is ready.
+  auto task_d = CreateTask(&mock_task_d, now);
+  // ShouldBeQueued() should return false since sequence is already in immediate
+  // queue.
+  EXPECT_FALSE(sequence_transaction.ShouldBeQueued());
+  sequence_transaction.PushImmediateTask(std::move(task_d));
+
+  // Sequence should be in immediate queue.
+  EXPECT_EQ(sequence->GetCurrentLocationForTesting(),
+            Sequence::SequenceLocation::kImmediateQueue);
+
+  registered_task_source.WillRunTask();
+
+  // WillRunTask() has been called so sequence location should be kInWorker.
+  EXPECT_EQ(sequence->GetCurrentLocationForTesting(),
+            Sequence::SequenceLocation::kInWorker);
+
+  // This should return task C since was ready before Task D was posted.
+  task = registered_task_source.TakeTask(&sequence_transaction);
+  ExpectMockTask(&mock_task_c, &task.value());
+  EXPECT_FALSE(task->queue_time.is_null());
+
+  // Remove the empty slot. Task D should now be in front.
+  EXPECT_TRUE(registered_task_source.DidProcessTask(&sequence_transaction));
+
+  // Task D should be run so this should return true and location should be set
+  // to immediate queue.
+  EXPECT_TRUE(registered_task_source.WillReEnqueue(now, &sequence_transaction));
+  EXPECT_EQ(sequence->GetCurrentLocationForTesting(),
+            Sequence::SequenceLocation::kImmediateQueue);
+
+  registered_task_source.WillRunTask();
+
+  // This should return task D since it's immediate.
+  task = registered_task_source.TakeTask(&sequence_transaction);
+  ExpectMockTask(&mock_task_d, &task.value());
+  EXPECT_FALSE(task->queue_time.is_null());
+
+  // Remove the empty slot. Sequence should be empty.
+  EXPECT_FALSE(registered_task_source.DidProcessTask(&sequence_transaction));
+  EXPECT_EQ(sequence->GetCurrentLocationForTesting(),
+            Sequence::SequenceLocation::kNone);
+}
+
+// Test that PushDelayedTask method is used only for delayed tasks
+TEST(ThreadPoolSequenceTest, TestPushDelayedTaskMethodUsage) {
+  testing::StrictMock<MockTask> mock_task_a;
+
+  scoped_refptr<Sequence> sequence =
+      MakeRefCounted<Sequence>(TaskTraits(TaskPriority::BEST_EFFORT), nullptr,
+                               TaskSourceExecutionMode::kParallel);
+
+  Sequence::Transaction sequence_transaction(sequence->BeginTransaction());
+
+  // Push task B in the sequence.
+  auto task_a = CreateTask(&mock_task_a);
+  // ShouldBeQueued() should return true since sequence is empty.
+  EXPECT_TRUE(sequence_transaction.ShouldBeQueued());
+  // PushDelayedTask(...) should be used for delayed tasks only.
+  EXPECT_DCHECK_DEATH(
+      { sequence_transaction.PushDelayedTask(std::move(task_a)); });
+}
+
 }  // namespace internal
 }  // namespace base
diff --git a/base/task/thread_pool/task.h b/base/task/thread_pool/task.h
index 0920049..0489e27 100644
--- a/base/task/thread_pool/task.h
+++ b/base/task/thread_pool/task.h
@@ -50,8 +50,6 @@
 
   Task& operator=(Task&& other);
 
-  void SetScheduled();
-
   // Required by IntrusiveHeap.
   void SetHeapHandle(const HeapHandle& handle) {}
 
diff --git a/base/task/thread_pool/task_source.cc b/base/task/thread_pool/task_source.cc
index bdc49ad..41dd50a 100644
--- a/base/task/thread_pool/task_source.cc
+++ b/base/task/thread_pool/task_source.cc
@@ -110,6 +110,13 @@
   return *this;
 }
 
+void RegisteredTaskSource::OnBecomeReady() {
+#if DCHECK_IS_ON()
+  DCHECK_EQ(run_step_, State::kInitial);
+#endif  // DCHECK_IS_ON()
+  task_source_->OnBecomeReady();
+}
+
 TaskSource::RunStatus RegisteredTaskSource::WillRunTask() {
   TaskSource::RunStatus run_status = task_source_->WillRunTask();
 #if DCHECK_IS_ON()
@@ -143,6 +150,15 @@
   return task_source_->DidProcessTask(transaction);
 }
 
+bool RegisteredTaskSource::WillReEnqueue(TimeTicks now,
+                                         TaskSource::Transaction* transaction) {
+  DCHECK(!transaction || transaction->task_source() == get());
+#if DCHECK_IS_ON()
+  DCHECK_EQ(State::kInitial, run_step_);
+#endif  // DCHECK_IS_ON()
+  return task_source_->WillReEnqueue(now, transaction);
+}
+
 RegisteredTaskSource::RegisteredTaskSource(
     scoped_refptr<TaskSource> task_source,
     TaskTracker* task_tracker)
diff --git a/base/task/thread_pool/task_source.h b/base/task/thread_pool/task_source.h
index a5f653ec..d811365 100644
--- a/base/task/thread_pool/task_source.h
+++ b/base/task/thread_pool/task_source.h
@@ -18,6 +18,7 @@
 #include "base/task/thread_pool/task.h"
 #include "base/task/thread_pool/task_source_sort_key.h"
 #include "base/threading/sequence_local_storage_map.h"
+#include "base/time/time.h"
 
 namespace base {
 namespace internal {
@@ -45,8 +46,8 @@
 // 1- It has new tasks that can run concurrently as a result of external
 //    operations, e.g. posting a new task to an empty Sequence or increasing
 //    max concurrency of a JobTaskSource;
-// 2- A worker finished running a task from it and DidProcessTask() returned
-//    true; or
+// 2- A worker finished running a task from it and both DidProcessTask() and
+//    WillReEnqueue() returned true; or
 // 3- A worker is about to run a task from it and WillRunTask() returned
 //    kAllowedNotSaturated.
 //
@@ -58,7 +59,8 @@
 //    with TakeTask().
 // 3- (optional) Execute the task.
 // 4- Inform the task source that a task was processed with DidProcessTask(),
-//    and re-enqueue the task source iff requested.
+//    and re-enqueue the task source iff requested. The task source is ready to
+//    run immediately iff WillReEnqueue() returns true.
 // When a task source is registered multiple times, many overlapping chains of
 // operations may run concurrently, as permitted by WillRunTask(). This allows
 // tasks from the same task source to run in parallel.
@@ -169,10 +171,10 @@
   // Transaction because it is never mutated.
   ThreadPolicy thread_policy() const { return traits_.thread_policy(); }
 
-  // A reference to TaskRunner is only retained between PushTask() and when
-  // DidProcessTask() returns false, guaranteeing it is safe to dereference this
-  // pointer. Otherwise, the caller should guarantee such TaskRunner still
-  // exists before dereferencing.
+  // A reference to TaskRunner is only retained between
+  // PushImmediateTask()/PushDelayedTask() and when DidProcessTask() returns
+  // false, guaranteeing it is safe to dereference this pointer. Otherwise, the
+  // caller should guarantee such TaskRunner still exists before dereferencing.
   TaskRunner* task_runner() const { return task_runner_; }
 
   TaskSourceExecutionMode execution_mode() const { return execution_mode_; }
@@ -182,10 +184,12 @@
 
   virtual RunStatus WillRunTask() = 0;
 
-  // Implementations of TakeTask(), DidProcessTask() and Clear() must ensure
-  // proper synchronization iff |transaction| is nullptr.
+  // Implementations of TakeTask(), DidProcessTask(), WillReEnqueue(), and
+  // Clear() must ensure proper synchronization iff |transaction| is nullptr.
   virtual Task TakeTask(TaskSource::Transaction* transaction) = 0;
   virtual bool DidProcessTask(TaskSource::Transaction* transaction) = 0;
+  virtual bool WillReEnqueue(TimeTicks now,
+                             TaskSource::Transaction* transaction) = 0;
 
   // This may be called for each outstanding RegisteredTaskSource that's ready.
   // The implementation needs to support this being called multiple times;
@@ -206,6 +210,7 @@
   mutable CheckedLock lock_{UniversalPredecessor()};
 
  private:
+  virtual void OnBecomeReady() = 0;
   friend class RefCountedThreadSafe<TaskSource>;
   friend class RegisteredTaskSource;
 
@@ -230,7 +235,7 @@
 // used by a single worker at a time. However, the same task source may be
 // registered several times, spawning multiple RegisteredTaskSources. A
 // RegisteredTaskSource resets to its initial state when WillRunTask() fails
-// or after DidProcessTask(), so it can be used again.
+// or after DidProcessTask() and WillReEnqueue(), so it can be used again.
 class BASE_EXPORT RegisteredTaskSource {
  public:
   RegisteredTaskSource();
@@ -261,6 +266,10 @@
   // that indicates if the operation is allowed (TakeTask() can be called).
   TaskSource::RunStatus WillRunTask();
 
+  // Informs this TaskSource that it has become ready to run and is being moved
+  // from delayed to immediate queue.
+  void OnBecomeReady();
+
   // Returns the next task to run from this TaskSource. This should be called
   // only after WillRunTask() returned RunStatus::kAllowed*. |transaction| is
   // optional and should only be provided if this operation is already part of
@@ -274,6 +283,13 @@
   // Returns true if the TaskSource should be queued after this operation.
   bool DidProcessTask(TaskSource::Transaction* transaction = nullptr);
 
+  // Must be called iff DidProcessTask() previously returns true .
+  // |transaction| is optional and should only be provided if this
+  // operation is already part of a transaction. Returns true if the
+  // TaskSource is ready to run immediately.
+  bool WillReEnqueue(TimeTicks now,
+                     TaskSource::Transaction* transaction = nullptr);
+
   // Returns a task that clears this TaskSource to make it empty. |transaction|
   // is optional and should only be provided if this operation is already part
   // of a transaction.
diff --git a/base/task/thread_pool/task_tracker_unittest.cc b/base/task/thread_pool/task_tracker_unittest.cc
index cde9825..9b14bb6e 100644
--- a/base/task/thread_pool/task_tracker_unittest.cc
+++ b/base/task/thread_pool/task_tracker_unittest.cc
@@ -122,7 +122,7 @@
 
       post_and_queue_succeeded =
           tracker_->WillPostTask(&task_, sequence_->shutdown_behavior());
-      sequence_->BeginTransaction().PushTask(std::move(task_));
+      sequence_->BeginTransaction().PushImmediateTask(std::move(task_));
       task_source_ = tracker_->RegisterTaskSource(std::move(sequence_));
 
       post_and_queue_succeeded &= !!task_source_;
@@ -958,7 +958,7 @@
 
   {
     Sequence::Transaction sequence_transaction(sequence->BeginTransaction());
-    sequence_transaction.PushTask(std::move(task));
+    sequence_transaction.PushImmediateTask(std::move(task));
 
     EXPECT_FALSE(SequenceToken::GetForCurrentThread().IsValid());
   }
@@ -1158,7 +1158,7 @@
 
   scoped_refptr<Sequence> sequence =
       test::CreateSequenceWithTask(std::move(task_1), default_traits);
-  sequence->BeginTransaction().PushTask(std::move(task_2));
+  sequence->BeginTransaction().PushImmediateTask(std::move(task_2));
   EXPECT_EQ(sequence,
             test::QueueAndRunTaskSource(&tracker_, sequence).Unregister());
 }
diff --git a/base/task/thread_pool/test_utils.cc b/base/task/thread_pool/test_utils.cc
index 308cc94..ea75cec 100644
--- a/base/task/thread_pool/test_utils.cc
+++ b/base/task/thread_pool/test_utils.cc
@@ -107,7 +107,7 @@
     TaskSourceExecutionMode execution_mode) {
   scoped_refptr<Sequence> sequence =
       MakeRefCounted<Sequence>(traits, task_runner.get(), execution_mode);
-  sequence->BeginTransaction().PushTask(std::move(task));
+  sequence->BeginTransaction().PushImmediateTask(std::move(task));
   return sequence;
 }
 
@@ -190,7 +190,7 @@
     Task task,
     scoped_refptr<Sequence> sequence) {
   auto transaction = sequence->BeginTransaction();
-  const bool sequence_should_be_queued = transaction.WillPushTask();
+  const bool sequence_should_be_queued = transaction.ShouldBeQueued();
   RegisteredTaskSource task_source;
   if (sequence_should_be_queued) {
     task_source = task_tracker_->RegisterTaskSource(std::move(sequence));
@@ -198,7 +198,7 @@
     if (!task_source)
       return;
   }
-  transaction.PushTask(std::move(task));
+  transaction.PushImmediateTask(std::move(task));
   if (task_source) {
     thread_group_->PushTaskSourceAndWakeUpWorkers(
         {std::move(task_source), std::move(transaction)});
diff --git a/base/task/thread_pool/thread_group.cc b/base/task/thread_pool/thread_group.cc
index 11dd18a..a16db32 100644
--- a/base/task/thread_pool/thread_group.cc
+++ b/base/task/thread_pool/thread_group.cc
@@ -162,6 +162,10 @@
   ThreadGroup* destination_thread_group = delegate_->GetThreadGroupForTraits(
       transaction_with_task_source.transaction.traits());
 
+  bool push_to_immediate_queue =
+      transaction_with_task_source.task_source.WillReEnqueue(
+          TimeTicks::Now(), &transaction_with_task_source.transaction);
+
   if (destination_thread_group == this) {
     // Another worker that was running a task from this task source may have
     // reenqueued it already, in which case its heap_handle will be valid. It
@@ -174,8 +178,10 @@
       // reenqueue it inside the scope of the lock.
       auto sort_key = transaction_with_task_source.task_source->GetSortKey(
           disable_fair_scheduling_);
-      priority_queue_.Push(std::move(transaction_with_task_source.task_source),
-                           sort_key);
+      if (push_to_immediate_queue) {
+        priority_queue_.Push(
+            std::move(transaction_with_task_source.task_source), sort_key);
+      }
     }
     // This is called unconditionally to ensure there are always workers to run
     // task sources in the queue. Some ThreadGroup implementations only invoke
diff --git a/base/task/thread_pool/thread_pool_impl.cc b/base/task/thread_pool/thread_pool_impl.cc
index 14abd275..205ed148 100644
--- a/base/task/thread_pool/thread_pool_impl.cc
+++ b/base/task/thread_pool/thread_pool_impl.cc
@@ -410,7 +410,7 @@
 bool ThreadPoolImpl::PostTaskWithSequenceNow(Task task,
                                              scoped_refptr<Sequence> sequence) {
   auto transaction = sequence->BeginTransaction();
-  const bool sequence_should_be_queued = transaction.WillPushTask();
+  const bool sequence_should_be_queued = transaction.ShouldBeQueued();
   RegisteredTaskSource task_source;
   if (sequence_should_be_queued) {
     task_source = task_tracker_->RegisterTaskSource(sequence);
@@ -420,7 +420,7 @@
   }
   if (!task_tracker_->WillPostTaskNow(task, transaction.traits().priority()))
     return false;
-  transaction.PushTask(std::move(task));
+  transaction.PushImmediateTask(std::move(task));
   if (task_source) {
     const TaskTraits traits = transaction.traits();
     GetThreadGroupForTraits(traits)->PushTaskSourceAndWakeUpWorkers(
diff --git a/base/task/thread_pool/worker_thread_unittest.cc b/base/task/thread_pool/worker_thread_unittest.cc
index 156c735..1e31dda 100644
--- a/base/task/thread_pool/worker_thread_unittest.cc
+++ b/base/task/thread_pool/worker_thread_unittest.cc
@@ -203,7 +203,7 @@
                   TimeTicks::Now(), TimeDelta());
         EXPECT_TRUE(outer_->task_tracker_.WillPostTask(
             &task, sequence->shutdown_behavior()));
-        sequence_transaction.PushTask(std::move(task));
+        sequence_transaction.PushImmediateTask(std::move(task));
       }
       auto registered_task_source =
           outer_->task_tracker_.RegisterTaskSource(sequence);
@@ -239,15 +239,19 @@
         EXPECT_FALSE(registered_task_source);
       } else {
         EXPECT_TRUE(registered_task_source);
+        EXPECT_TRUE(registered_task_source.WillReEnqueue(TimeTicks::Now()));
 
         // Verify the number of Tasks in |registered_task_source|.
         for (int i = 0; i < outer_->TasksPerSequence() - 1; ++i) {
           registered_task_source.WillRunTask();
           IgnoreResult(registered_task_source.TakeTask());
-          EXPECT_EQ(i == outer_->TasksPerSequence() - 2,
-                    !registered_task_source.DidProcessTask());
+          if (i < outer_->TasksPerSequence() - 2) {
+            EXPECT_TRUE(registered_task_source.DidProcessTask());
+            EXPECT_TRUE(registered_task_source.WillReEnqueue(TimeTicks::Now()));
+          } else {
+            EXPECT_FALSE(registered_task_source.DidProcessTask());
+          }
         }
-
         scoped_refptr<TaskSource> task_source =
             registered_task_source.Unregister();
         {
@@ -485,7 +489,7 @@
               TimeTicks::Now(), TimeDelta());
     EXPECT_TRUE(
         task_tracker_->WillPostTask(&task, sequence->shutdown_behavior()));
-    sequence->BeginTransaction().PushTask(std::move(task));
+    sequence->BeginTransaction().PushImmediateTask(std::move(task));
     auto registered_task_source =
         task_tracker_->RegisterTaskSource(std::move(sequence));
     EXPECT_TRUE(registered_task_source);
diff --git a/base/tracing/protos/chrome_track_event.proto b/base/tracing/protos/chrome_track_event.proto
index 344cd8f..58007f42 100644
--- a/base/tracing/protos/chrome_track_event.proto
+++ b/base/tracing/protos/chrome_track_event.proto
@@ -747,6 +747,7 @@
 
   optional EventType event_type = 1;
   optional bool has_high_latency = 2;
+  repeated string high_latency_stage = 3;
 }
 
 message ProcessSingleton {
diff --git a/build/fuchsia/linux_internal.sdk.sha1 b/build/fuchsia/linux_internal.sdk.sha1
index 187a554..dd7881b 100644
--- a/build/fuchsia/linux_internal.sdk.sha1
+++ b/build/fuchsia/linux_internal.sdk.sha1
@@ -1 +1 @@
-9.20220809.0.1
+9.20220809.1.1
diff --git a/cc/metrics/compositor_frame_reporter.cc b/cc/metrics/compositor_frame_reporter.cc
index 8293311..3dfe719 100644
--- a/cc/metrics/compositor_frame_reporter.cc
+++ b/cc/metrics/compositor_frame_reporter.cc
@@ -94,8 +94,9 @@
 constexpr int kEventLatencyHistogramBucketCount = 100;
 constexpr base::TimeDelta kHighLatencyMin = base::Milliseconds(75);
 
-// Number of stages of the current PipelineReporter
-constexpr int kNumOfStages = static_cast<int>(StageType::kStageTypeCount);
+// Number of breakdown stages of the current PipelineReporter
+constexpr int kNumOfStages = static_cast<int>(StageType::kStageTypeCount) - 1;
+
 // Number of dispatch stages of the current EventLatency
 constexpr int kNumDispatchStages =
     static_cast<int>(EventMetrics::DispatchStage::kMaxValue);
@@ -104,6 +105,8 @@
 // since TimeDelta calculate based on microseconds instead of nanoseconds,
 // therefore, decimals of stage durations in microseconds may be lost.)
 constexpr double kWeightOfCurStageInPercent = 25;
+// Used for comparing doubles
+constexpr double kEpsilon = 0.001;
 
 std::string GetCompositorLatencyHistogramName(
     FrameReportType report_type,
@@ -128,12 +131,11 @@
     const EventMetrics& event_metrics) {
   auto* scroll_metrics = event_metrics.AsScroll();
   auto* pinch_metrics = event_metrics.AsPinch();
-  return base::StrCat(
-      {"EventLatency.", event_metrics.GetTypeName(),
-       scroll_metrics || pinch_metrics ? "." : "",
-       scroll_metrics
-           ? scroll_metrics->GetScrollTypeName()
-           : pinch_metrics ? pinch_metrics->GetPinchTypeName() : ""});
+  return base::StrCat({"EventLatency.", event_metrics.GetTypeName(),
+                       scroll_metrics || pinch_metrics ? "." : "",
+                       scroll_metrics  ? scroll_metrics->GetScrollTypeName()
+                       : pinch_metrics ? pinch_metrics->GetPinchTypeName()
+                                       : ""});
 }
 
 constexpr char kTraceCategory[] =
@@ -154,6 +156,29 @@
          100;
 }
 
+// Calculate new prediction of latency based on old prediction and current
+// latency
+base::TimeDelta PredictLatency(base::TimeDelta previous_prediction,
+                               base::TimeDelta current_latency) {
+  return (kWeightOfCurStageInPercent * current_latency +
+          (100 - kWeightOfCurStageInPercent) * previous_prediction) /
+         100;
+}
+
+double DetermineHighestContribution(
+    double contribution_change,
+    double highest_contribution_change,
+    const std::string& stage_name,
+    std::vector<std::string>& high_latency_stages) {
+  if (std::abs(contribution_change - highest_contribution_change) < kEpsilon) {
+    high_latency_stages.push_back(stage_name);
+  } else if (contribution_change > highest_contribution_change) {
+    highest_contribution_change = contribution_change;
+    high_latency_stages = {stage_name};
+  }
+  return highest_contribution_change;
+}
+
 }  // namespace
 
 // CompositorFrameReporter::ProcessedBlinkBreakdown::Iterator ==================
@@ -341,6 +366,16 @@
                             buffer_ready_available_);
 }
 
+// CompositorFrameReporter::CompositorLatencyInfo ==============================
+
+CompositorFrameReporter::CompositorLatencyInfo::CompositorLatencyInfo() =
+    default;
+CompositorFrameReporter::CompositorLatencyInfo::CompositorLatencyInfo(
+    base::TimeDelta init_value)
+    : top_level_stages(kNumOfStages, init_value), total_latency(init_value) {}
+CompositorFrameReporter::CompositorLatencyInfo::~CompositorLatencyInfo() =
+    default;
+
 // CompositorFrameReporter =====================================================
 
 CompositorFrameReporter::CompositorFrameReporter(
@@ -560,7 +595,8 @@
     : dispatch_durations(num_dispatch_stages, base::Microseconds(-1)),
       transition_duration(base::Microseconds(-1)),
       compositor_durations(num_compositor_stages, base::Microseconds(-1)),
-      total_duration(base::Microseconds(-1)) {}
+      total_duration(base::Microseconds(-1)),
+      transition_name("") {}
 CompositorFrameReporter::EventLatencyInfo::~EventLatencyInfo() = default;
 
 void CompositorFrameReporter::StartStage(
@@ -925,9 +961,9 @@
     const int stage_type_index =
         blink_breakdown
             ? kBlinkBreakdownInitialIndex + static_cast<int>(*blink_breakdown)
-            : viz_breakdown
-                  ? kVizBreakdownInitialIndex + static_cast<int>(*viz_breakdown)
-                  : static_cast<int>(stage_type);
+        : viz_breakdown
+            ? kVizBreakdownInitialIndex + static_cast<int>(*viz_breakdown)
+            : static_cast<int>(stage_type);
     const int histogram_index =
         (stage_type_index == static_cast<int>(StageType::kTotalLatency)
              ? kStagesWithBreakdownCount + frame_sequence_tracker_type_index
@@ -1119,6 +1155,10 @@
           reporter->set_frame_type(ChromeFrameReporter::BACKFILL);
         }
 
+        for (auto stage : high_latency_substages_) {
+          reporter->add_high_latency_contribution_stage(stage);
+        }
+
         // TODO(crbug.com/1086974): Set 'drop reason' if applicable.
       });
 
@@ -1248,20 +1288,19 @@
   DiscardOldPartialUpdateReporters();
 }
 
-void CompositorFrameReporter::CalculateStageLatencyPrediction(
-    std::vector<base::TimeDelta>& previous_predictions) {
+void CompositorFrameReporter::CalculateCompositorLatencyPrediction(
+    CompositorLatencyInfo& previous_predictions,
+    base::TimeDelta prediction_deviation_threshold) {
   // `stage_history_` should not be empty since we are calling this function
   // from DidPresentCompositorFrame(), which means there has to be some sort of
   // stage data.
   DCHECK(!stage_history_.empty());
 
-  int index_of_total_latency = static_cast<int>(StageType::kTotalLatency);
-
-  // The bad case of having `previous_predictions` being 0s should never happen
-  // since this function always only record the current PipelineReporter's
-  // duration if its duration is not 0s. Investigate if such rare case happens.
-  DCHECK(!previous_predictions[index_of_total_latency].is_zero())
-      << "previous_predictions should theoretically never have duration of 0s ";
+  // If the bad case of having `total_latency` of `previous_predictions` happens
+  // then it would mess up the prediction calculation, therefore, we want to
+  // reset the prediction by setting everything back to -1
+  if (previous_predictions.total_latency.is_zero())
+    previous_predictions = CompositorLatencyInfo(base::Microseconds(-1));
 
   base::TimeDelta total_pipeline_latency =
       stage_history_.back().end_time - stage_history_[0].start_time;
@@ -1275,36 +1314,42 @@
   // Note that `current_stage_durations` would always have the same length as
   // `previous_predictions`, since each index represent the breakdown stages of
   // the PipelineReporter listed at enum class, StageType.
-  std::vector<base::TimeDelta> current_stage_durations(kNumOfStages,
-                                                       base::Microseconds(0));
+  CompositorLatencyInfo current_stage_durations(base::Microseconds(0));
+  current_stage_durations.total_latency = total_pipeline_latency;
+
   for (auto stage : stage_history_) {
+    if (stage.stage_type == StageType::kTotalLatency)
+      continue;
     base::TimeDelta substageLatency = stage.end_time - stage.start_time;
-    current_stage_durations[static_cast<int>(stage.stage_type)] =
-        substageLatency;
+    current_stage_durations
+        .top_level_stages[static_cast<int>(stage.stage_type)] = substageLatency;
   }
-  current_stage_durations[index_of_total_latency] = total_pipeline_latency;
 
   // Do not record current pipeline details or update predictions if no frame
   // is submitted.
   if (current_stage_durations
-          [static_cast<int>(
-               StageType::kSubmitCompositorFrameToPresentationCompositorFrame)]
-              .is_zero())
+          .top_level_stages[static_cast<int>(
+              StageType::kSubmitCompositorFrameToPresentationCompositorFrame)]
+          .is_zero())
     return;
 
   // The previous prediction is initialized to be -1, so check if the current
   // PipelineReporter is the first reporter ever to be calculated.
-  if (previous_predictions[index_of_total_latency] == base::Microseconds(-1)) {
+  if (previous_predictions.total_latency == base::Microseconds(-1)) {
     previous_predictions = current_stage_durations;
   } else {
-    // TODO(crbug.com/1334823): Perform latency attribution.
+    if ((current_stage_durations.total_latency -
+         previous_predictions.total_latency) >= prediction_deviation_threshold)
+      FindHighLatencyAttribution(previous_predictions, current_stage_durations);
 
     for (int i = 0; i < kNumOfStages; i++) {
-      previous_predictions[i] =
-          (kWeightOfCurStageInPercent * current_stage_durations[i] +
-           (100 - kWeightOfCurStageInPercent) * previous_predictions[i]) /
-          100;
+      previous_predictions.top_level_stages[i] =
+          PredictLatency(previous_predictions.top_level_stages[i],
+                         current_stage_durations.top_level_stages[i]);
     }
+    previous_predictions.total_latency =
+        PredictLatency(previous_predictions.total_latency,
+                       current_stage_durations.total_latency);
   }
 }
 
@@ -1389,6 +1434,9 @@
       base::TimeDelta stage_duration = stage_it->start_time - dispatch_end_time;
       actual_event_latency.transition_duration = stage_duration;
       actual_event_latency.total_duration += stage_duration;
+      actual_event_latency.transition_name =
+          EventLatencyTracingRecorder::GetDispatchToCompositorBreakdownName(
+              last_valid_stage, stage_it->stage_type);
     }
   }
 
@@ -1404,8 +1452,14 @@
     }
   }
 
-  // TODO(crbug.com/1334827): Implement attribution for the substage with the
-  // highest latency.
+  // High latency attribution.
+  if (predicted_event_latency.total_duration.is_positive() &&
+      actual_event_latency.total_duration -
+              predicted_event_latency.total_duration >=
+          prediction_deviation_threshold) {
+    FindEventLatencyAttribution(event_metrics.get(), predicted_event_latency,
+                                actual_event_latency);
+  }
 
   // Calculate new dispatch stage predictions.
   base::TimeDelta predicted_total_duration = base::Microseconds(0);
@@ -1588,4 +1642,109 @@
   return info;
 }
 
+void CompositorFrameReporter::FindHighLatencyAttribution(
+    CompositorLatencyInfo& previous_predictions,
+    CompositorLatencyInfo& current_stage_durations) {
+  double contribution_change = -1;
+  double highest_contribution_change = -1;
+  std::vector<int> highest_contribution_change_index;
+
+  for (int i = 0; i < kNumOfStages; i++) {
+    contribution_change = (current_stage_durations.top_level_stages[i] /
+                           current_stage_durations.total_latency) -
+                          (previous_predictions.top_level_stages[i] /
+                           previous_predictions.total_latency);
+
+    if (contribution_change > highest_contribution_change) {
+      highest_contribution_change = contribution_change;
+      highest_contribution_change_index = {i};
+    } else if (std::abs(contribution_change - highest_contribution_change) <
+               kEpsilon) {
+      highest_contribution_change_index.push_back(i);
+    }
+  }
+
+  // It is not expensive to go through vector of indexes again since it is
+  // usually very small (possibilities of breakdown stages having the same
+  // change contribution is small).
+  for (auto index : highest_contribution_change_index) {
+    high_latency_substages_.push_back(
+        GetStageName(static_cast<StageType>(index)));
+  }
+}
+
+void CompositorFrameReporter::FindEventLatencyAttribution(
+    EventMetrics* event_metrics,
+    CompositorFrameReporter::EventLatencyInfo& predicted_event_latency,
+    CompositorFrameReporter::EventLatencyInfo& actual_event_latency) {
+  if (!event_metrics)
+    return;
+
+  std::vector<std::string> high_latency_stages;
+  double contribution_change = -1;
+  double highest_contribution_change = -1;
+
+  // Check dispatch stage change
+  EventMetrics::DispatchStage dispatch_stage =
+      EventMetrics::DispatchStage::kGenerated;
+  base::TimeTicks dispatch_timestamp =
+      event_metrics->GetDispatchStageTimestamp(dispatch_stage);
+  while (dispatch_stage != EventMetrics::DispatchStage::kMaxValue) {
+    DCHECK(!dispatch_timestamp.is_null());
+    auto end_stage = static_cast<EventMetrics::DispatchStage>(
+        static_cast<int>(dispatch_stage) + 1);
+    base::TimeTicks end_timestamp =
+        event_metrics->GetDispatchStageTimestamp(end_stage);
+    while (end_timestamp.is_null() &&
+           end_stage != EventMetrics::DispatchStage::kMaxValue) {
+      end_stage = static_cast<EventMetrics::DispatchStage>(
+          static_cast<int>(end_stage) + 1);
+      end_timestamp = event_metrics->GetDispatchStageTimestamp(end_stage);
+    }
+    if (end_timestamp.is_null())
+      break;
+
+    contribution_change =
+        (actual_event_latency
+             .dispatch_durations[static_cast<int>(end_stage) - 1] /
+         actual_event_latency.total_duration) -
+        (predicted_event_latency
+             .dispatch_durations[static_cast<int>(end_stage) - 1] /
+         predicted_event_latency.total_duration);
+    std::string dispatch_stage_name =
+        EventLatencyTracingRecorder::GetDispatchBreakdownName(dispatch_stage,
+                                                              end_stage);
+    highest_contribution_change = DetermineHighestContribution(
+        contribution_change, highest_contribution_change, dispatch_stage_name,
+        high_latency_stages);
+
+    dispatch_stage = end_stage;
+    dispatch_timestamp = end_timestamp;
+  }
+
+  // Check dispatch-to-compositor stage change
+  contribution_change = (actual_event_latency.transition_duration /
+                         actual_event_latency.total_duration) -
+                        (predicted_event_latency.transition_duration /
+                         predicted_event_latency.total_duration);
+  highest_contribution_change = DetermineHighestContribution(
+      contribution_change, highest_contribution_change,
+      actual_event_latency.transition_name, high_latency_stages);
+
+  // Check compositor stage change
+  for (int i = 0; i < kNumOfStages; i++) {
+    contribution_change = (actual_event_latency.compositor_durations[i] /
+                           actual_event_latency.total_duration) -
+                          (predicted_event_latency.compositor_durations[i] /
+                           predicted_event_latency.total_duration);
+    highest_contribution_change = DetermineHighestContribution(
+        contribution_change, highest_contribution_change,
+        GetStageName(static_cast<StageType>(i)), high_latency_stages);
+  }
+
+  for (auto stage : high_latency_stages) {
+    event_metrics->SetHighLatencyStage(stage);
+  }
+}
+
 }  // namespace cc
diff --git a/cc/metrics/compositor_frame_reporter.h b/cc/metrics/compositor_frame_reporter.h
index 8743e4b..68116860 100644
--- a/cc/metrics/compositor_frame_reporter.h
+++ b/cc/metrics/compositor_frame_reporter.h
@@ -153,6 +153,7 @@
     base::TimeDelta transition_duration;
     std::vector<base::TimeDelta> compositor_durations;
     base::TimeDelta total_duration;
+    std::string transition_name;
     EventLatencyInfo(const int num_dispatch_stages,
                      const int num_compositor_stages);
     EventLatencyInfo(const EventLatencyInfo&);
@@ -243,6 +244,17 @@
     base::TimeTicks swap_start_;
   };
 
+  // Wrapper for all level of breakdown stages' prediction
+  struct CC_EXPORT CompositorLatencyInfo {
+    CompositorLatencyInfo();
+    explicit CompositorLatencyInfo(base::TimeDelta init_value);
+    ~CompositorLatencyInfo();
+
+    std::vector<base::TimeDelta> top_level_stages;
+    // TODO(crbug.com/1334823): add viz and blink breakdown
+    base::TimeDelta total_latency;
+  };
+
   CompositorFrameReporter(const ActiveTrackers& active_trackers,
                           const viz::BeginFrameArgs& args,
                           bool should_report_histograms,
@@ -344,12 +356,8 @@
         is_accompanied_by_main_thread_update;
   }
 
-  void set_is_forked(bool is_forked) {
-    is_forked_ = is_forked;
-  }
-  void set_is_backfill(bool is_backfill) {
-    is_backfill_ = is_backfill;
-  }
+  void set_is_forked(bool is_forked) { is_forked_ = is_forked; }
+  void set_is_backfill(bool is_backfill) { is_backfill_ = is_backfill; }
 
   const viz::BeginFrameId& frame_id() const { return args_.frame_id; }
 
@@ -370,8 +378,9 @@
   // This function is called to calculate breakdown stage duration's prediction
   // based on the `previous_predictions` and update the `previous_predictions`
   // to the new prediction calculated.
-  void CalculateStageLatencyPrediction(
-      std::vector<base::TimeDelta>& previous_predictions);
+  void CalculateCompositorLatencyPrediction(
+      CompositorLatencyInfo& previous_predictions,
+      base::TimeDelta prediction_deviation_threshold);
 
   // Sets EventLatency stage duration predictions based on previous trace
   // durations using exponentially weighted averages.
@@ -382,9 +391,20 @@
   ReporterType get_reporter_type() { return reporter_type_; }
 
   void set_reporter_type_to_impl() { reporter_type_ = ReporterType::kImpl; }
-
   void set_reporter_type_to_main() { reporter_type_ = ReporterType::kMain; }
 
+  const std::vector<std::string>& high_latency_substages_for_testing() {
+    return high_latency_substages_;
+  }
+
+  void ClearHighLatencySubstagesForTesting() {
+    high_latency_substages_.clear();
+  }
+
+  std::vector<std::unique_ptr<EventMetrics>>& events_metrics_for_testing() {
+    return events_metrics_;
+  }
+
  protected:
   void set_has_partial_update(bool has_partial_update) {
     has_partial_update_ = has_partial_update;
@@ -437,6 +457,15 @@
   // updates (see comments on EventMetrics::requires_main_thread_update_).
   EventMetrics::List TakeMainBlockedEventsMetrics();
 
+  void FindHighLatencyAttribution(
+      CompositorLatencyInfo& previous_predictions,
+      CompositorLatencyInfo& current_stage_durations);
+
+  void FindEventLatencyAttribution(
+      EventMetrics* event_metrics,
+      CompositorFrameReporter::EventLatencyInfo& predicted_event_latency,
+      CompositorFrameReporter::EventLatencyInfo& actual_event_latency);
+
   // Whether UMA histograms should be reported or not.
   const bool should_report_histograms_;
 
@@ -526,6 +555,8 @@
 
   const GlobalMetricsTrackers global_trackers_;
 
+  std::vector<std::string> high_latency_substages_;
+
   ReporterType reporter_type_;
 
   base::WeakPtrFactory<CompositorFrameReporter> weak_factory_{this};
diff --git a/cc/metrics/compositor_frame_reporter_unittest.cc b/cc/metrics/compositor_frame_reporter_unittest.cc
index c39286d6..3969aa3 100644
--- a/cc/metrics/compositor_frame_reporter_unittest.cc
+++ b/cc/metrics/compositor_frame_reporter_unittest.cc
@@ -202,8 +202,9 @@
   TotalFrameCounter total_frame_counter_;
   std::unique_ptr<CompositorFrameReporter> pipeline_reporter_;
 
+  // Number of breakdown stages of the current PipelineReporter
   const int kNumOfStages =
-      static_cast<int>(CompositorFrameReporter::StageType::kStageTypeCount);
+      static_cast<int>(CompositorFrameReporter::StageType::kStageTypeCount) - 1;
   const int kNumDispatchStages =
       static_cast<int>(EventMetrics::DispatchStage::kMaxValue);
   const base::TimeDelta kLatencyPredictionDeviationThreshold =
@@ -813,34 +814,54 @@
   pipeline_reporter_->TerminateFrame(
       CompositorFrameReporter::FrameTerminationStatus::kPresentedFrame, Now());
 
-  int kNumOfStages =
-      static_cast<int>(CompositorFrameReporter::StageType::kStageTypeCount);
-
   // predictions when this is the very first prediction
-  std::vector<base::TimeDelta> expected_latency_predictions1(
-      kNumOfStages - 1, base::Microseconds(3));
-  expected_latency_predictions1.push_back(base::Microseconds(21));
+  CompositorFrameReporter::CompositorLatencyInfo expected_latency_predictions1;
+  expected_latency_predictions1.top_level_stages =
+      std::vector<base::TimeDelta>(kNumOfStages, base::Microseconds(3));
+  expected_latency_predictions1.total_latency = base::Microseconds(21);
 
   // predictions when there exists a previous prediction
-  std::vector<base::TimeDelta> expected_latency_predictions2 = {
+  CompositorFrameReporter::CompositorLatencyInfo expected_latency_predictions2;
+  expected_latency_predictions2.top_level_stages = {
       base::Microseconds(1), base::Microseconds(0), base::Microseconds(3),
       base::Microseconds(0), base::Microseconds(2), base::Microseconds(3),
-      base::Microseconds(0), base::Microseconds(12)};
+      base::Microseconds(0)};
+  expected_latency_predictions2.total_latency = base::Microseconds(12);
 
-  std::vector<base::TimeDelta> actual_latency_predictions1(
-      kNumOfStages, base::Microseconds(-1));
-  pipeline_reporter_->CalculateStageLatencyPrediction(
-      actual_latency_predictions1);
+  // expected attribution for all 3 cases above
+  std::vector<std::string> expected_latency_attributions = {};
 
-  std::vector<base::TimeDelta> actual_latency_predictions2 = {
+  CompositorFrameReporter::CompositorLatencyInfo actual_latency_predictions1(
+      base::Microseconds(-1));
+  pipeline_reporter_->CalculateCompositorLatencyPrediction(
+      actual_latency_predictions1, kLatencyPredictionDeviationThreshold);
+  std::vector<std::string> actual_latency_attributions1 =
+      pipeline_reporter_->high_latency_substages_for_testing();
+  pipeline_reporter_->ClearHighLatencySubstagesForTesting();
+
+  CompositorFrameReporter::CompositorLatencyInfo actual_latency_predictions2;
+  actual_latency_predictions2.top_level_stages = {
       base::Microseconds(1), base::Microseconds(0), base::Microseconds(4),
       base::Microseconds(0), base::Microseconds(2), base::Microseconds(3),
-      base::Microseconds(0), base::Microseconds(10)};
-  pipeline_reporter_->CalculateStageLatencyPrediction(
-      actual_latency_predictions2);
+      base::Microseconds(0)};
+  actual_latency_predictions2.total_latency = base::Microseconds(10);
+  pipeline_reporter_->CalculateCompositorLatencyPrediction(
+      actual_latency_predictions2, kLatencyPredictionDeviationThreshold);
+  std::vector<std::string> actual_latency_attributions2 =
+      pipeline_reporter_->high_latency_substages_for_testing();
+  pipeline_reporter_->ClearHighLatencySubstagesForTesting();
 
-  EXPECT_EQ(expected_latency_predictions1, actual_latency_predictions1);
-  EXPECT_EQ(expected_latency_predictions2, actual_latency_predictions2);
+  EXPECT_EQ(expected_latency_predictions1.top_level_stages,
+            actual_latency_predictions1.top_level_stages);
+  EXPECT_EQ(expected_latency_predictions1.total_latency,
+            actual_latency_predictions1.total_latency);
+  EXPECT_EQ(expected_latency_predictions2.top_level_stages,
+            actual_latency_predictions2.top_level_stages);
+  EXPECT_EQ(expected_latency_predictions2.total_latency,
+            actual_latency_predictions2.total_latency);
+
+  EXPECT_EQ(expected_latency_attributions, actual_latency_attributions1);
+  EXPECT_EQ(expected_latency_attributions, actual_latency_attributions2);
 
   pipeline_reporter_ = nullptr;
 }
@@ -879,35 +900,55 @@
 
   AdvanceNowByUs(0);
   pipeline_reporter_->TerminateFrame(
-      CompositorFrameReporter::FrameTerminationStatus::kPresentedFrame, Now());
-
-  int kNumOfStages =
-      static_cast<int>(CompositorFrameReporter::StageType::kStageTypeCount);
+      CompositorFrameReporter::FrameTerminationStatus::kDidNotProduceFrame,
+      Now());
 
   // predictions when this is the very first prediction
-  std::vector<base::TimeDelta> expected_latency_predictions1(
-      kNumOfStages, base::Microseconds(-1));
+  CompositorFrameReporter::CompositorLatencyInfo expected_latency_predictions1(
+      base::Microseconds(-1));
 
   // predictions when there exists a previous prediction
-  std::vector<base::TimeDelta> expected_latency_predictions2 = {
+  CompositorFrameReporter::CompositorLatencyInfo expected_latency_predictions2;
+  expected_latency_predictions2.top_level_stages = {
       base::Microseconds(1), base::Microseconds(0), base::Microseconds(4),
       base::Microseconds(0), base::Microseconds(2), base::Microseconds(3),
-      base::Microseconds(0), base::Microseconds(10)};
+      base::Microseconds(0)};
+  expected_latency_predictions2.total_latency = base::Microseconds(10);
 
-  std::vector<base::TimeDelta> actual_latency_predictions1(
-      kNumOfStages, base::Microseconds(-1));
-  pipeline_reporter_->CalculateStageLatencyPrediction(
-      actual_latency_predictions1);
+  // expected attribution for all 3 cases above
+  std::vector<std::string> expected_latency_attributions = {};
 
-  std::vector<base::TimeDelta> actual_latency_predictions2 = {
+  CompositorFrameReporter::CompositorLatencyInfo actual_latency_predictions1(
+      base::Microseconds(-1));
+  pipeline_reporter_->CalculateCompositorLatencyPrediction(
+      actual_latency_predictions1, kLatencyPredictionDeviationThreshold);
+  std::vector<std::string> actual_latency_attributions1 =
+      pipeline_reporter_->high_latency_substages_for_testing();
+  pipeline_reporter_->ClearHighLatencySubstagesForTesting();
+
+  CompositorFrameReporter::CompositorLatencyInfo actual_latency_predictions2;
+  actual_latency_predictions2.top_level_stages = {
       base::Microseconds(1), base::Microseconds(0), base::Microseconds(4),
       base::Microseconds(0), base::Microseconds(2), base::Microseconds(3),
-      base::Microseconds(0), base::Microseconds(10)};
-  pipeline_reporter_->CalculateStageLatencyPrediction(
-      actual_latency_predictions2);
+      base::Microseconds(0)};
+  actual_latency_predictions2.total_latency = base::Microseconds(10);
+  pipeline_reporter_->CalculateCompositorLatencyPrediction(
+      actual_latency_predictions2, kLatencyPredictionDeviationThreshold);
+  std::vector<std::string> actual_latency_attributions2 =
+      pipeline_reporter_->high_latency_substages_for_testing();
+  pipeline_reporter_->ClearHighLatencySubstagesForTesting();
 
-  EXPECT_EQ(expected_latency_predictions1, actual_latency_predictions1);
-  EXPECT_EQ(expected_latency_predictions2, actual_latency_predictions2);
+  EXPECT_EQ(expected_latency_predictions1.top_level_stages,
+            actual_latency_predictions1.top_level_stages);
+  EXPECT_EQ(expected_latency_predictions1.total_latency,
+            actual_latency_predictions1.total_latency);
+  EXPECT_EQ(expected_latency_predictions2.top_level_stages,
+            actual_latency_predictions2.top_level_stages);
+  EXPECT_EQ(expected_latency_predictions2.total_latency,
+            actual_latency_predictions2.total_latency);
+
+  EXPECT_EQ(expected_latency_attributions, actual_latency_attributions1);
+  EXPECT_EQ(expected_latency_attributions, actual_latency_attributions2);
 
   pipeline_reporter_ = nullptr;
 }
@@ -929,7 +970,7 @@
   pipeline_reporter_->StartStage(
       CompositorFrameReporter::StageType::kEndCommitToActivation, Now());
 
-  AdvanceNowByUs(1000000);
+  AdvanceNowByUs(10000000);
   pipeline_reporter_->StartStage(
       CompositorFrameReporter::StageType::kActivation, Now());
 
@@ -948,37 +989,272 @@
   pipeline_reporter_->TerminateFrame(
       CompositorFrameReporter::FrameTerminationStatus::kPresentedFrame, Now());
 
-  int kNumOfStages =
-      static_cast<int>(CompositorFrameReporter::StageType::kStageTypeCount);
-
   // predictions when this is the very first prediction
-  std::vector<base::TimeDelta> expected_latency_predictions1 = {
+  CompositorFrameReporter::CompositorLatencyInfo expected_latency_predictions1;
+  expected_latency_predictions1.top_level_stages = {
       base::Microseconds(10000000), base::Microseconds(5000000),
-      base::Microseconds(6000000),  base::Microseconds(1000000),
+      base::Microseconds(6000000),  base::Microseconds(10000000),
       base::Microseconds(0),        base::Microseconds(2000000),
-      base::Microseconds(10000000), base::Microseconds(34000000)};
+      base::Microseconds(10000000)};
+  expected_latency_predictions1.total_latency = base::Microseconds(43000000);
 
   // predictions when there exists a previous prediction
-  std::vector<base::TimeDelta> expected_latency_predictions2 = {
+  CompositorFrameReporter::CompositorLatencyInfo expected_latency_predictions2;
+  expected_latency_predictions2.top_level_stages = {
       base::Microseconds(2500000), base::Microseconds(1250000),
-      base::Microseconds(1500003), base::Microseconds(250000),
+      base::Microseconds(1500003), base::Microseconds(2500000),
       base::Microseconds(1),       base::Microseconds(500002),
-      base::Microseconds(2500000), base::Microseconds(8500007)};
+      base::Microseconds(2500000)};
+  expected_latency_predictions2.total_latency = base::Microseconds(10750007);
 
-  std::vector<base::TimeDelta> actual_latency_predictions1(
-      kNumOfStages, base::Microseconds(-1));
-  pipeline_reporter_->CalculateStageLatencyPrediction(
-      actual_latency_predictions1);
+  // expected attribution for cases 1 above
+  std::vector<std::string> expected_latency_attributions1 = {};
 
-  std::vector<base::TimeDelta> actual_latency_predictions2 = {
+  // expected attribution for case 2 above
+  std::vector<std::string> expected_latency_attributions2 = {
+      "EndCommitToActivation",
+      "SubmitCompositorFrameToPresentationCompositorFrame"};
+
+  CompositorFrameReporter::CompositorLatencyInfo actual_latency_predictions1(
+      base::Microseconds(-1));
+  pipeline_reporter_->CalculateCompositorLatencyPrediction(
+      actual_latency_predictions1, kLatencyPredictionDeviationThreshold);
+  std::vector<std::string> actual_latency_attributions1 =
+      pipeline_reporter_->high_latency_substages_for_testing();
+  pipeline_reporter_->ClearHighLatencySubstagesForTesting();
+
+  CompositorFrameReporter::CompositorLatencyInfo actual_latency_predictions2;
+  actual_latency_predictions2.top_level_stages = {
       base::Microseconds(1), base::Microseconds(0), base::Microseconds(4),
       base::Microseconds(0), base::Microseconds(2), base::Microseconds(3),
-      base::Microseconds(0), base::Microseconds(10)};
-  pipeline_reporter_->CalculateStageLatencyPrediction(
-      actual_latency_predictions2);
+      base::Microseconds(0)};
+  actual_latency_predictions2.total_latency = base::Microseconds(10);
+  pipeline_reporter_->CalculateCompositorLatencyPrediction(
+      actual_latency_predictions2, kLatencyPredictionDeviationThreshold);
+  std::vector<std::string> actual_latency_attributions2 =
+      pipeline_reporter_->high_latency_substages_for_testing();
+  pipeline_reporter_->ClearHighLatencySubstagesForTesting();
 
-  EXPECT_EQ(expected_latency_predictions1, actual_latency_predictions1);
-  EXPECT_EQ(expected_latency_predictions2, actual_latency_predictions2);
+  EXPECT_EQ(expected_latency_predictions1.top_level_stages,
+            actual_latency_predictions1.top_level_stages);
+  EXPECT_EQ(expected_latency_predictions1.total_latency,
+            actual_latency_predictions1.total_latency);
+  EXPECT_EQ(expected_latency_predictions2.top_level_stages,
+            actual_latency_predictions2.top_level_stages);
+  EXPECT_EQ(expected_latency_predictions2.total_latency,
+            actual_latency_predictions2.total_latency);
+
+  EXPECT_EQ(expected_latency_attributions1, actual_latency_attributions1);
+  EXPECT_EQ(expected_latency_attributions2, actual_latency_attributions2);
+
+  pipeline_reporter_ = nullptr;
+}
+
+TEST_F(CompositorFrameReporterTest, StageLatencyMultiplePrediction) {
+  CompositorFrameReporter::CompositorLatencyInfo actual_latency_predictions(
+      base::Microseconds(-1));
+  CompositorFrameReporter::CompositorLatencyInfo expected_latency_predictions;
+
+  // First compositor reporter (general)
+  pipeline_reporter_->StartStage(
+      CompositorFrameReporter::StageType::kBeginImplFrameToSendBeginMainFrame,
+      Now());
+
+  AdvanceNowByUs(16000);
+  pipeline_reporter_->StartStage(
+      CompositorFrameReporter::StageType::kEndActivateToSubmitCompositorFrame,
+      Now());
+
+  AdvanceNowByUs(1500);
+  pipeline_reporter_->StartStage(
+      CompositorFrameReporter::StageType::
+          kSubmitCompositorFrameToPresentationCompositorFrame,
+      Now());
+
+  AdvanceNowByUs(833000);
+  pipeline_reporter_->TerminateFrame(
+      CompositorFrameReporter::FrameTerminationStatus::kPresentedFrame, Now());
+
+  expected_latency_predictions.top_level_stages = {
+      base::Microseconds(16000), base::Microseconds(0),
+      base::Microseconds(0),     base::Microseconds(0),
+      base::Microseconds(0),     base::Microseconds(1500),
+      base::Microseconds(833000)};
+  expected_latency_predictions.total_latency = base::Microseconds(850500);
+
+  pipeline_reporter_->CalculateCompositorLatencyPrediction(
+      actual_latency_predictions, kLatencyPredictionDeviationThreshold);
+
+  EXPECT_EQ(expected_latency_predictions.top_level_stages,
+            actual_latency_predictions.top_level_stages);
+  EXPECT_EQ(expected_latency_predictions.total_latency,
+            actual_latency_predictions.total_latency);
+
+  // Second compositor reporter (without subtmit stage)
+  pipeline_reporter_ = CreatePipelineReporter();
+  pipeline_reporter_->StartStage(
+      CompositorFrameReporter::StageType::kBeginImplFrameToSendBeginMainFrame,
+      Now());
+
+  AdvanceNowByUs(16000);
+  pipeline_reporter_->TerminateFrame(
+      CompositorFrameReporter::FrameTerminationStatus::kDidNotProduceFrame,
+      Now());
+
+  expected_latency_predictions.top_level_stages = {
+      base::Microseconds(16000), base::Microseconds(0),
+      base::Microseconds(0),     base::Microseconds(0),
+      base::Microseconds(0),     base::Microseconds(1500),
+      base::Microseconds(833000)};
+  expected_latency_predictions.total_latency = base::Microseconds(850500);
+
+  pipeline_reporter_->CalculateCompositorLatencyPrediction(
+      actual_latency_predictions, kLatencyPredictionDeviationThreshold);
+
+  EXPECT_EQ(expected_latency_predictions.top_level_stages,
+            actual_latency_predictions.top_level_stages);
+  EXPECT_EQ(expected_latency_predictions.total_latency,
+            actual_latency_predictions.total_latency);
+
+  // Third compositor reporter (prediction and actual latency does not differ
+  // by 8)
+  pipeline_reporter_ = CreatePipelineReporter();
+  pipeline_reporter_->StartStage(
+      CompositorFrameReporter::StageType::kBeginImplFrameToSendBeginMainFrame,
+      Now());
+
+  AdvanceNowByUs(16500);
+  pipeline_reporter_->StartStage(
+      CompositorFrameReporter::StageType::kEndActivateToSubmitCompositorFrame,
+      Now());
+
+  AdvanceNowByUs(2000);
+  pipeline_reporter_->StartStage(
+      CompositorFrameReporter::StageType::
+          kSubmitCompositorFrameToPresentationCompositorFrame,
+      Now());
+
+  AdvanceNowByUs(833000);
+  pipeline_reporter_->TerminateFrame(
+      CompositorFrameReporter::FrameTerminationStatus::kPresentedFrame, Now());
+
+  expected_latency_predictions.top_level_stages = {
+      base::Microseconds(16125), base::Microseconds(0),
+      base::Microseconds(0),     base::Microseconds(0),
+      base::Microseconds(0),     base::Microseconds(1625),
+      base::Microseconds(833000)};
+  expected_latency_predictions.total_latency = base::Microseconds(850750);
+
+  pipeline_reporter_->CalculateCompositorLatencyPrediction(
+      actual_latency_predictions, kLatencyPredictionDeviationThreshold);
+
+  EXPECT_EQ(expected_latency_predictions.top_level_stages,
+            actual_latency_predictions.top_level_stages);
+  EXPECT_EQ(expected_latency_predictions.total_latency,
+            actual_latency_predictions.total_latency);
+
+  // Fourth compositor reporter (total duration is 0)
+  pipeline_reporter_ = CreatePipelineReporter();
+  pipeline_reporter_->StartStage(
+      CompositorFrameReporter::StageType::kBeginImplFrameToSendBeginMainFrame,
+      Now());
+
+  AdvanceNowByUs(0);
+  pipeline_reporter_->StartStage(
+      CompositorFrameReporter::StageType::kSendBeginMainFrameToCommit, Now());
+
+  AdvanceNowByUs(0);
+  pipeline_reporter_->StartStage(CompositorFrameReporter::StageType::kCommit,
+                                 Now());
+
+  AdvanceNowByUs(0);
+  pipeline_reporter_->StartStage(
+      CompositorFrameReporter::StageType::kEndCommitToActivation, Now());
+
+  AdvanceNowByUs(0);
+  pipeline_reporter_->StartStage(
+      CompositorFrameReporter::StageType::
+          kSubmitCompositorFrameToPresentationCompositorFrame,
+      Now());
+
+  AdvanceNowByUs(0);
+  pipeline_reporter_->TerminateFrame(
+      CompositorFrameReporter::FrameTerminationStatus::kDidNotProduceFrame,
+      Now());
+
+  expected_latency_predictions.top_level_stages = {
+      base::Microseconds(16125), base::Microseconds(0),
+      base::Microseconds(0),     base::Microseconds(0),
+      base::Microseconds(0),     base::Microseconds(1625),
+      base::Microseconds(833000)};
+  expected_latency_predictions.total_latency = base::Microseconds(850750);
+
+  pipeline_reporter_->CalculateCompositorLatencyPrediction(
+      actual_latency_predictions, kLatencyPredictionDeviationThreshold);
+
+  EXPECT_EQ(expected_latency_predictions.top_level_stages,
+            actual_latency_predictions.top_level_stages);
+  EXPECT_EQ(expected_latency_predictions.total_latency,
+            actual_latency_predictions.total_latency);
+
+  // Fifth compositor reporter (prediction and actual latency differ by a lot)
+  pipeline_reporter_ = CreatePipelineReporter();
+  pipeline_reporter_->StartStage(
+      CompositorFrameReporter::StageType::kBeginImplFrameToSendBeginMainFrame,
+      Now());
+
+  AdvanceNowByUs(16000);
+  pipeline_reporter_->StartStage(
+      CompositorFrameReporter::StageType::kSendBeginMainFrameToCommit, Now());
+
+  AdvanceNowByUs(60000);
+  pipeline_reporter_->StartStage(CompositorFrameReporter::StageType::kCommit,
+                                 Now());
+
+  AdvanceNowByUs(6000);
+  pipeline_reporter_->StartStage(
+      CompositorFrameReporter::StageType::kEndCommitToActivation, Now());
+
+  AdvanceNowByUs(3000);
+  pipeline_reporter_->StartStage(
+      CompositorFrameReporter::StageType::kActivation, Now());
+
+  AdvanceNowByUs(300);
+  pipeline_reporter_->StartStage(
+      CompositorFrameReporter::StageType::kEndActivateToSubmitCompositorFrame,
+      Now());
+
+  AdvanceNowByUs(39000);
+  pipeline_reporter_->StartStage(
+      CompositorFrameReporter::StageType::
+          kSubmitCompositorFrameToPresentationCompositorFrame,
+      Now());
+
+  AdvanceNowByUs(833000);
+  pipeline_reporter_->TerminateFrame(
+      CompositorFrameReporter::FrameTerminationStatus::kDidNotProduceFrame,
+      Now());
+
+  expected_latency_predictions.top_level_stages = {
+      base::Microseconds(16093), base::Microseconds(15000),
+      base::Microseconds(1500),  base::Microseconds(750),
+      base::Microseconds(75),    base::Microseconds(10968),
+      base::Microseconds(833000)};
+  expected_latency_predictions.total_latency = base::Microseconds(877387);
+
+  std::vector<std::string> expected_latency_attributions = {
+      "SendBeginMainFrameToCommit"};
+
+  pipeline_reporter_->CalculateCompositorLatencyPrediction(
+      actual_latency_predictions, kLatencyPredictionDeviationThreshold);
+  std::vector<std::string> actual_latency_attributions =
+      pipeline_reporter_->high_latency_substages_for_testing();
+
+  EXPECT_EQ(expected_latency_predictions.top_level_stages,
+            actual_latency_predictions.top_level_stages);
+  EXPECT_EQ(expected_latency_predictions.total_latency,
+            actual_latency_predictions.total_latency);
+  EXPECT_EQ(expected_latency_attributions, actual_latency_attributions);
 
   pipeline_reporter_ = nullptr;
 }
@@ -1072,12 +1348,12 @@
   pipeline_reporter_ = nullptr;
 }
 
-// Tests that when a new frame with missing dispatch stages is presented to the
-// user, event latency predictions are reported properly.
+// Tests that when a new frame with missing dispatch stages is presented to
+// the user, event latency predictions are reported properly.
 TEST_F(CompositorFrameReporterTest,
        EventLatencyDispatchPredictionsWithMissingStages) {
-  // Invalid EventLatency stage durations will cause program to crash, validity
-  // checked in event_latency_tracing_recorder.cc.
+  // Invalid EventLatency stage durations will cause program to crash,
+  // validity checked in event_latency_tracing_recorder.cc.
   std::vector<int> dispatch_times = {400, 600, 700, -1, -1};
   std::unique_ptr<EventMetrics> event_metrics_ptrs[] = {
       CreateScrollUpdateEventMetricsWithDispatchTimes(
@@ -1274,8 +1550,8 @@
               actual_predictions1.compositor_durations[i]);
     EXPECT_EQ(expected_compositor2[i],
               actual_predictions2.compositor_durations[i]);
-    EXPECT_EQ(expected_compositor2[i],
-              actual_predictions2.compositor_durations[i]);
+    EXPECT_EQ(expected_compositor3[i],
+              actual_predictions3.compositor_durations[i]);
   }
   EXPECT_EQ(expected_transition1, actual_predictions1.transition_duration);
   EXPECT_EQ(expected_total1, actual_predictions1.total_duration);
@@ -1394,5 +1670,284 @@
   pipeline_reporter_ = nullptr;
 }
 
+// Tests that when a frame is presented to the user, high latency attribution
+// for EventLatency is reported properly for filtered EventTypes.
+TEST_F(CompositorFrameReporterTest, EventLatencyAttributionPredictions) {
+  std::vector<int> dispatch_times = {300, 300, 300, 50000, 300};
+  // The prediction should only be updated with the ScrollUpdateType event,
+  // other EventType metrics should be ignored.
+  std::unique_ptr<EventMetrics> event_metrics_ptrs[] = {
+      CreateScrollUpdateEventMetricsWithDispatchTimes(
+          false, ScrollUpdateEventMetrics::ScrollUpdateType::kContinued,
+          dispatch_times)};
+  EXPECT_THAT(event_metrics_ptrs, Each(NotNull()));
+  EventMetrics::List events_metrics = {
+      std::make_move_iterator(std::begin(event_metrics_ptrs)),
+      std::make_move_iterator(std::end(event_metrics_ptrs))};
+
+  AdvanceNowByUs(300);
+  pipeline_reporter_->StartStage(
+      CompositorFrameReporter::StageType::kBeginImplFrameToSendBeginMainFrame,
+      Now());
+
+  // For this test there are only 3 compositor substages updated which reflects
+  // a more realistic scenario.
+
+  AdvanceNowByUs(300);  // kBeginImplFrameToSendBeginMainFrame duration
+  pipeline_reporter_->StartStage(
+      CompositorFrameReporter::StageType::kEndActivateToSubmitCompositorFrame,
+      Now());
+
+  AdvanceNowByUs(50000);  // kEndActivateToSubmitCompositorFrame duration
+  pipeline_reporter_->StartStage(
+      CompositorFrameReporter::StageType::
+          kSubmitCompositorFrameToPresentationCompositorFrame,
+      Now());
+
+  // kSubmitCompositorFrameToPresentationCompositorFrame duration
+  AdvanceNowByUs(300);
+  pipeline_reporter_->TerminateFrame(
+      CompositorFrameReporter::FrameTerminationStatus::kPresentedFrame, Now());
+
+  pipeline_reporter_->AddEventsMetrics(std::move(events_metrics));
+
+  // Test with no high latency attribution.
+  CompositorFrameReporter::EventLatencyInfo expected_predictions1 =
+      CompositorFrameReporter::EventLatencyInfo(kNumDispatchStages,
+                                                kNumOfStages);
+  IntToTimeDeltaVector(expected_predictions1.dispatch_durations,
+                       std::vector<int>{300, 300, 300, 50000, 300});
+  expected_predictions1.transition_duration = base::Microseconds(300);
+  IntToTimeDeltaVector(expected_predictions1.compositor_durations,
+                       std::vector<int>{300, -1, -1, -1, -1, 50000, 300});
+  expected_predictions1.total_duration = base::Microseconds(102100);
+
+  CompositorFrameReporter::EventLatencyInfo actual_predictions1 =
+      CompositorFrameReporter::EventLatencyInfo(kNumDispatchStages,
+                                                kNumOfStages);
+  pipeline_reporter_->CalculateEventLatencyPrediction(
+      actual_predictions1, kLatencyPredictionDeviationThreshold);
+
+  std::unique_ptr<EventMetrics>& event_metrics =
+      pipeline_reporter_->events_metrics_for_testing()[0];
+  std::vector<std::string> attribution = event_metrics->GetHighLatencyStages();
+  EXPECT_EQ(0, (int)attribution.size());
+  event_metrics->ClearHighLatencyStagesForTesting();
+
+  // Test with 1 high latency stage attributed.
+  CompositorFrameReporter::EventLatencyInfo expected_predictions2 =
+      CompositorFrameReporter::EventLatencyInfo(kNumDispatchStages,
+                                                kNumOfStages);
+  IntToTimeDeltaVector(expected_predictions2.dispatch_durations,
+                       std::vector<int>{300, 300, 300, 12725, 300});
+  expected_predictions2.transition_duration = base::Microseconds(300);
+  IntToTimeDeltaVector(expected_predictions2.compositor_durations,
+                       std::vector<int>{300, -1, -1, -1, -1, 50000, 300});
+  expected_predictions2.total_duration = base::Microseconds(64825);
+
+  CompositorFrameReporter::EventLatencyInfo actual_predictions2 =
+      CompositorFrameReporter::EventLatencyInfo(kNumDispatchStages,
+                                                kNumOfStages);
+  IntToTimeDeltaVector(actual_predictions2.dispatch_durations,
+                       std::vector<int>{300, 300, 300, 300, 300});
+  actual_predictions2.transition_duration = base::Microseconds(300);
+  IntToTimeDeltaVector(actual_predictions2.compositor_durations,
+                       std::vector<int>{300, -1, -1, -1, -1, 50000, 300});
+  actual_predictions2.total_duration = base::Microseconds(52400);
+  pipeline_reporter_->CalculateEventLatencyPrediction(
+      actual_predictions2, kLatencyPredictionDeviationThreshold);
+
+  attribution = event_metrics->GetHighLatencyStages();
+  EXPECT_EQ(1, (int)attribution.size());
+  EXPECT_EQ("RendererCompositorToMain", attribution[0]);
+  event_metrics->ClearHighLatencyStagesForTesting();
+
+  // Test with more than 1 high latency stage attributed.
+  CompositorFrameReporter::EventLatencyInfo expected_predictions3 =
+      CompositorFrameReporter::EventLatencyInfo(kNumDispatchStages,
+                                                kNumOfStages);
+  IntToTimeDeltaVector(expected_predictions3.dispatch_durations,
+                       std::vector<int>{300, 300, 300, 12725, 300});
+  expected_predictions3.transition_duration = base::Microseconds(300);
+  IntToTimeDeltaVector(expected_predictions3.compositor_durations,
+                       std::vector<int>{300, -1, -1, -1, -1, 12725, 300});
+  expected_predictions3.total_duration = base::Microseconds(27550);
+
+  CompositorFrameReporter::EventLatencyInfo actual_predictions3 =
+      CompositorFrameReporter::EventLatencyInfo(kNumDispatchStages,
+                                                kNumOfStages);
+  IntToTimeDeltaVector(actual_predictions3.dispatch_durations,
+                       std::vector<int>{300, 300, 300, 300, 300});
+  actual_predictions3.transition_duration = base::Microseconds(300);
+  IntToTimeDeltaVector(actual_predictions3.compositor_durations,
+                       std::vector<int>{300, -1, -1, -1, -1, 300, 300});
+  actual_predictions3.total_duration = base::Microseconds(2700);
+  pipeline_reporter_->CalculateEventLatencyPrediction(
+      actual_predictions3, kLatencyPredictionDeviationThreshold);
+
+  attribution = event_metrics->GetHighLatencyStages();
+  EXPECT_EQ(2, (int)attribution.size());
+  EXPECT_EQ("RendererCompositorToMain", attribution[0]);
+  EXPECT_EQ("EndActivateToSubmitCompositorFrame", attribution[1]);
+
+  // Check that all prediction values are accurate.
+  for (int i = 0; i < kNumDispatchStages; i++) {
+    EXPECT_EQ(expected_predictions1.dispatch_durations[i],
+              actual_predictions1.dispatch_durations[i]);
+    EXPECT_EQ(expected_predictions2.dispatch_durations[i],
+              actual_predictions2.dispatch_durations[i]);
+    EXPECT_EQ(expected_predictions3.dispatch_durations[i],
+              actual_predictions3.dispatch_durations[i]);
+  }
+  for (int i = 0; i < kNumOfStages; i++) {
+    EXPECT_EQ(expected_predictions1.compositor_durations[i],
+              actual_predictions1.compositor_durations[i]);
+    EXPECT_EQ(expected_predictions2.compositor_durations[i],
+              actual_predictions2.compositor_durations[i]);
+    EXPECT_EQ(expected_predictions3.compositor_durations[i],
+              actual_predictions3.compositor_durations[i]);
+  }
+  EXPECT_EQ(expected_predictions1.transition_duration,
+            actual_predictions1.transition_duration);
+  EXPECT_EQ(expected_predictions1.total_duration,
+            actual_predictions1.total_duration);
+  EXPECT_EQ(expected_predictions2.transition_duration,
+            actual_predictions2.transition_duration);
+  EXPECT_EQ(expected_predictions2.total_duration,
+            actual_predictions2.total_duration);
+  EXPECT_EQ(expected_predictions3.transition_duration,
+            actual_predictions3.transition_duration);
+  EXPECT_EQ(expected_predictions3.total_duration,
+            actual_predictions3.total_duration);
+
+  pipeline_reporter_ = nullptr;
+}
+
+// Tests that when a frame is presented to the user, high latency attribution
+// for EventLatency is reported properly for filtered EventTypes.
+TEST_F(CompositorFrameReporterTest, EventLatencyAttributionChangePredictions) {
+  std::vector<int> dispatch_times = {40000, -1, -1, 300, 50000};
+  // The prediction should only be updated with the ScrollUpdateType event,
+  // other EventType metrics should be ignored.
+  std::unique_ptr<EventMetrics> event_metrics_ptrs[] = {
+      CreateScrollUpdateEventMetricsWithDispatchTimes(
+          false, ScrollUpdateEventMetrics::ScrollUpdateType::kContinued,
+          dispatch_times)};
+  EXPECT_THAT(event_metrics_ptrs, Each(NotNull()));
+  EventMetrics::List events_metrics = {
+      std::make_move_iterator(std::begin(event_metrics_ptrs)),
+      std::make_move_iterator(std::end(event_metrics_ptrs))};
+
+  AdvanceNowByUs(300);
+  pipeline_reporter_->StartStage(
+      CompositorFrameReporter::StageType::kBeginImplFrameToSendBeginMainFrame,
+      Now());
+
+  // For this test there are only 3 compositor substages updated which reflects
+  // a more realistic scenario.
+
+  AdvanceNowByUs(300);  // kBeginImplFrameToSendBeginMainFrame duration
+  pipeline_reporter_->StartStage(
+      CompositorFrameReporter::StageType::kEndActivateToSubmitCompositorFrame,
+      Now());
+
+  AdvanceNowByUs(50000);  // kEndActivateToSubmitCompositorFrame duration
+  pipeline_reporter_->StartStage(
+      CompositorFrameReporter::StageType::
+          kSubmitCompositorFrameToPresentationCompositorFrame,
+      Now());
+
+  // kSubmitCompositorFrameToPresentationCompositorFrame duration
+  AdvanceNowByUs(300);
+  pipeline_reporter_->TerminateFrame(
+      CompositorFrameReporter::FrameTerminationStatus::kPresentedFrame, Now());
+
+  pipeline_reporter_->AddEventsMetrics(std::move(events_metrics));
+
+  // Test 1
+  CompositorFrameReporter::EventLatencyInfo expected_predictions1 =
+      CompositorFrameReporter::EventLatencyInfo(kNumDispatchStages,
+                                                kNumOfStages);
+  IntToTimeDeltaVector(expected_predictions1.dispatch_durations,
+                       std::vector<int>{10300, -1, -1, 300, 42500});
+  expected_predictions1.transition_duration = base::Microseconds(300);
+  IntToTimeDeltaVector(expected_predictions1.compositor_durations,
+                       std::vector<int>{300, -1, -1, -1, -1, 15200, 300});
+  expected_predictions1.total_duration = base::Microseconds(69200);
+
+  CompositorFrameReporter::EventLatencyInfo actual_predictions1 =
+      CompositorFrameReporter::EventLatencyInfo(kNumDispatchStages,
+                                                kNumOfStages);
+  IntToTimeDeltaVector(actual_predictions1.dispatch_durations,
+                       std::vector<int>{400, -1, -1, 300, 40000});
+  actual_predictions1.transition_duration = base::Microseconds(300);
+  IntToTimeDeltaVector(actual_predictions1.compositor_durations,
+                       std::vector<int>{300, -1, -1, -1, -1, 3600, 300});
+  actual_predictions1.total_duration = base::Microseconds(45200);
+  pipeline_reporter_->CalculateEventLatencyPrediction(
+      actual_predictions1, kLatencyPredictionDeviationThreshold);
+
+  std::unique_ptr<EventMetrics>& event_metrics =
+      pipeline_reporter_->events_metrics_for_testing()[0];
+  std::vector<std::string> attribution = event_metrics->GetHighLatencyStages();
+  EXPECT_EQ(2, (int)attribution.size());
+  EXPECT_EQ("GenerationToRendererCompositor", attribution[0]);
+  EXPECT_EQ("EndActivateToSubmitCompositorFrame", attribution[1]);
+  event_metrics->ClearHighLatencyStagesForTesting();
+
+  // Test 2
+  CompositorFrameReporter::EventLatencyInfo expected_predictions2 =
+      CompositorFrameReporter::EventLatencyInfo(kNumDispatchStages,
+                                                kNumOfStages);
+  IntToTimeDeltaVector(expected_predictions2.dispatch_durations,
+                       std::vector<int>{10225, -1, -1, 300, 12725});
+  expected_predictions2.transition_duration = base::Microseconds(300);
+
+  IntToTimeDeltaVector(expected_predictions2.compositor_durations,
+                       std::vector<int>{300, -1, -1, -1, -1, 12725, 300});
+  expected_predictions2.total_duration = base::Microseconds(36875);
+
+  CompositorFrameReporter::EventLatencyInfo actual_predictions2 =
+      CompositorFrameReporter::EventLatencyInfo(kNumDispatchStages,
+                                                kNumOfStages);
+  IntToTimeDeltaVector(actual_predictions2.dispatch_durations,
+                       std::vector<int>{300, -1, -1, 300, 300});
+  actual_predictions2.transition_duration = base::Microseconds(300);
+  IntToTimeDeltaVector(actual_predictions2.compositor_durations,
+                       std::vector<int>{300, -1, -1, -1, -1, 300, 300});
+  actual_predictions2.total_duration = base::Microseconds(2100);
+  pipeline_reporter_->CalculateEventLatencyPrediction(
+      actual_predictions2, kLatencyPredictionDeviationThreshold);
+
+  attribution = event_metrics->GetHighLatencyStages();
+  EXPECT_EQ(2, (int)attribution.size());
+  EXPECT_EQ("RendererMainProcessing", attribution[0]);
+  EXPECT_EQ("EndActivateToSubmitCompositorFrame", attribution[1]);
+
+  // Check that all prediction values are accurate.
+  for (int i = 0; i < kNumDispatchStages; i++) {
+    EXPECT_EQ(expected_predictions1.dispatch_durations[i],
+              actual_predictions1.dispatch_durations[i]);
+    EXPECT_EQ(expected_predictions2.dispatch_durations[i],
+              actual_predictions2.dispatch_durations[i]);
+  }
+  for (int i = 0; i < kNumOfStages; i++) {
+    EXPECT_EQ(expected_predictions1.compositor_durations[i],
+              actual_predictions1.compositor_durations[i]);
+    EXPECT_EQ(expected_predictions2.compositor_durations[i],
+              actual_predictions2.compositor_durations[i]);
+  }
+  EXPECT_EQ(expected_predictions1.transition_duration,
+            actual_predictions1.transition_duration);
+  EXPECT_EQ(expected_predictions1.total_duration,
+            actual_predictions1.total_duration);
+  EXPECT_EQ(expected_predictions2.transition_duration,
+            actual_predictions2.transition_duration);
+  EXPECT_EQ(expected_predictions2.total_duration,
+            actual_predictions2.total_duration);
+
+  pipeline_reporter_ = nullptr;
+}
+
 }  // namespace
 }  // namespace cc
diff --git a/cc/metrics/compositor_frame_reporting_controller.cc b/cc/metrics/compositor_frame_reporting_controller.cc
index 9258cf1..5e7816e 100644
--- a/cc/metrics/compositor_frame_reporting_controller.cc
+++ b/cc/metrics/compositor_frame_reporting_controller.cc
@@ -23,11 +23,11 @@
 using FrameTerminationStatus = CompositorFrameReporter::FrameTerminationStatus;
 
 constexpr char kTraceCategory[] = "cc,benchmark";
-constexpr int kNumOfStages = static_cast<int>(StageType::kStageTypeCount);
+constexpr int kNumOfStages = static_cast<int>(StageType::kStageTypeCount) - 1;
 constexpr int kNumDispatchStages =
     static_cast<int>(EventMetrics::DispatchStage::kMaxValue);
 constexpr base::TimeDelta kDefaultLatencyPredictionDeviationThreshold =
-    base::Milliseconds(8.33);
+    viz::BeginFrameArgs::DefaultInterval() / 2;
 }  // namespace
 
 CompositorFrameReportingController::CompositorFrameReportingController(
@@ -37,8 +37,8 @@
     : should_report_histograms_(should_report_histograms),
       layer_tree_host_id_(layer_tree_host_id),
       latency_ukm_reporter_(std::make_unique<LatencyUkmReporter>()),
-      previous_latency_predictions_main_(kNumOfStages, base::Microseconds(-1)),
-      previous_latency_predictions_impl_(kNumOfStages, base::Microseconds(-1)),
+      previous_latency_predictions_main_(base::Microseconds(-1)),
+      previous_latency_predictions_impl_(base::Microseconds(-1)),
       event_latency_predictions_(
           CompositorFrameReporter::EventLatencyInfo(kNumDispatchStages,
                                                     kNumOfStages)) {
@@ -483,18 +483,20 @@
     reporter->TerminateFrame(termination_status,
                              details.presentation_feedback.timestamp);
 
-    base::TimeDelta frame_interval =
+    base::TimeDelta latency_prediction_deviation_threshold =
         details.presentation_feedback.interval.is_zero()
             ? kDefaultLatencyPredictionDeviationThreshold
             : (details.presentation_feedback.interval) / 2;
     switch (reporter->get_reporter_type()) {
       case CompositorFrameReporter::ReporterType::kImpl:
-        reporter->CalculateStageLatencyPrediction(
-            previous_latency_predictions_impl_);
+        reporter->CalculateCompositorLatencyPrediction(
+            previous_latency_predictions_impl_,
+            latency_prediction_deviation_threshold);
         break;
       case CompositorFrameReporter::ReporterType::kMain:
-        reporter->CalculateStageLatencyPrediction(
-            previous_latency_predictions_main_);
+        reporter->CalculateCompositorLatencyPrediction(
+            previous_latency_predictions_main_,
+            latency_prediction_deviation_threshold);
         break;
     }
 
@@ -516,8 +518,8 @@
 
       // TODO(crbug.com/1334827): Consider using a separate container to
       // differentiate event predictions with and without a main dispatch stage.
-      reporter->CalculateEventLatencyPrediction(event_latency_predictions_,
-                                                frame_interval);
+      reporter->CalculateEventLatencyPrediction(
+          event_latency_predictions_, latency_prediction_deviation_threshold);
 
       // For presented frames, if `reporter` was cloned from another reporter,
       // and the original reporter is still alive, then check whether the cloned
diff --git a/cc/metrics/compositor_frame_reporting_controller.h b/cc/metrics/compositor_frame_reporting_controller.h
index 52485fe..321e492 100644
--- a/cc/metrics/compositor_frame_reporting_controller.h
+++ b/cc/metrics/compositor_frame_reporting_controller.h
@@ -237,10 +237,10 @@
   // interval of last begin frame args.
   base::TimeDelta last_interval_;
 
-  // These variables store the breakdown stage latency predictions made based
-  // on impl and main reporter's previous frames.
-  std::vector<base::TimeDelta> previous_latency_predictions_main_;
-  std::vector<base::TimeDelta> previous_latency_predictions_impl_;
+  CompositorFrameReporter::CompositorLatencyInfo
+      previous_latency_predictions_main_;
+  CompositorFrameReporter::CompositorLatencyInfo
+      previous_latency_predictions_impl_;
 
   // Container that stores the EventLatency stage latency predictions based on
   // previous event traces.
diff --git a/cc/metrics/event_latency_tracing_recorder.cc b/cc/metrics/event_latency_tracing_recorder.cc
index 8ad1d52d..138dac53 100644
--- a/cc/metrics/event_latency_tracing_recorder.cc
+++ b/cc/metrics/event_latency_tracing_recorder.cc
@@ -18,9 +18,45 @@
 constexpr char kTracingCategory[] = "cc,benchmark,input";
 constexpr base::TimeDelta high_latency_threshold = base::Milliseconds(90);
 
-// Returns the name of the event dispatch breakdown of EventLatency trace events
-// between `start_stage` and `end_stage`.
-constexpr const char* GetDispatchBreakdownName(
+constexpr perfetto::protos::pbzero::EventLatency::EventType ToProtoEnum(
+    EventMetrics::EventType event_type) {
+#define CASE(event_type, proto_event_type)  \
+  case EventMetrics::EventType::event_type: \
+    return perfetto::protos::pbzero::EventLatency::proto_event_type
+  switch (event_type) {
+    CASE(kMousePressed, MOUSE_PRESSED);
+    CASE(kMouseReleased, MOUSE_RELEASED);
+    CASE(kMouseWheel, MOUSE_WHEEL);
+    CASE(kKeyPressed, KEY_PRESSED);
+    CASE(kKeyReleased, KEY_RELEASED);
+    CASE(kTouchPressed, TOUCH_PRESSED);
+    CASE(kTouchReleased, TOUCH_RELEASED);
+    CASE(kTouchMoved, TOUCH_MOVED);
+    CASE(kGestureScrollBegin, GESTURE_SCROLL_BEGIN);
+    CASE(kGestureScrollUpdate, GESTURE_SCROLL_UPDATE);
+    CASE(kGestureScrollEnd, GESTURE_SCROLL_END);
+    CASE(kGestureDoubleTap, GESTURE_DOUBLE_TAP);
+    CASE(kGestureLongPress, GESTURE_LONG_PRESS);
+    CASE(kGestureLongTap, GESTURE_LONG_TAP);
+    CASE(kGestureShowPress, GESTURE_SHOW_PRESS);
+    CASE(kGestureTap, GESTURE_TAP);
+    CASE(kGestureTapCancel, GESTURE_TAP_CANCEL);
+    CASE(kGestureTapDown, GESTURE_TAP_DOWN);
+    CASE(kGestureTapUnconfirmed, GESTURE_TAP_UNCONFIRMED);
+    CASE(kGestureTwoFingerTap, GESTURE_TWO_FINGER_TAP);
+    CASE(kFirstGestureScrollUpdate, FIRST_GESTURE_SCROLL_UPDATE);
+    CASE(kMouseDragged, MOUSE_DRAGGED);
+    CASE(kGesturePinchBegin, GESTURE_PINCH_BEGIN);
+    CASE(kGesturePinchEnd, GESTURE_PINCH_END);
+    CASE(kGesturePinchUpdate, GESTURE_PINCH_UPDATE);
+    CASE(kInertialGestureScrollUpdate, INERTIAL_GESTURE_SCROLL_UPDATE);
+  }
+}
+
+}  // namespace
+
+// static
+const char* EventLatencyTracingRecorder::GetDispatchBreakdownName(
     EventMetrics::DispatchStage start_stage,
     EventMetrics::DispatchStage end_stage) {
   switch (start_stage) {
@@ -54,9 +90,8 @@
   }
 }
 
-// Returns the name of EventLatency breakdown between `dispatch_stage` and
-// `compositor_stage`.
-constexpr const char* GetDispatchToCompositorBreakdownName(
+// static
+const char* EventLatencyTracingRecorder::GetDispatchToCompositorBreakdownName(
     EventMetrics::DispatchStage dispatch_stage,
     CompositorFrameReporter::StageType compositor_stage) {
   switch (dispatch_stage) {
@@ -112,9 +147,8 @@
   }
 }
 
-// Returns the name of EventLatency breakdown between `dispatch_stage` and
-// termination for events not associated with a frame update.
-constexpr const char* GetDispatchToTerminationBreakdownName(
+// static
+const char* EventLatencyTracingRecorder::GetDispatchToTerminationBreakdownName(
     EventMetrics::DispatchStage dispatch_stage) {
   switch (dispatch_stage) {
     case EventMetrics::DispatchStage::kArrivedInRendererCompositor:
@@ -133,43 +167,6 @@
   }
 }
 
-constexpr perfetto::protos::pbzero::EventLatency::EventType ToProtoEnum(
-    EventMetrics::EventType event_type) {
-#define CASE(event_type, proto_event_type)  \
-  case EventMetrics::EventType::event_type: \
-    return perfetto::protos::pbzero::EventLatency::proto_event_type
-  switch (event_type) {
-    CASE(kMousePressed, MOUSE_PRESSED);
-    CASE(kMouseReleased, MOUSE_RELEASED);
-    CASE(kMouseWheel, MOUSE_WHEEL);
-    CASE(kKeyPressed, KEY_PRESSED);
-    CASE(kKeyReleased, KEY_RELEASED);
-    CASE(kTouchPressed, TOUCH_PRESSED);
-    CASE(kTouchReleased, TOUCH_RELEASED);
-    CASE(kTouchMoved, TOUCH_MOVED);
-    CASE(kGestureScrollBegin, GESTURE_SCROLL_BEGIN);
-    CASE(kGestureScrollUpdate, GESTURE_SCROLL_UPDATE);
-    CASE(kGestureScrollEnd, GESTURE_SCROLL_END);
-    CASE(kGestureDoubleTap, GESTURE_DOUBLE_TAP);
-    CASE(kGestureLongPress, GESTURE_LONG_PRESS);
-    CASE(kGestureLongTap, GESTURE_LONG_TAP);
-    CASE(kGestureShowPress, GESTURE_SHOW_PRESS);
-    CASE(kGestureTap, GESTURE_TAP);
-    CASE(kGestureTapCancel, GESTURE_TAP_CANCEL);
-    CASE(kGestureTapDown, GESTURE_TAP_DOWN);
-    CASE(kGestureTapUnconfirmed, GESTURE_TAP_UNCONFIRMED);
-    CASE(kGestureTwoFingerTap, GESTURE_TWO_FINGER_TAP);
-    CASE(kFirstGestureScrollUpdate, FIRST_GESTURE_SCROLL_UPDATE);
-    CASE(kMouseDragged, MOUSE_DRAGGED);
-    CASE(kGesturePinchBegin, GESTURE_PINCH_BEGIN);
-    CASE(kGesturePinchEnd, GESTURE_PINCH_END);
-    CASE(kGesturePinchUpdate, GESTURE_PINCH_UPDATE);
-    CASE(kInertialGestureScrollUpdate, INERTIAL_GESTURE_SCROLL_UPDATE);
-  }
-}
-
-}  // namespace
-
 // static
 void EventLatencyTracingRecorder::RecordEventLatencyTraceEvent(
     EventMetrics* event_metrics,
@@ -195,6 +192,12 @@
         bool has_high_latency =
             (termination_time - generated_timestamp) > high_latency_threshold;
         event_latency->set_has_high_latency(has_high_latency);
+        for (auto stage : event_metrics->GetHighLatencyStages()) {
+          // TODO(crbug.com/1334827): Consider changing the high_latency_stage
+          // type from a string to enum type in chrome_track_event.proto,
+          // similar to event_type.
+          event_latency->add_high_latency_stage(stage);
+        }
       });
 
   // Event dispatch stages.
diff --git a/cc/metrics/event_latency_tracing_recorder.h b/cc/metrics/event_latency_tracing_recorder.h
index 67202ca..75503719 100644
--- a/cc/metrics/event_latency_tracing_recorder.h
+++ b/cc/metrics/event_latency_tracing_recorder.h
@@ -15,6 +15,23 @@
 
 class EventLatencyTracingRecorder {
  public:
+  // Returns the name of the event dispatch breakdown of EventLatency trace
+  // events between `start_stage` and `end_stage`.
+  static const char* GetDispatchBreakdownName(
+      EventMetrics::DispatchStage start_stage,
+      EventMetrics::DispatchStage end_stage);
+
+  // Returns the name of EventLatency breakdown between `dispatch_stage` and
+  // `compositor_stage`.
+  static const char* GetDispatchToCompositorBreakdownName(
+      EventMetrics::DispatchStage dispatch_stage,
+      CompositorFrameReporter::StageType compositor_stage);
+
+  // Returns the name of EventLatency breakdown between `dispatch_stage` and
+  // termination for events not associated with a frame update.
+  static const char* GetDispatchToTerminationBreakdownName(
+      EventMetrics::DispatchStage dispatch_stage);
+
   static void RecordEventLatencyTraceEvent(
       EventMetrics* event_metrics,
       base::TimeTicks termination_time,
diff --git a/cc/metrics/event_metrics.cc b/cc/metrics/event_metrics.cc
index aff7043..62306ee 100644
--- a/cc/metrics/event_metrics.cc
+++ b/cc/metrics/event_metrics.cc
@@ -6,6 +6,7 @@
 
 #include <algorithm>
 #include <ostream>
+#include <string>
 #include <utility>
 
 #include "base/check.h"
@@ -272,6 +273,10 @@
   return kInterestingEvents[static_cast<int>(type_)].name;
 }
 
+void EventMetrics::SetHighLatencyStage(const std::string& stage) {
+  high_latency_stages_.push_back(stage);
+}
+
 void EventMetrics::SetDispatchStageTimestamp(DispatchStage stage) {
   DCHECK(dispatch_stage_timestamps_[static_cast<size_t>(stage)].is_null());
 
diff --git a/cc/metrics/event_metrics.h b/cc/metrics/event_metrics.h
index b8c43eab..ce133874 100644
--- a/cc/metrics/event_metrics.h
+++ b/cc/metrics/event_metrics.h
@@ -6,6 +6,7 @@
 #define CC_METRICS_EVENT_METRICS_H_
 
 #include <memory>
+#include <string>
 #include <vector>
 
 #include "base/memory/raw_ptr.h"
@@ -106,6 +107,12 @@
   // Returns a string representing event type.
   const char* GetTypeName() const;
 
+  void SetHighLatencyStage(const std::string& stage);
+  const std::vector<std::string>& GetHighLatencyStages() const {
+    return high_latency_stages_;
+  }
+  void ClearHighLatencyStagesForTesting() { high_latency_stages_.clear(); }
+
   void SetDispatchStageTimestamp(DispatchStage stage);
   base::TimeTicks GetDispatchStageTimestamp(DispatchStage stage) const;
 
@@ -167,6 +174,8 @@
 
   EventType type_;
 
+  std::vector<std::string> high_latency_stages_;
+
   const raw_ptr<const base::TickClock> tick_clock_;
 
   // Timestamps of different stages of event dispatch. Timestamps are set as the
diff --git a/chrome/VERSION b/chrome/VERSION
index 74c57bb..86dacc6 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=106
 MINOR=0
-BUILD=5230
+BUILD=5231
 PATCH=0
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index d6d99fc..fed5085 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -1080,6 +1080,7 @@
     "//chrome/browser/ui/android/autofill/internal:junit",
     "//chrome/browser/ui/android/default_browser_promo:java",
     "//chrome/browser/ui/android/default_browser_promo:junit",
+    "//chrome/browser/ui/android/fast_checkout:junit",
     "//chrome/browser/ui/android/favicon:java",
     "//chrome/browser/ui/android/layouts:java",
     "//chrome/browser/ui/android/layouts:junit",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/InstalledWebappBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/InstalledWebappBridge.java
index 05333347..ba125dc 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/InstalledWebappBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/InstalledWebappBridge.java
@@ -68,6 +68,13 @@
     }
 
     @CalledByNative
+    @ContentSettingValues
+    private static int getPermission(@ContentSettingsType int type, String origin) {
+        return InstalledWebappPermissionManager.get().getPermission(
+                type, Origin.create(Uri.parse(origin)));
+    }
+
+    @CalledByNative
     private static String getOriginFromPermission(Permission permission) {
         return permission.origin.toString();
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/InstalledWebappPermissionManager.java b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/InstalledWebappPermissionManager.java
index b33304d..7f6cd5e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/InstalledWebappPermissionManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/InstalledWebappPermissionManager.java
@@ -221,9 +221,8 @@
         }
     }
 
-    @VisibleForTesting
     @ContentSettingValues
-    int getPermission(@ContentSettingsType int type, Origin origin) {
+    public int getPermission(@ContentSettingsType int type, Origin origin) {
         switch (type) {
             case ContentSettingsType.NOTIFICATIONS: {
                 @ContentSettingValues
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java
index ddb41aae..554ad43 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelper.java
@@ -183,7 +183,6 @@
     private float mLeftMargin;
     private float mRightMargin;
     private final boolean mIncognito;
-    private float mBrightness;
     // Whether the CascadingStripStacker should be used.
     private boolean mShouldCascadeTabs;
     private boolean mIsFirstLayoutPass;
@@ -252,7 +251,6 @@
                 res.getString(R.string.accessibility_toolbar_btn_new_incognito_tab));
         mContext = context;
         mIncognito = incognito;
-        mBrightness = 1.f;
 
         // Create tab menu
         mTabMenu = new ListPopupWindow(mContext);
@@ -329,28 +327,6 @@
     }
 
     /**
-     * @return The brightness of background tabs in the tabstrip.
-     */
-    public float getBackgroundTabBrightness() {
-        // TODO(crbug.com/1347591): Remove unused brightness code.
-        return mInReorderMode ? 0.75f : 1.0f;
-    }
-
-    /**
-     * Sets the brightness for the entire tabstrip.
-     */
-    public void setBrightness(float brightness) {
-        mBrightness = brightness;
-    }
-
-    /**
-     * @return The brightness of the entire tabstrip.
-     */
-    public float getBrightness() {
-        return mBrightness;
-    }
-
-    /**
      * @return The opacity to use for the fade on the left side of the tab strip.
      */
     public float getLeftFadeOpacity() {
@@ -1752,13 +1728,15 @@
                 ANIM_TAB_MOVE_MS);
         mRunningAnimator.start();
 
-        // 3. Clear any tab group margins if they are enabled.
+        // 3. Un-dim the background tabs.
+        setBackgroundTabsDimmed(false);
+
+        // 4. Clear any tab group margins if they are enabled.
         if (isTabGroupsEnabled()) {
             resetTabGroupMargins();
-            setBackgroundTabsDimmed(false);
         }
 
-        // 4. Request an update.
+        // 5. Request an update.
         mUpdateHost.requestUpdate();
     }
 
@@ -1838,7 +1816,8 @@
         for (int i = 0; i < mStripTabs.length; i++) {
             final StripLayoutTab tab = mStripTabs[i];
 
-            if (mTabGroupModelFilter.getRootId(getTabById(tab.getId())) == groupId) {
+            if (mTabGroupModelFilter.getRootId(getTabById(tab.getId())) == groupId
+                    && tab != mInteractingTab) {
                 tab.setBrightness(dimmed ? BACKGROUND_TAB_BRIGHTNESS_DIMMED
                                          : BACKGROUND_TAB_BRIGHTNESS_DEFAULT);
             }
@@ -1919,7 +1898,7 @@
         if (mHoveringOverGroup != hoveringOverGroup) {
             // 1.a. Reset hover variables.
             mHoveringOverGroup = hoveringOverGroup;
-            mHoverStartTime = 0L;
+            mHoverStartTime = INVALID_TIME;
             mHoverStartOffset = 0;
 
             // 1.b. Set tab group dim as necessary.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java
index b927bda..e374b9f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManager.java
@@ -462,20 +462,6 @@
         return getActiveStripLayoutHelper().getRightFadeOpacity();
     }
 
-    /**
-     * @return The brightness of background tabs in the tabstrip.
-     */
-    public float getBackgroundTabBrightness() {
-        return getActiveStripLayoutHelper().getBackgroundTabBrightness();
-    }
-
-    /**
-     * @return The brightness of the entire tabstrip.
-     */
-    public float getBrightness() {
-        return getActiveStripLayoutHelper().getBrightness();
-    }
-
     /** Update the title cache for the available tabs in the model. */
     private void updateTitleCacheForInit() {
         LayerTitleCache titleCache = mLayerTitleCacheSupplier.get();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/TabStripSceneLayer.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/TabStripSceneLayer.java
index 1406614..eaf72ea3 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/TabStripSceneLayer.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/TabStripSceneLayer.java
@@ -114,8 +114,7 @@
         final float width = layoutHelper.getWidth() * mDpToPx;
         final float height = layoutHelper.getHeight() * mDpToPx;
         TabStripSceneLayerJni.get().updateTabStripLayer(mNativePtr, TabStripSceneLayer.this, width,
-                height, yOffset * mDpToPx, layoutHelper.getBackgroundTabBrightness(),
-                layoutHelper.getBrightness(), shouldReaddBackground(layoutHelper.getOrientation()));
+                height, yOffset * mDpToPx, shouldReaddBackground(layoutHelper.getOrientation()));
 
         updateStripScrim(layoutHelper.getStripScrim());
 
@@ -200,8 +199,7 @@
                 long nativeTabStripSceneLayer, TabStripSceneLayer caller, boolean visible);
         void finishBuildingFrame(long nativeTabStripSceneLayer, TabStripSceneLayer caller);
         void updateTabStripLayer(long nativeTabStripSceneLayer, TabStripSceneLayer caller,
-                float width, float height, float yOffset, float backgroundTabBrightness,
-                float brightness, boolean shouldReadBackground);
+                float width, float height, float yOffset, boolean shouldReadBackground);
         void updateStripScrim(long nativeTabStripSceneLayer, TabStripSceneLayer caller, float x,
                 float y, float width, float height, int color, float alpha);
         void updateNewTabButton(long nativeTabStripSceneLayer, TabStripSceneLayer caller,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/PictureInPictureActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/media/PictureInPictureActivity.java
index 16d1fdeb..3677d083 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/PictureInPictureActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/PictureInPictureActivity.java
@@ -163,28 +163,28 @@
 
         private MediaActionButtonsManager() {
             mPreviousTrack = createRemoteAction(MediaSessionAction.PREVIOUS_TRACK,
-                    R.drawable.ic_skip_previous_white_36dp, R.string.accessibility_previous_track);
-            mPlay = createRemoteAction(MediaSessionAction.PLAY, R.drawable.ic_play_arrow_white_36dp,
+                    R.drawable.ic_skip_previous_white_24dp, R.string.accessibility_previous_track);
+            mPlay = createRemoteAction(MediaSessionAction.PLAY, R.drawable.ic_play_arrow_white_24dp,
                     R.string.accessibility_play);
-            mPause = createRemoteAction(MediaSessionAction.PAUSE, R.drawable.ic_pause_white_36dp,
+            mPause = createRemoteAction(MediaSessionAction.PAUSE, R.drawable.ic_pause_white_24dp,
                     R.string.accessibility_pause);
             mReplay = createRemoteAction(MediaSessionAction.PLAY, R.drawable.ic_replay_white_24dp,
                     R.string.accessibility_replay);
             mNextTrack = createRemoteAction(MediaSessionAction.NEXT_TRACK,
-                    R.drawable.ic_skip_next_white_36dp, R.string.accessibility_next_track);
+                    R.drawable.ic_skip_next_white_24dp, R.string.accessibility_next_track);
             mHangUp = createRemoteAction(MediaSessionAction.HANG_UP,
-                    R.drawable.ic_call_end_white_36dp, R.string.accessibility_hang_up);
+                    R.drawable.ic_call_end_white_24dp, R.string.accessibility_hang_up);
             mMicrophone = new ToggleRemoteAction(
                     createRemoteAction(MediaSessionAction.TOGGLE_MICROPHONE,
-                            R.drawable.ic_mic_white_36dp, R.string.accessibility_mute_microphone),
+                            R.drawable.ic_mic_white_24dp, R.string.accessibility_mute_microphone),
                     createRemoteAction(MediaSessionAction.TOGGLE_MICROPHONE,
-                            R.drawable.ic_mic_off_white_36dp,
+                            R.drawable.ic_mic_off_white_24dp,
                             R.string.accessibility_unmute_microphone));
-            mCamera = new ToggleRemoteAction(createRemoteAction(MediaSessionAction.TOGGLE_CAMERA,
-                                                     R.drawable.ic_videocam_white_36dp,
-                                                     R.string.accessibility_turn_off_camera),
+            mCamera = new ToggleRemoteAction(
                     createRemoteAction(MediaSessionAction.TOGGLE_CAMERA,
-                            R.drawable.ic_videocam_off_white_36dp,
+                            R.drawable.ic_videocam_24dp, R.string.accessibility_turn_off_camera),
+                    createRemoteAction(MediaSessionAction.TOGGLE_CAMERA,
+                            R.drawable.ic_videocam_off_white_24dp,
                             R.string.accessibility_turn_on_camera));
 
             mPlaybackState = PlaybackState.END_OF_VIDEO;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tracing/TracingNotificationManager.java b/chrome/android/java/src/org/chromium/chrome/browser/tracing/TracingNotificationManager.java
index 3f4ccbd9..ae2969d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tracing/TracingNotificationManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tracing/TracingNotificationManager.java
@@ -115,7 +115,7 @@
                         .setContentTitle(title)
                         .setContentText(message)
                         .setOngoing(true)
-                        .addAction(R.drawable.ic_stop_white_36dp, MSG_STOP,
+                        .addAction(R.drawable.ic_stop_white_24dp, MSG_STOP,
                                 TracingNotificationServiceImpl.getStopRecordingIntent(context));
         showNotification(sTracingActiveNotificationBuilder.build());
     }
diff --git a/chrome/app/theme/chromium/win/chromium.ico b/chrome/app/theme/chromium/win/chromium.ico
index 1cfe8f13..d0aa0c2 100644
--- a/chrome/app/theme/chromium/win/chromium.ico
+++ b/chrome/app/theme/chromium/win/chromium.ico
Binary files differ
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index f7fd2499..3b1d0dc 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -4792,12 +4792,10 @@
       "icon_loader_chromeos.cc",
       "lifetime/application_lifetime_chromeos.cc",
       "lifetime/application_lifetime_chromeos.h",
-      "media/chromeos_login_media_access_handler.cc",
-      "media/chromeos_login_media_access_handler.h",
+      "media/chromeos_login_and_lock_media_access_handler.cc",
+      "media/chromeos_login_and_lock_media_access_handler.h",
       "media/public_session_media_access_handler.cc",
       "media/public_session_media_access_handler.h",
-      "media/public_session_tab_capture_access_handler.cc",
-      "media/public_session_tab_capture_access_handler.h",
       "media/webrtc/desktop_media_list_ash.cc",
       "media/webrtc/desktop_media_list_ash.h",
       "media/webrtc/window_icon_util_chromeos.cc",
@@ -5351,6 +5349,8 @@
       "chromeos/app_mode/chrome_kiosk_external_loader_broker.h",
       "chromeos/app_mode/kiosk_app_external_loader.cc",
       "chromeos/app_mode/kiosk_app_external_loader.h",
+      "chromeos/app_mode/kiosk_app_service_launcher.cc",
+      "chromeos/app_mode/kiosk_app_service_launcher.h",
       "chromeos/app_mode/kiosk_settings_navigation_throttle.cc",
       "chromeos/app_mode/kiosk_settings_navigation_throttle.h",
       "chromeos/app_mode/startup_app_launcher_update_checker.cc",
@@ -5477,6 +5477,8 @@
       "lacros/for_which_extension_type.h",
       "lacros/force_installed_tracker_lacros.cc",
       "lacros/force_installed_tracker_lacros.h",
+      "lacros/fullscreen_controller_client_lacros.cc",
+      "lacros/fullscreen_controller_client_lacros.h",
       "lacros/identity_manager_lacros.cc",
       "lacros/identity_manager_lacros.h",
       "lacros/lacros_butter_bar.cc",
@@ -5588,6 +5590,7 @@
       "//chromeos/startup",
       "//chromeos/ui/base",
       "//chromeos/ui/frame",
+      "//chromeos/ui/wm",
       "//components/arc/common",
       "//extensions/browser/updater",
       "//ui/chromeos/styles:cros_styles_views",
diff --git a/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.cc b/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.cc
index b273d21..1d4d5d2 100644
--- a/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.cc
+++ b/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.cc
@@ -31,8 +31,6 @@
       left_fade_(cc::UIResourceLayer::Create()),
       right_fade_(cc::UIResourceLayer::Create()),
       model_selector_button_(cc::UIResourceLayer::Create()),
-      background_tab_brightness_(1.f),
-      brightness_(1.f),
       write_index_(0),
       content_tree_(nullptr) {
   new_tab_button_->SetIsDrawable(true);
@@ -115,23 +113,12 @@
                                              jfloat width,
                                              jfloat height,
                                              jfloat y_offset,
-                                             jfloat background_tab_brightness,
-                                             jfloat brightness,
                                              jboolean should_readd_background) {
-  background_tab_brightness_ = background_tab_brightness;
   gfx::RectF content(0, y_offset, width, height);
   layer()->SetPosition(gfx::PointF(0, y_offset));
   tab_strip_layer_->SetBounds(gfx::Size(width, height));
   scrollable_strip_layer_->SetBounds(gfx::Size(width, height));
 
-  if (brightness != brightness_) {
-    brightness_ = brightness;
-    cc::FilterOperations filters;
-    if (brightness_ < 1.f)
-      filters.Append(cc::FilterOperation::CreateBrightnessFilter(brightness_));
-    tab_strip_layer_->SetFilters(filters);
-  }
-
   // Content tree should not be affected by tab strip scene layer visibility.
   if (content_tree_)
     content_tree_->layer()->SetPosition(gfx::PointF(0, -y_offset));
diff --git a/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.h b/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.h
index 3b36b91a..4a85d0d 100644
--- a/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.h
+++ b/chrome/browser/android/compositor/scene_layer/tab_strip_scene_layer.h
@@ -53,8 +53,6 @@
                            jfloat width,
                            jfloat height,
                            jfloat y_offset,
-                           jfloat background_tab_brightness,
-                           jfloat brightness,
                            jboolean should_readd_background);
 
   void UpdateStripScrim(JNIEnv* env,
@@ -148,8 +146,6 @@
   scoped_refptr<cc::UIResourceLayer> right_fade_;
   scoped_refptr<cc::UIResourceLayer> model_selector_button_;
 
-  float background_tab_brightness_;
-  float brightness_;
   unsigned write_index_;
   TabHandleLayerList tab_handle_layers_;
   raw_ptr<SceneLayer> content_tree_;
diff --git a/chrome/browser/apps/app_service/publishers/extension_apps_chromeos.cc b/chrome/browser/apps/app_service/publishers/extension_apps_chromeos.cc
index 48e4c3e5..0b01525 100644
--- a/chrome/browser/apps/app_service/publishers/extension_apps_chromeos.cc
+++ b/chrome/browser/apps/app_service/publishers/extension_apps_chromeos.cc
@@ -718,11 +718,9 @@
     return;
   }
 
-  const base::Value* disabled_system_features_pref =
-      local_state->GetList(policy::policy_prefs::kSystemFeaturesDisableList);
-  if (!disabled_system_features_pref) {
-    return;
-  }
+  const base::Value::List& disabled_system_features_pref =
+      local_state->GetValueList(
+          policy::policy_prefs::kSystemFeaturesDisableList);
 
   const bool is_pref_disabled_mode_hidden =
       local_state->GetString(
@@ -1026,12 +1024,12 @@
 }
 
 void ExtensionAppsChromeOs::UpdateAppDisabledState(
-    const base::Value* disabled_system_features_pref,
+    const base::Value::List& disabled_system_features_pref,
     int feature,
     const std::string& app_id,
     bool is_disabled_mode_changed) {
-  const bool is_disabled = base::Contains(
-      disabled_system_features_pref->GetListDeprecated(), base::Value(feature));
+  const bool is_disabled =
+      base::Contains(disabled_system_features_pref, base::Value(feature));
   // Sometimes the policy is updated before the app is installed, so this way
   // the disabled_apps_ is updated regardless the Publish should happen or not
   // and the app will be published with the correct readiness upon its
diff --git a/chrome/browser/apps/app_service/publishers/extension_apps_chromeos.h b/chrome/browser/apps/app_service/publishers/extension_apps_chromeos.h
index 2e83ec8..c416f01 100644
--- a/chrome/browser/apps/app_service/publishers/extension_apps_chromeos.h
+++ b/chrome/browser/apps/app_service/publishers/extension_apps_chromeos.h
@@ -176,10 +176,11 @@
 
   content::WebContents* LaunchImpl(AppLaunchParams&& params) override;
 
-  void UpdateAppDisabledState(const base::Value* disabled_system_features_pref,
-                              int feature,
-                              const std::string& app_id,
-                              bool is_disabled_mode_changed);
+  void UpdateAppDisabledState(
+      const base::Value::List& disabled_system_features_pref,
+      int feature,
+      const std::string& app_id,
+      bool is_disabled_mode_changed);
 
   void LaunchExtension(const std::string& app_id,
                        int32_t event_flags,
diff --git a/chrome/browser/apps/app_service/publishers/web_apps_crosapi_browsertest.cc b/chrome/browser/apps/app_service/publishers/web_apps_crosapi_browsertest.cc
index 03ca64d9..4c479f1 100644
--- a/chrome/browser/apps/app_service/publishers/web_apps_crosapi_browsertest.cc
+++ b/chrome/browser/apps/app_service/publishers/web_apps_crosapi_browsertest.cc
@@ -13,16 +13,12 @@
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
-#include "chrome/browser/ash/crosapi/crosapi_ash.h"
-#include "chrome/browser/ash/crosapi/crosapi_manager.h"
-#include "chrome/browser/ash/crosapi/test_controller_ash.h"
+#include "chrome/browser/ash/crosapi/ash_requires_lacros_browsertestbase.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/views/apps/app_dialog/app_uninstall_dialog_view.h"
 #include "chrome/browser/web_applications/test/app_registration_waiter.h"
 #include "chrome/browser/web_applications/web_app_id.h"
 #include "chrome/common/chrome_features.h"
-#include "chrome/test/base/chromeos/ash_browser_test_starter.h"
-#include "chrome/test/base/in_process_browser_test.h"
 #include "chromeos/crosapi/mojom/test_controller.mojom-test-utils.h"
 #include "components/services/app_service/public/cpp/app_launch_util.h"
 #include "content/public/test/browser_test.h"
@@ -107,7 +103,8 @@
 
 }  // namespace
 
-class WebAppsCrosapiBrowserTest : public InProcessBrowserTest {
+class WebAppsCrosapiBrowserTest
+    : public crosapi::AshRequiresLacrosBrowserTestBase {
  public:
   WebAppsCrosapiBrowserTest() {
     scoped_feature_list_.InitWithFeatures(
@@ -116,29 +113,20 @@
   ~WebAppsCrosapiBrowserTest() override = default;
 
  protected:
-  void SetUpInProcessBrowserTestFixture() override {
-    if (!ash_starter_.HasLacrosArgument()) {
-      return;
-    }
-    ASSERT_TRUE(ash_starter_.PrepareEnvironmentForLacros());
-  }
-
   void SetUpOnMainThread() override {
-    if (!ash_starter_.HasLacrosArgument()) {
+    crosapi::AshRequiresLacrosBrowserTestBase::SetUpOnMainThread();
+    if (!HasLacrosArgument()) {
       return;
     }
-    auto* manager = crosapi::CrosapiManager::Get();
-    test_controller_ash_ = std::make_unique<crosapi::TestControllerAsh>();
-    manager->crosapi_ash()->SetTestControllerForTesting(
-        test_controller_ash_.get());
 
-    ash_starter_.StartLacros(this);
+    web_app::AppTypeInitializationWaiter(profile(), apps::AppType::kWeb)
+        .Await();
   }
 
   std::string InstallWebApp(const std::string& start_url,
                             apps::WindowMode mode) {
     crosapi::mojom::StandaloneBrowserTestControllerAsyncWaiter waiter(
-        test_controller_ash_->GetStandaloneBrowserTestController().get());
+        GetStandaloneBrowserTestController());
     std::string app_id;
     waiter.InstallWebApp(start_url, mode, &app_id);
     web_app::AppRegistrationWaiter(browser()->profile(), app_id).Await();
@@ -151,16 +139,12 @@
     return apps::AppServiceProxyFactory::GetForProfile(profile());
   }
 
-  const test::AshBrowserTestStarter& ash_starter() { return ash_starter_; }
-
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
-  test::AshBrowserTestStarter ash_starter_;
-  std::unique_ptr<crosapi::TestControllerAsh> test_controller_ash_;
 };
 
 IN_PROC_BROWSER_TEST_F(WebAppsCrosapiBrowserTest, PinUsingContextMenu) {
-  if (!ash_starter().HasLacrosArgument()) {
+  if (!HasLacrosArgument()) {
     return;
   }
 
@@ -228,7 +212,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(WebAppsCrosapiBrowserTest, Uninstall) {
-  if (!ash_starter().HasLacrosArgument()) {
+  if (!HasLacrosArgument()) {
     return;
   }
 
diff --git a/chrome/browser/apps/app_service/webapk/webapk_prefs.cc b/chrome/browser/apps/app_service/webapk/webapk_prefs.cc
index ee49c82..efefa03 100644
--- a/chrome/browser/apps/app_service/webapk/webapk_prefs.cc
+++ b/chrome/browser/apps/app_service/webapk/webapk_prefs.cc
@@ -55,14 +55,13 @@
 
 absl::optional<std::string> GetWebApkPackageName(Profile* profile,
                                                  const std::string& app_id) {
-  const base::Value* app_dict = profile->GetPrefs()
-                                    ->GetDictionary(kGeneratedWebApksPref)
-                                    ->FindDictKey(app_id);
+  const base::Value::Dict* app_dict =
+      profile->GetPrefs()->GetValueDict(kGeneratedWebApksPref).FindDict(app_id);
   if (!app_dict) {
     return absl::nullopt;
   }
 
-  const std::string* package_name = app_dict->FindStringKey(kPackageNameKey);
+  const std::string* package_name = app_dict->FindString(kPackageNameKey);
   if (!package_name) {
     return absl::nullopt;
   }
diff --git a/chrome/browser/ash/borealis/borealis_disk_manager_dispatcher_unittest.cc b/chrome/browser/ash/borealis/borealis_disk_manager_dispatcher_unittest.cc
index ee3d838b6..3c2957b 100644
--- a/chrome/browser/ash/borealis/borealis_disk_manager_dispatcher_unittest.cc
+++ b/chrome/browser/ash/borealis/borealis_disk_manager_dispatcher_unittest.cc
@@ -6,6 +6,7 @@
 
 #include "chrome/browser/ash/borealis/borealis_disk_manager.h"
 #include "chrome/browser/ash/borealis/borealis_disk_manager_impl.h"
+#include "chrome/browser/ash/borealis/borealis_metrics.h"
 #include "chrome/browser/ash/borealis/testing/callback_factory.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
diff --git a/chrome/browser/ash/borealis/borealis_disk_manager_impl.cc b/chrome/browser/ash/borealis/borealis_disk_manager_impl.cc
index 9a04b09..7bc4edbc 100644
--- a/chrome/browser/ash/borealis/borealis_disk_manager_impl.cc
+++ b/chrome/browser/ash/borealis/borealis_disk_manager_impl.cc
@@ -95,21 +95,17 @@
 
 BorealisDiskManagerImpl::BorealisDiskManagerImpl(const BorealisContext* context)
     : context_(context),
-      request_count_(0),
+      service_(borealis::BorealisService::GetForProfile(context_->profile())),
       free_space_provider_(std::make_unique<FreeSpaceProvider>()),
       weak_factory_(this) {
-  borealis::BorealisService::GetForProfile(context_->profile())
-      ->DiskManagerDispatcher()
-      .SetDiskManagerDelegate(this);
+  service_->DiskManagerDispatcher().SetDiskManagerDelegate(this);
 }
 
 BorealisDiskManagerImpl::~BorealisDiskManagerImpl() {
   if (DiskManagementVersion() == DiskManagementVersion::CROSDISK) {
     RecordBorealisDiskClientNumRequestsPerSessionHistogram(request_count_);
   }
-  borealis::BorealisService::GetForProfile(context_->profile())
-      ->DiskManagerDispatcher()
-      .RemoveDiskManagerDelegate(this);
+  service_->DiskManagerDispatcher().RemoveDiskManagerDelegate(this);
 }
 
 // Helper function that returns how many bytes the |available_space| would
diff --git a/chrome/browser/ash/borealis/borealis_disk_manager_impl.h b/chrome/browser/ash/borealis/borealis_disk_manager_impl.h
index 18f5330b..d4a4bc8 100644
--- a/chrome/browser/ash/borealis/borealis_disk_manager_impl.h
+++ b/chrome/browser/ash/borealis/borealis_disk_manager_impl.h
@@ -7,8 +7,9 @@
 
 #include "base/callback.h"
 #include "base/memory/weak_ptr.h"
-#include "chrome/browser/ash/borealis/borealis_context_manager.h"
+#include "chrome/browser/ash/borealis/borealis_context.h"
 #include "chrome/browser/ash/borealis/borealis_disk_manager.h"
+#include "chrome/browser/ash/borealis/borealis_service.h"
 
 namespace borealis {
 // Amount of space, in bytes, that borealis needs to leave free on the host.
@@ -110,7 +111,8 @@
                Described<BorealisSyncDiskSizeResult>> success_or_error);
 
   const BorealisContext* const context_;
-  int request_count_;
+  BorealisService* const service_;
+  int request_count_{0};
   std::unique_ptr<BuildDiskInfo> build_disk_info_transition_;
   std::unique_ptr<ResizeDisk> resize_disk_transition_;
   std::unique_ptr<SyncDisk> sync_disk_transition_;
diff --git a/chrome/browser/ash/borealis/borealis_disk_manager_unittest.cc b/chrome/browser/ash/borealis/borealis_disk_manager_unittest.cc
index a98e3d2..703983a7 100644
--- a/chrome/browser/ash/borealis/borealis_disk_manager_unittest.cc
+++ b/chrome/browser/ash/borealis/borealis_disk_manager_unittest.cc
@@ -15,6 +15,7 @@
 #include "chrome/browser/ash/borealis/borealis_context.h"
 #include "chrome/browser/ash/borealis/borealis_disk_manager_dispatcher.h"
 #include "chrome/browser/ash/borealis/borealis_features.h"
+#include "chrome/browser/ash/borealis/borealis_metrics.h"
 #include "chrome/browser/ash/borealis/borealis_service_fake.h"
 #include "chrome/browser/ash/borealis/borealis_window_manager.h"
 #include "chrome/browser/ash/borealis/testing/callback_factory.h"
diff --git a/chrome/browser/ash/bruschetta/bruschetta_mount_provider.cc b/chrome/browser/ash/bruschetta/bruschetta_mount_provider.cc
index 290ae5b..e6af40cb 100644
--- a/chrome/browser/ash/bruschetta/bruschetta_mount_provider.cc
+++ b/chrome/browser/ash/bruschetta/bruschetta_mount_provider.cc
@@ -59,9 +59,9 @@
     std::move(callback).Run(false, 0, 0, base::FilePath());
     return;
   }
+  auto* tracker = guest_os::GuestOsSessionTracker::GetForProfile(profile_);
 
-  auto info = guest_os::GuestOsSessionTracker::GetForProfile(profile_)->GetInfo(
-      guest_id_);
+  auto info = tracker->GetInfo(guest_id_);
   if (!info) {
     // Shouldn't happen unless you managed to shutdown the VM at the same
     // instant as you booted it.
@@ -69,6 +69,9 @@
     std::move(callback).Run(false, 0, 0, base::FilePath());
     return;
   }
+  unmount_subscription_ = tracker->RunOnShutdown(
+      guest_id_, base::BindOnce(&BruschettaMountProvider::Unmount,
+                                weak_ptr_factory_.GetWeakPtr()));
   std::move(callback).Run(true, info->cid, info->sftp_vsock_port,
                           info->homedir);
 }
diff --git a/chrome/browser/ash/bruschetta/bruschetta_mount_provider.h b/chrome/browser/ash/bruschetta/bruschetta_mount_provider.h
index 808daa7..2112e22 100644
--- a/chrome/browser/ash/bruschetta/bruschetta_mount_provider.h
+++ b/chrome/browser/ash/bruschetta/bruschetta_mount_provider.h
@@ -39,6 +39,7 @@
   void OnRunning(PrepareCallback callback, BruschettaResult result);
   Profile* profile_;
   guest_os::GuestId guest_id_;
+  base::CallbackListSubscription unmount_subscription_;
   base::WeakPtrFactory<BruschettaMountProvider> weak_ptr_factory_{this};
 };
 
diff --git a/chrome/browser/ash/crosapi/BUILD.gn b/chrome/browser/ash/crosapi/BUILD.gn
index ed8d45d..55de272 100644
--- a/chrome/browser/ash/crosapi/BUILD.gn
+++ b/chrome/browser/ash/crosapi/BUILD.gn
@@ -117,6 +117,8 @@
     "files_app_launcher.h",
     "force_installed_tracker_ash.cc",
     "force_installed_tracker_ash.h",
+    "fullscreen_controller_ash.cc",
+    "fullscreen_controller_ash.h",
     "geolocation_service_ash.cc",
     "geolocation_service_ash.h",
     "hosted_app_util.cc",
@@ -350,6 +352,8 @@
   testonly = true
 
   sources = [
+    "ash_requires_lacros_browsertestbase.cc",
+    "ash_requires_lacros_browsertestbase.h",
     "fake_browser_manager.cc",
     "fake_browser_manager.h",
     "input_method_test_interface_ash.cc",
@@ -393,6 +397,7 @@
     "download_controller_ash_unittest.cc",
     "fake_migration_progress_tracker.h",
     "field_trial_service_ash_unittest.cc",
+    "fullscreen_controller_ash_unittest.cc",
     "geolocation_service_ash_unittest.cc",
     "keystore_service_ash_unittest.cc",
     "lacros_availability_policy_observer_unittest.cc",
diff --git a/chrome/browser/ash/crosapi/ash_requires_lacros_browsertestbase.cc b/chrome/browser/ash/crosapi/ash_requires_lacros_browsertestbase.cc
new file mode 100644
index 0000000..8502f376
--- /dev/null
+++ b/chrome/browser/ash/crosapi/ash_requires_lacros_browsertestbase.cc
@@ -0,0 +1,53 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/crosapi/ash_requires_lacros_browsertestbase.h"
+
+#include "ash/constants/ash_features.h"
+#include "base/location.h"
+#include "base/one_shot_event.h"
+#include "base/run_loop.h"
+#include "chrome/browser/ash/crosapi/crosapi_ash.h"
+#include "chrome/browser/ash/crosapi/crosapi_manager.h"
+#include "mojo/public/cpp/bindings/remote.h"
+
+namespace crosapi {
+
+AshRequiresLacrosBrowserTestBase::AshRequiresLacrosBrowserTestBase() {
+  scoped_feature_list_.InitAndEnableFeature(chromeos::features::kLacrosSupport);
+}
+
+AshRequiresLacrosBrowserTestBase::~AshRequiresLacrosBrowserTestBase() = default;
+
+void AshRequiresLacrosBrowserTestBase::SetUpInProcessBrowserTestFixture() {
+  if (!ash_starter_.HasLacrosArgument()) {
+    return;
+  }
+  ASSERT_TRUE(ash_starter_.PrepareEnvironmentForLacros());
+}
+
+void AshRequiresLacrosBrowserTestBase::SetUpOnMainThread() {
+  if (!ash_starter_.HasLacrosArgument()) {
+    return;
+  }
+  auto* manager = crosapi::CrosapiManager::Get();
+  test_controller_ash_ = std::make_unique<crosapi::TestControllerAsh>();
+  manager->crosapi_ash()->SetTestControllerForTesting(  // IN-TEST
+      test_controller_ash_.get());
+
+  ash_starter_.StartLacros(this);
+
+  base::RunLoop run_loop;
+  test_controller_ash_->on_standalone_browser_test_controller_bound().Post(
+      FROM_HERE, run_loop.QuitClosure());
+  run_loop.Run();
+}
+
+mojom::StandaloneBrowserTestController*
+AshRequiresLacrosBrowserTestBase::GetStandaloneBrowserTestController() {
+  CHECK(test_controller_ash_);
+  return test_controller_ash_->GetStandaloneBrowserTestController().get();
+}
+
+}  // namespace crosapi
diff --git a/chrome/browser/ash/crosapi/ash_requires_lacros_browsertestbase.h b/chrome/browser/ash/crosapi/ash_requires_lacros_browsertestbase.h
new file mode 100644
index 0000000..2416141
--- /dev/null
+++ b/chrome/browser/ash/crosapi/ash_requires_lacros_browsertestbase.h
@@ -0,0 +1,48 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ASH_CROSAPI_ASH_REQUIRES_LACROS_BROWSERTESTBASE_H_
+#define CHROME_BROWSER_ASH_CROSAPI_ASH_REQUIRES_LACROS_BROWSERTESTBASE_H_
+
+#include "chrome/test/base/in_process_browser_test.h"
+
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/ash/crosapi/test_controller_ash.h"
+#include "chrome/test/base/chromeos/ash_browser_test_starter.h"
+#include "chromeos/crosapi/mojom/test_controller.mojom.h"
+#include "mojo/public/cpp/bindings/remote.h"
+
+namespace crosapi {
+
+// Base class for Ash browser tests that depend on Lacros and use
+// StandaloneBrowserTestController.
+class AshRequiresLacrosBrowserTestBase : public InProcessBrowserTest {
+ public:
+  AshRequiresLacrosBrowserTestBase();
+  ~AshRequiresLacrosBrowserTestBase() override;
+
+ protected:
+  void SetUpInProcessBrowserTestFixture() override;
+
+  // Waits for Lacros to start, and for the StandaloneBrowserTestController to
+  // connect.
+  void SetUpOnMainThread() override;
+
+  // Returns whether the --lacros-chrome-path is provided.
+  // If returns false, we should not do any Lacros related testing
+  // because the Lacros instance is not provided.
+  bool HasLacrosArgument() const { return ash_starter_.HasLacrosArgument(); }
+
+  // Controller to send commands to the connected Lacros crosapi client.
+  mojom::StandaloneBrowserTestController* GetStandaloneBrowserTestController();
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+  test::AshBrowserTestStarter ash_starter_;
+  std::unique_ptr<crosapi::TestControllerAsh> test_controller_ash_;
+};
+
+}  // namespace crosapi
+
+#endif  // CHROME_BROWSER_ASH_CROSAPI_ASH_REQUIRES_LACROS_BROWSERTESTBASE_H_
diff --git a/chrome/browser/ash/crosapi/crosapi_ash.cc b/chrome/browser/ash/crosapi/crosapi_ash.cc
index b150648..5f31f30 100644
--- a/chrome/browser/ash/crosapi/crosapi_ash.cc
+++ b/chrome/browser/ash/crosapi/crosapi_ash.cc
@@ -52,6 +52,7 @@
 #include "chrome/browser/ash/crosapi/file_manager_ash.h"
 #include "chrome/browser/ash/crosapi/file_system_provider_service_ash.h"
 #include "chrome/browser/ash/crosapi/force_installed_tracker_ash.h"
+#include "chrome/browser/ash/crosapi/fullscreen_controller_ash.h"
 #include "chrome/browser/ash/crosapi/geolocation_service_ash.h"
 #include "chrome/browser/ash/crosapi/identity_manager_ash.h"
 #include "chrome/browser/ash/crosapi/idle_service_ash.h"
@@ -200,6 +201,7 @@
           std::make_unique<FileSystemProviderServiceAsh>()),
       force_installed_tracker_ash_(
           std::make_unique<ForceInstalledTrackerAsh>()),
+      fullscreen_controller_ash_(std::make_unique<FullscreenControllerAsh>()),
       geolocation_service_ash_(std::make_unique<GeolocationServiceAsh>()),
       identity_manager_ash_(std::make_unique<IdentityManagerAsh>()),
       idle_service_ash_(std::make_unique<IdleServiceAsh>()),
@@ -392,6 +394,11 @@
   force_installed_tracker_ash_->BindReceiver(std::move(receiver));
 }
 
+void CrosapiAsh::BindFullscreenController(
+    mojo::PendingReceiver<crosapi::mojom::FullscreenController> receiver) {
+  fullscreen_controller_ash_->BindReceiver(std::move(receiver));
+}
+
 void CrosapiAsh::BindGeolocationService(
     mojo::PendingReceiver<crosapi::mojom::GeolocationService> receiver) {
   geolocation_service_ash_->BindReceiver(std::move(receiver));
diff --git a/chrome/browser/ash/crosapi/crosapi_ash.h b/chrome/browser/ash/crosapi/crosapi_ash.h
index f142a18a..1068806 100644
--- a/chrome/browser/ash/crosapi/crosapi_ash.h
+++ b/chrome/browser/ash/crosapi/crosapi_ash.h
@@ -62,6 +62,7 @@
 class FileManagerAsh;
 class FileSystemProviderServiceAsh;
 class ForceInstalledTrackerAsh;
+class FullscreenControllerAsh;
 class GeolocationServiceAsh;
 class IdentityManagerAsh;
 class IdleServiceAsh;
@@ -200,6 +201,8 @@
       override;
   void BindForceInstalledTracker(
       mojo::PendingReceiver<mojom::ForceInstalledTracker> receiver) override;
+  void BindFullscreenController(
+      mojo::PendingReceiver<mojom::FullscreenController> receiver) override;
   void BindGeolocationService(
       mojo::PendingReceiver<mojom::GeolocationService> receiver) override;
   void BindIdentityManager(
@@ -347,6 +350,10 @@
     return force_installed_tracker_ash_.get();
   }
 
+  FullscreenControllerAsh* fullscreen_controller_ash() {
+    return fullscreen_controller_ash_.get();
+  }
+
   KioskSessionServiceAsh* kiosk_session_service() {
     return kiosk_session_service_ash_.get();
   }
@@ -463,6 +470,7 @@
   std::unique_ptr<FileSystemProviderServiceAsh>
       file_system_provider_service_ash_;
   std::unique_ptr<ForceInstalledTrackerAsh> force_installed_tracker_ash_;
+  std::unique_ptr<FullscreenControllerAsh> fullscreen_controller_ash_;
   std::unique_ptr<GeolocationServiceAsh> geolocation_service_ash_;
   std::unique_ptr<IdentityManagerAsh> identity_manager_ash_;
   std::unique_ptr<IdleServiceAsh> idle_service_ash_;
diff --git a/chrome/browser/ash/crosapi/crosapi_util.cc b/chrome/browser/ash/crosapi/crosapi_util.cc
index c17d879e..0d39fe8c 100644
--- a/chrome/browser/ash/crosapi/crosapi_util.cc
+++ b/chrome/browser/ash/crosapi/crosapi_util.cc
@@ -71,6 +71,7 @@
 #include "chromeos/crosapi/mojom/file_manager.mojom.h"
 #include "chromeos/crosapi/mojom/file_system_provider.mojom.h"
 #include "chromeos/crosapi/mojom/force_installed_tracker.mojom.h"
+#include "chromeos/crosapi/mojom/fullscreen_controller.mojom.h"
 #include "chromeos/crosapi/mojom/geolocation.mojom.h"
 #include "chromeos/crosapi/mojom/holding_space_service.mojom.h"
 #include "chromeos/crosapi/mojom/identity_manager.mojom.h"
@@ -241,7 +242,7 @@
   return {T::Uuid_, T::Version_};
 }
 
-static_assert(crosapi::mojom::Crosapi::Version_ == 93,
+static_assert(crosapi::mojom::Crosapi::Version_ == 94,
               "If you add a new crosapi, please add it to "
               "kInterfaceVersionEntries below.");
 
@@ -286,6 +287,7 @@
     MakeInterfaceVersionEntry<crosapi::mojom::FileManager>(),
     MakeInterfaceVersionEntry<crosapi::mojom::FileSystemProviderService>(),
     MakeInterfaceVersionEntry<crosapi::mojom::ForceInstalledTracker>(),
+    MakeInterfaceVersionEntry<crosapi::mojom::FullscreenController>(),
     MakeInterfaceVersionEntry<crosapi::mojom::GeolocationService>(),
     MakeInterfaceVersionEntry<crosapi::mojom::HoldingSpaceService>(),
     MakeInterfaceVersionEntry<crosapi::mojom::IdentityManager>(),
diff --git a/chrome/browser/ash/crosapi/fullscreen_controller_ash.cc b/chrome/browser/ash/crosapi/fullscreen_controller_ash.cc
new file mode 100644
index 0000000..652da4f
--- /dev/null
+++ b/chrome/browser/ash/crosapi/fullscreen_controller_ash.cc
@@ -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.
+
+#include "chrome/browser/ash/crosapi/fullscreen_controller_ash.h"
+
+#include "chromeos/crosapi/mojom/fullscreen_controller.mojom.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/remote.h"
+
+namespace crosapi {
+
+FullscreenControllerAsh::FullscreenControllerAsh() = default;
+FullscreenControllerAsh::~FullscreenControllerAsh() = default;
+
+void FullscreenControllerAsh::BindReceiver(
+    mojo::PendingReceiver<mojom::FullscreenController> pending_receiver) {
+  receivers_.Add(this, std::move(pending_receiver));
+}
+
+void FullscreenControllerAsh::ShouldExitFullscreenBeforeLock(
+    base::OnceCallback<void(bool)> callback) {
+  // Ash should exit full screen before lock (which is the default) if there are
+  // no remote clients.
+  if (remotes_.empty()) {
+    std::move(callback).Run(true);
+    return;
+  }
+
+  // Assumes that there is only one remote client.
+  remotes_.begin()->get()->ShouldExitFullscreenBeforeLock(
+      base::BindOnce(&FullscreenControllerAsh::OnShouldExitFullscreenBeforeLock,
+                     weak_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+void FullscreenControllerAsh::AddClient(
+    mojo::PendingRemote<mojom::FullscreenControllerClient> client) {
+  remotes_.Add(
+      mojo::Remote<mojom::FullscreenControllerClient>(std::move(client)));
+}
+
+void FullscreenControllerAsh::OnShouldExitFullscreenBeforeLock(
+    base::OnceCallback<void(bool)> callback,
+    bool should_exit_fullscreen) {
+  std::move(callback).Run(should_exit_fullscreen);
+}
+
+}  // namespace crosapi
diff --git a/chrome/browser/ash/crosapi/fullscreen_controller_ash.h b/chrome/browser/ash/crosapi/fullscreen_controller_ash.h
new file mode 100644
index 0000000..99c1af4
--- /dev/null
+++ b/chrome/browser/ash/crosapi/fullscreen_controller_ash.h
@@ -0,0 +1,48 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ASH_CROSAPI_FULLSCREEN_CONTROLLER_ASH_H_
+#define CHROME_BROWSER_ASH_CROSAPI_FULLSCREEN_CONTROLLER_ASH_H_
+
+#include "base/memory/weak_ptr.h"
+#include "chromeos/crosapi/mojom/fullscreen_controller.mojom.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+#include "mojo/public/cpp/bindings/remote_set.h"
+
+namespace crosapi {
+
+// The ash-chrome implementation of the fullscreen controller crosapi interface.
+class FullscreenControllerAsh : public mojom::FullscreenController {
+ public:
+  FullscreenControllerAsh();
+  FullscreenControllerAsh(const FullscreenControllerAsh&) = delete;
+  FullscreenControllerAsh& operator=(const FullscreenControllerAsh&) = delete;
+  ~FullscreenControllerAsh() override;
+
+  void BindReceiver(
+      mojo::PendingReceiver<mojom::FullscreenController> receiver);
+
+  // Whether full screen mode should be exited on session lock/unlock.
+  void ShouldExitFullscreenBeforeLock(base::OnceCallback<void(bool)> callback);
+
+  // crosapi::mojom::FullscreenController:
+  void AddClient(
+      mojo::PendingRemote<mojom::FullscreenControllerClient> client) override;
+
+ private:
+  // Passed as a callback to `ShouldExitFullscreenBeforeLock` to the remote
+  // client. Forwards the received response to Ash.
+  void OnShouldExitFullscreenBeforeLock(base::OnceCallback<void(bool)> callback,
+                                        bool should_exit_fullscreen);
+
+  mojo::ReceiverSet<mojom::FullscreenController> receivers_;
+  mojo::RemoteSet<mojom::FullscreenControllerClient> remotes_;
+
+  base::WeakPtrFactory<FullscreenControllerAsh> weak_factory_{this};
+};
+
+}  // namespace crosapi
+
+#endif  // CHROME_BROWSER_ASH_CROSAPI_FULLSCREEN_CONTROLLER_ASH_H_
diff --git a/chrome/browser/ash/crosapi/fullscreen_controller_ash_unittest.cc b/chrome/browser/ash/crosapi/fullscreen_controller_ash_unittest.cc
new file mode 100644
index 0000000..5ae4e53
--- /dev/null
+++ b/chrome/browser/ash/crosapi/fullscreen_controller_ash_unittest.cc
@@ -0,0 +1,84 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/crosapi/fullscreen_controller_ash.h"
+
+#include "base/test/task_environment.h"
+#include "base/test/test_future.h"
+#include "chromeos/crosapi/mojom/fullscreen_controller.mojom.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace crosapi {
+
+using mojom::FullscreenControllerClient;
+
+using ShouldExitFullscreenBeforeLockCallback =
+    mojom::FullscreenControllerClient::ShouldExitFullscreenBeforeLockCallback;
+
+class FullscreenControllerAshBaseTest : public testing::Test {
+ public:
+  void InvokeShouldExitFullscreenBeforeLock(bool expected_result) {
+    base::test::TestFuture<bool> future;
+    fullscreen_controller_ash()->ShouldExitFullscreenBeforeLock(
+        future.GetCallback());
+
+    EXPECT_EQ(future.Get(), expected_result);
+  }
+
+  FullscreenControllerAsh* fullscreen_controller_ash() {
+    return &fullscreen_controller_ash_;
+  }
+
+ private:
+  base::test::SingleThreadTaskEnvironment task_environment_;
+  FullscreenControllerAsh fullscreen_controller_ash_;
+};
+
+// Test that the default (True) is returned if
+// `ShouldExitFullscreenBeforeLock()` is invoked with no client bound.
+TEST_F(FullscreenControllerAshBaseTest,
+       ShouldExitFullscreenBeforeLockWithoutBoundClient) {
+  InvokeShouldExitFullscreenBeforeLock(/*expected_result=*/true);
+}
+
+class FullscreenControllerAshTest : public FullscreenControllerAshBaseTest,
+                                    public testing::WithParamInterface<bool> {
+ public:
+  class MockFullscreenControllerClient : public FullscreenControllerClient {
+   public:
+    MOCK_METHOD(void,
+                ShouldExitFullscreenBeforeLock,
+                (ShouldExitFullscreenBeforeLockCallback callback),
+                (override));
+  };
+
+ protected:
+  testing::StrictMock<MockFullscreenControllerClient> client_;
+};
+
+// Test that the correct response is returned if
+// `ShouldExitFullscreenBeforeLock()` is invoked with a client bound.
+TEST_P(FullscreenControllerAshTest,
+       ShouldExitFullscreenBeforeLockWithBoundClient) {
+  bool expected_result = GetParam();
+
+  // Bind `client_`.
+  mojo::Receiver<FullscreenControllerClient> client_receiver{&client_};
+  fullscreen_controller_ash()->AddClient(
+      client_receiver.BindNewPipeAndPassRemoteWithVersion());
+
+  // Mock `client_` response for `ShouldExitFullscreenBeforeLock()`.
+  EXPECT_CALL(client_, ShouldExitFullscreenBeforeLock)
+      .WillOnce(testing::Invoke(
+          [expected_result](ShouldExitFullscreenBeforeLockCallback callback) {
+            std::move(callback).Run(expected_result);
+          }));
+
+  InvokeShouldExitFullscreenBeforeLock(expected_result);
+}
+
+INSTANTIATE_TEST_SUITE_P(All, FullscreenControllerAshTest, testing::Bool());
+
+}  // namespace crosapi
diff --git a/chrome/browser/ash/crosapi/test_controller_ash.cc b/chrome/browser/ash/crosapi/test_controller_ash.cc
index ca038d0..e384281 100644
--- a/chrome/browser/ash/crosapi/test_controller_ash.cc
+++ b/chrome/browser/ash/crosapi/test_controller_ash.cc
@@ -365,6 +365,9 @@
   standalone_browser_test_controller_.Bind(std::move(controller));
   standalone_browser_test_controller_.set_disconnect_handler(base::BindOnce(
       &TestControllerAsh::OnControllerDisconnected, base::Unretained(this)));
+
+  if (!on_standalone_browser_test_controller_bound_.is_signaled())
+    on_standalone_browser_test_controller_bound_.Signal();
 }
 
 void TestControllerAsh::WaiterFinished(OverviewWaiter* waiter) {
diff --git a/chrome/browser/ash/crosapi/test_controller_ash.h b/chrome/browser/ash/crosapi/test_controller_ash.h
index 3b8de6b..b97198e5 100644
--- a/chrome/browser/ash/crosapi/test_controller_ash.h
+++ b/chrome/browser/ash/crosapi/test_controller_ash.h
@@ -9,10 +9,12 @@
 #include <string>
 #include <vector>
 
+#include "base/one_shot_event.h"
 #include "chrome/browser/ash/crosapi/crosapi_ash.h"
 #include "chromeos/crosapi/mojom/test_controller.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
+#include "mojo/public/cpp/bindings/remote.h"
 #include "ui/base/models/simple_menu_model.h"
 
 namespace crosapi {
@@ -101,6 +103,12 @@
     return standalone_browser_test_controller_;
   }
 
+  // Signals when standalone browser test controller becomes bound.
+  const base::OneShotEvent& on_standalone_browser_test_controller_bound()
+      const {
+    return on_standalone_browser_test_controller_bound_;
+  }
+
  private:
   class OverviewWaiter;
 
@@ -135,6 +143,8 @@
   // Controller to send commands to the connected lacros crosapi client.
   mojo::Remote<mojom::StandaloneBrowserTestController>
       standalone_browser_test_controller_;
+
+  base::OneShotEvent on_standalone_browser_test_controller_bound_;
 };
 
 class TestShillControllerAsh : public crosapi::mojom::TestShillController {
diff --git a/chrome/browser/ash/crosapi/vpn_extension_observer_ash_browsertest.cc b/chrome/browser/ash/crosapi/vpn_extension_observer_ash_browsertest.cc
index 2c5922f..2c09847 100644
--- a/chrome/browser/ash/crosapi/vpn_extension_observer_ash_browsertest.cc
+++ b/chrome/browser/ash/crosapi/vpn_extension_observer_ash_browsertest.cc
@@ -6,11 +6,7 @@
 #include "ash/public/cpp/network_config_service.h"
 #include "base/test/bind.h"
 #include "base/test/test_future.h"
-#include "chrome/browser/ash/crosapi/crosapi_ash.h"
-#include "chrome/browser/ash/crosapi/crosapi_manager.h"
-#include "chrome/browser/ash/crosapi/test_controller_ash.h"
-#include "chrome/test/base/chromeos/ash_browser_test_starter.h"
-#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/browser/ash/crosapi/ash_requires_lacros_browsertestbase.h"
 #include "chromeos/crosapi/mojom/test_controller.mojom-test-utils.h"
 #include "chromeos/services/network_config/public/cpp/cros_network_config_test_observer.h"
 #include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
@@ -24,38 +20,17 @@
 
 }  // namespace
 
-class VpnExtensionObserverBrowserTest : public InProcessBrowserTest {
+class VpnExtensionObserverBrowserTest
+    : public crosapi::AshRequiresLacrosBrowserTestBase {
  public:
-  void SetUpInProcessBrowserTestFixture() override {
-    if (!ash_starter_.HasLacrosArgument()) {
-      return;
-    }
-    ASSERT_TRUE(ash_starter_.PrepareEnvironmentForLacros());
-  }
-
-  void SetUpOnMainThread() override {
-    if (!ash_starter_.HasLacrosArgument()) {
-      return;
-    }
-    auto* manager = crosapi::CrosapiManager::Get();
-    test_controller_ash_ = std::make_unique<crosapi::TestControllerAsh>();
-    manager->crosapi_ash()->SetTestControllerForTesting(
-        test_controller_ash_.get());
-    ash_starter_.StartLacros(this);
-  }
-
   std::string LoadVpnExtension(const std::string& extension_name) {
     crosapi::mojom::StandaloneBrowserTestControllerAsyncWaiter waiter(
-        test_controller_ash_->GetStandaloneBrowserTestController().get());
+        GetStandaloneBrowserTestController());
 
     std::string extension_id;
     waiter.LoadVpnExtension(extension_name, &extension_id);
     return extension_id;
   }
-
- protected:
-  test::AshBrowserTestStarter ash_starter_;
-  std::unique_ptr<crosapi::TestControllerAsh> test_controller_ash_;
 };
 
 class ExtensionEventWaiter
@@ -87,10 +62,8 @@
   mojo::Remote<cros_network::mojom::CrosNetworkConfig> cros_network_config_;
 };
 
-// TODO(1339457): This test is flaky.
-IN_PROC_BROWSER_TEST_F(VpnExtensionObserverBrowserTest,
-                       DISABLED_LoadVpnExtension) {
-  if (!ash_starter_.HasLacrosArgument()) {
+IN_PROC_BROWSER_TEST_F(VpnExtensionObserverBrowserTest, LoadVpnExtension) {
+  if (!HasLacrosArgument()) {
     return;
   }
   auto waiter = std::make_unique<ExtensionEventWaiter>();
diff --git a/chrome/browser/ash/file_manager/empty_trash_io_task.cc b/chrome/browser/ash/file_manager/empty_trash_io_task.cc
index 376ad74..e704b78 100644
--- a/chrome/browser/ash/file_manager/empty_trash_io_task.cc
+++ b/chrome/browser/ash/file_manager/empty_trash_io_task.cc
@@ -13,7 +13,6 @@
 #include "content/public/browser/browser_thread.h"
 
 namespace file_manager::io_task {
-
 namespace {
 
 storage::FileSystemOperationRunner::OperationID
@@ -63,20 +62,20 @@
   complete_callback_ = std::move(complete_callback);
 
   enabled_trash_locations_ =
-      GenerateEnabledTrashLocationsForProfile(profile_, base_path_);
+      trash::GenerateEnabledTrashLocationsForProfile(profile_, base_path_);
   progress_.state = State::kInProgress;
 
-  TrashPathsMap::const_iterator it = enabled_trash_locations_.cbegin();
+  trash::TrashPathsMap::const_iterator it = enabled_trash_locations_.cbegin();
   if (it == enabled_trash_locations_.end()) {
     Complete(State::kSuccess);
     return;
   }
 
-  RemoveTrashSubDirectory(it, kFilesFolderName);
+  RemoveTrashSubDirectory(it, trash::kFilesFolderName);
 }
 
 void EmptyTrashIOTask::RemoveTrashSubDirectory(
-    TrashPathsMap::const_iterator& trash_location,
+    trash::TrashPathsMap::const_iterator& trash_location,
     const std::string& folder_name_to_remove) {
   const base::FilePath& trash_parent_path = trash_location->first;
   const base::FilePath trash_path =
@@ -104,7 +103,7 @@
 }
 
 void EmptyTrashIOTask::OnRemoveTrashSubDirectory(
-    TrashPathsMap::const_iterator& it,
+    trash::TrashPathsMap::const_iterator& it,
     const std::string& removed_folder_name,
     base::File::Error status) {
   progress_.outputs[progress_.outputs.size() - 1].error = status;
@@ -113,8 +112,8 @@
     Complete(State::kError);
     return;
   }
-  if (removed_folder_name == kFilesFolderName) {
-    RemoveTrashSubDirectory(it, kInfoFolderName);
+  if (removed_folder_name == trash::kFilesFolderName) {
+    RemoveTrashSubDirectory(it, trash::kInfoFolderName);
     return;
   }
   it++;
@@ -123,7 +122,7 @@
     return;
   }
 
-  RemoveTrashSubDirectory(it, kFilesFolderName);
+  RemoveTrashSubDirectory(it, trash::kFilesFolderName);
 }
 
 // Calls the completion callback for the task. `progress_` should not be
diff --git a/chrome/browser/ash/file_manager/empty_trash_io_task.h b/chrome/browser/ash/file_manager/empty_trash_io_task.h
index 0f6c93c..1d18178 100644
--- a/chrome/browser/ash/file_manager/empty_trash_io_task.h
+++ b/chrome/browser/ash/file_manager/empty_trash_io_task.h
@@ -49,12 +49,13 @@
  private:
   // Removes the entire trash subdirectory (e.g. .Trash/files) recursively. It
   // only iterates over the enabled trash locations.
-  void RemoveTrashSubDirectory(TrashPathsMap::const_iterator& trash_location,
-                               const std::string& folder_name_to_remove);
+  void RemoveTrashSubDirectory(
+      trash::TrashPathsMap::const_iterator& trash_location,
+      const std::string& folder_name_to_remove);
 
   // After removing the trash directory, continue iterating until there are no
   // more enabled trash directories left.
-  void OnRemoveTrashSubDirectory(TrashPathsMap::const_iterator& it,
+  void OnRemoveTrashSubDirectory(trash::TrashPathsMap::const_iterator& it,
                                  const std::string& removed_folder_name,
                                  base::File::Error status);
 
@@ -73,7 +74,7 @@
   raw_ptr<Profile> profile_;
 
   // A map containing paths which are enabled for trashing.
-  TrashPathsMap enabled_trash_locations_;
+  trash::TrashPathsMap enabled_trash_locations_;
 
   // Stores the id of the restore operation if one is in progress. Used to stop
   // the empty trash operation.
diff --git a/chrome/browser/ash/file_manager/empty_trash_io_task_unittest.cc b/chrome/browser/ash/file_manager/empty_trash_io_task_unittest.cc
index 0d21f67..4b1aaa4 100644
--- a/chrome/browser/ash/file_manager/empty_trash_io_task_unittest.cc
+++ b/chrome/browser/ash/file_manager/empty_trash_io_task_unittest.cc
@@ -63,8 +63,10 @@
         trash_parent_path.Append(relative_trash_folder);
     EXPECT_TRUE(EnsureTrashDirectorySetup(trash_directory));
 
-    trash_subdirectories.emplace_back(trash_directory.Append(kFilesFolderName));
-    trash_subdirectories.emplace_back(trash_directory.Append(kInfoFolderName));
+    trash_subdirectories.emplace_back(
+        trash_directory.Append(trash::kFilesFolderName));
+    trash_subdirectories.emplace_back(
+        trash_directory.Append(trash::kInfoFolderName));
     return trash_directory;
   }
 
@@ -72,12 +74,14 @@
     TrashDirectoriesAndSubDirectories directories;
 
     // Setup ~/MyFiles/.Trash
-    directories.trash_directories.emplace_back(SetupTrashDirectory(
-        my_files_dir_, kTrashFolderName, directories.trash_subdirectories));
+    directories.trash_directories.emplace_back(
+        SetupTrashDirectory(my_files_dir_, trash::kTrashFolderName,
+                            directories.trash_subdirectories));
 
     // Setup ~/MyFiles/Downloads/.Trash
-    directories.trash_directories.emplace_back(SetupTrashDirectory(
-        downloads_dir_, kTrashFolderName, directories.trash_subdirectories));
+    directories.trash_directories.emplace_back(
+        SetupTrashDirectory(downloads_dir_, trash::kTrashFolderName,
+                            directories.trash_subdirectories));
 
     // Setup /media/fuse/termina_hash_pengiun/.local/share/Trash
     directories.trash_directories.emplace_back(SetupTrashDirectory(
diff --git a/chrome/browser/ash/file_manager/restore_io_task.cc b/chrome/browser/ash/file_manager/restore_io_task.cc
index 5f92bd17c..a5b8808 100644
--- a/chrome/browser/ash/file_manager/restore_io_task.cc
+++ b/chrome/browser/ash/file_manager/restore_io_task.cc
@@ -16,8 +16,7 @@
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 
-namespace file_manager {
-namespace io_task {
+namespace file_manager::io_task {
 
 namespace {
 
@@ -78,7 +77,8 @@
   }
 
   progress_.state = State::kInProgress;
-  validator_ = std::make_unique<TrashInfoValidator>(profile_, base_path_);
+  validator_ =
+      std::make_unique<trash::TrashInfoValidator>(profile_, base_path_);
   validator_->SetDisconnectHandler(base::BindOnce(
       &RestoreIOTask::Complete, weak_ptr_factory_.GetWeakPtr(), State::kError));
 
@@ -111,7 +111,7 @@
 
 void RestoreIOTask::EnsureParentRestorePathExists(
     size_t idx,
-    base::FileErrorOr<ParsedTrashInfoData> parsed_data) {
+    base::FileErrorOr<trash::ParsedTrashInfoData> parsed_data) {
   if (parsed_data.is_error()) {
     progress_.sources[idx].error = parsed_data.error();
     Complete(State::kError);
@@ -265,5 +265,4 @@
   operation_id_.emplace(id);
 }
 
-}  // namespace io_task
-}  // namespace file_manager
+}  // namespace file_manager::io_task
diff --git a/chrome/browser/ash/file_manager/restore_io_task.h b/chrome/browser/ash/file_manager/restore_io_task.h
index b0d4656..027dcc8 100644
--- a/chrome/browser/ash/file_manager/restore_io_task.h
+++ b/chrome/browser/ash/file_manager/restore_io_task.h
@@ -19,8 +19,7 @@
 
 class Profile;
 
-namespace file_manager {
-namespace io_task {
+namespace file_manager::io_task {
 
 // This class represents a task restoring from trash. A restore task attempts to
 // restore files from a supported Trash folder back to it's original path. If
@@ -54,7 +53,7 @@
   // actually exists. In the event the file path has been removed, recreate it.
   void EnsureParentRestorePathExists(
       size_t idx,
-      base::FileErrorOr<ParsedTrashInfoData> parsed_data);
+      base::FileErrorOr<trash::ParsedTrashInfoData> parsed_data);
 
   void OnParentRestorePathExists(size_t idx,
                                  const base::FilePath& trashed_file_location,
@@ -100,7 +99,7 @@
   absl::optional<storage::FileSystemOperationRunner::OperationID> operation_id_;
 
   // Validates and parses .trashinfo files.
-  std::unique_ptr<TrashInfoValidator> validator_ = nullptr;
+  std::unique_ptr<trash::TrashInfoValidator> validator_ = nullptr;
 
   ProgressCallback progress_callback_;
   CompleteCallback complete_callback_;
@@ -108,7 +107,6 @@
   base::WeakPtrFactory<RestoreIOTask> weak_ptr_factory_{this};
 };
 
-}  // namespace io_task
-}  // namespace file_manager
+}  // namespace file_manager::io_task
 
 #endif  // CHROME_BROWSER_ASH_FILE_MANAGER_RESTORE_IO_TASK_H_
diff --git a/chrome/browser/ash/file_manager/restore_io_task_unittest.cc b/chrome/browser/ash/file_manager/restore_io_task_unittest.cc
index ef9f35c4..9cae2bc 100644
--- a/chrome/browser/ash/file_manager/restore_io_task_unittest.cc
+++ b/chrome/browser/ash/file_manager/restore_io_task_unittest.cc
@@ -28,8 +28,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
 
-namespace file_manager {
-namespace io_task {
+namespace file_manager::io_task {
 namespace {
 
 using ::base::test::RunClosure;
@@ -155,9 +154,10 @@
   EnsureTrashDirectorySetup(downloads_dir_);
 
   std::string foo_contents = base::RandBytesAsString(kTestFileSize);
-  const base::FilePath file_path = downloads_dir_.Append(kTrashFolderName)
-                                       .Append(kInfoFolderName)
-                                       .Append("foo.txt.trashinfo");
+  const base::FilePath file_path =
+      downloads_dir_.Append(trash::kTrashFolderName)
+          .Append(trash::kInfoFolderName)
+          .Append("foo.txt.trashinfo");
   ASSERT_TRUE(base::WriteFile(file_path, foo_contents));
 
   base::RunLoop run_loop;
@@ -191,12 +191,13 @@
   std::string foo_metadata_contents =
       GenerateTrashInfoContents("/../../../bad/actor/foo.txt");
 
-  const base::FilePath trash_path = downloads_dir_.Append(kTrashFolderName);
+  const base::FilePath trash_path =
+      downloads_dir_.Append(trash::kTrashFolderName);
   const base::FilePath info_file_path =
-      trash_path.Append(kInfoFolderName).Append("foo.txt.trashinfo");
+      trash_path.Append(trash::kInfoFolderName).Append("foo.txt.trashinfo");
   ASSERT_TRUE(base::WriteFile(info_file_path, foo_metadata_contents));
   const base::FilePath files_path =
-      trash_path.Append(kFilesFolderName).Append("foo.txt");
+      trash_path.Append(trash::kFilesFolderName).Append("foo.txt");
   ASSERT_TRUE(base::WriteFile(files_path, foo_contents));
 
   base::RunLoop run_loop;
@@ -228,12 +229,13 @@
   std::string foo_metadata_contents =
       GenerateTrashInfoContents("/Downloads/bar/foo.txt");
 
-  const base::FilePath trash_path = downloads_dir_.Append(kTrashFolderName);
+  const base::FilePath trash_path =
+      downloads_dir_.Append(trash::kTrashFolderName);
   const base::FilePath info_file_path =
-      trash_path.Append(kInfoFolderName).Append("foo.txt.trashinfo");
+      trash_path.Append(trash::kInfoFolderName).Append("foo.txt.trashinfo");
   ASSERT_TRUE(base::WriteFile(info_file_path, foo_metadata_contents));
   const base::FilePath files_path =
-      trash_path.Append(kFilesFolderName).Append("foo.txt");
+      trash_path.Append(trash::kFilesFolderName).Append("foo.txt");
   ASSERT_TRUE(base::WriteFile(files_path, foo_contents));
 
   base::RunLoop run_loop;
@@ -264,12 +266,13 @@
   std::string foo_metadata_contents =
       GenerateTrashInfoContents("/Downloads/bar/foo.txt");
 
-  const base::FilePath trash_path = downloads_dir_.Append(kTrashFolderName);
+  const base::FilePath trash_path =
+      downloads_dir_.Append(trash::kTrashFolderName);
   const base::FilePath info_file_path =
-      trash_path.Append(kInfoFolderName).Append("foo.txt.trashinfo");
+      trash_path.Append(trash::kInfoFolderName).Append("foo.txt.trashinfo");
   ASSERT_TRUE(base::WriteFile(info_file_path, foo_metadata_contents));
   const base::FilePath files_path =
-      trash_path.Append(kFilesFolderName).Append("foo.txt");
+      trash_path.Append(trash::kFilesFolderName).Append("foo.txt");
   ASSERT_TRUE(base::WriteFile(files_path, foo_contents));
 
   // Create conflicting item at same place restore is going to happen at.
@@ -369,12 +372,13 @@
 
   std::string foo_contents = base::RandBytesAsString(kTestFileSize);
 
-  const base::FilePath trash_path = downloads_dir_.Append(kTrashFolderName);
+  const base::FilePath trash_path =
+      downloads_dir_.Append(trash::kTrashFolderName);
   const base::FilePath info_file_path =
-      trash_path.Append(kInfoFolderName).Append("foo.txt.trashinfo");
+      trash_path.Append(trash::kInfoFolderName).Append("foo.txt.trashinfo");
   ASSERT_TRUE(base::WriteFile(info_file_path, foo_contents));
   const base::FilePath files_path =
-      trash_path.Append(kFilesFolderName).Append("foo.txt");
+      trash_path.Append(trash::kFilesFolderName).Append("foo.txt");
   ASSERT_TRUE(base::WriteFile(files_path, foo_contents));
 
   base::RunLoop run_loop;
@@ -397,5 +401,4 @@
 }
 
 }  // namespace
-}  // namespace io_task
-}  // namespace file_manager
+}  // namespace file_manager::io_task
diff --git a/chrome/browser/ash/file_manager/trash_common_util.cc b/chrome/browser/ash/file_manager/trash_common_util.cc
index c260696..8bb2f894 100644
--- a/chrome/browser/ash/file_manager/trash_common_util.cc
+++ b/chrome/browser/ash/file_manager/trash_common_util.cc
@@ -9,8 +9,7 @@
 #include "chrome/browser/ash/file_manager/path_util.h"
 #include "chrome/browser/ash/file_manager/volume_manager.h"
 
-namespace file_manager {
-namespace io_task {
+namespace file_manager::trash {
 
 constexpr char kTrashFolderName[] = ".Trash";
 constexpr char kInfoFolderName[] = "info";
@@ -104,5 +103,4 @@
   return enabled_trash_locations;
 }
 
-}  // namespace io_task
-}  // namespace file_manager
+}  // namespace file_manager::trash
diff --git a/chrome/browser/ash/file_manager/trash_common_util.h b/chrome/browser/ash/file_manager/trash_common_util.h
index 3624ad21..31570c6 100644
--- a/chrome/browser/ash/file_manager/trash_common_util.h
+++ b/chrome/browser/ash/file_manager/trash_common_util.h
@@ -12,8 +12,7 @@
 
 class Profile;
 
-namespace file_manager {
-namespace io_task {
+namespace file_manager::trash {
 
 // Constant representing the Trash folder name.
 extern const char kTrashFolderName[];
@@ -89,7 +88,6 @@
     Profile* profile,
     const base::FilePath& base_path);
 
-}  // namespace io_task
-}  // namespace file_manager
+}  // namespace file_manager::trash
 
 #endif  // CHROME_BROWSER_ASH_FILE_MANAGER_TRASH_COMMON_UTIL_H_
diff --git a/chrome/browser/ash/file_manager/trash_info_validator.cc b/chrome/browser/ash/file_manager/trash_info_validator.cc
index f35e6ca..0c70a98 100644
--- a/chrome/browser/ash/file_manager/trash_info_validator.cc
+++ b/chrome/browser/ash/file_manager/trash_info_validator.cc
@@ -14,7 +14,7 @@
 
 class Profile;
 
-namespace file_manager {
+namespace file_manager::trash {
 
 namespace {
 
@@ -28,7 +28,7 @@
 TrashInfoValidator::TrashInfoValidator(Profile* profile,
                                        const base::FilePath& base_path) {
   enabled_trash_locations_ =
-      io_task::GenerateEnabledTrashLocationsForProfile(profile, base_path);
+      trash::GenerateEnabledTrashLocationsForProfile(profile, base_path);
 
   parser_ = std::make_unique<chromeos::trash_service::TrashInfoParser>();
 }
@@ -47,7 +47,7 @@
     const base::FilePath& trash_info_path,
     ValidateAndParseTrashInfoCallback callback) {
   // Validates the supplied file ends in a .trashinfo extension.
-  if (trash_info_path.FinalExtension() != io_task::kTrashInfoExtension) {
+  if (trash_info_path.FinalExtension() != kTrashInfoExtension) {
     RunCallbackWithError(base::File::FILE_ERROR_INVALID_URL,
                          std::move(callback));
     return;
@@ -74,7 +74,7 @@
   // Ensure the corresponding file that this metadata file refers to actually
   // exists.
   base::FilePath trashed_file_location =
-      trash_folder_location.Append(io_task::kFilesFolderName)
+      trash_folder_location.Append(kFilesFolderName)
           .Append(trash_info_path.BaseName().RemoveFinalExtension());
 
   base::ThreadPool::PostTaskAndReplyWithResult(
@@ -143,4 +143,4 @@
       base::FileErrorOr<ParsedTrashInfoData>(std::move(parsed_data)));
 }
 
-}  // namespace file_manager
+}  // namespace file_manager::trash
diff --git a/chrome/browser/ash/file_manager/trash_info_validator.h b/chrome/browser/ash/file_manager/trash_info_validator.h
index 25b3295e..e030b86 100644
--- a/chrome/browser/ash/file_manager/trash_info_validator.h
+++ b/chrome/browser/ash/file_manager/trash_info_validator.h
@@ -14,7 +14,7 @@
 #include "chrome/browser/ash/file_manager/trash_common_util.h"
 #include "chromeos/ash/components/trash_service/public/cpp/trash_info_parser.h"
 
-namespace file_manager {
+namespace file_manager::trash {
 
 // On a successful parse of .trashinfo files, returns the restoration path,
 // deletion date and actual location of the trashed file.
@@ -64,8 +64,9 @@
   //   - Resides in an enabled trash directory
   //   - The file resides in the info directory
   //   - Has an identical item in the files directory with no .trashinfo suffix
-  // On confirming the above it then calls the TrashService to retrieve the
-  // parsed trashinfo data. The `trash_info_path` must be absolute.
+  // In the event the above fails, the `callback` will be invoked with an error,
+  // on success it then calls the TrashService to retrieve the parsed trashinfo
+  // data. The `trash_info_path` must be absolute.
   void ValidateAndParseTrashInfo(const base::FilePath& trash_info_path,
                                  ValidateAndParseTrashInfoCallback callback);
 
@@ -94,7 +95,7 @@
                          base::Time deletion_date);
 
   // A map containing paths which are enabled for trashing.
-  io_task::TrashPathsMap enabled_trash_locations_;
+  trash::TrashPathsMap enabled_trash_locations_;
 
   // Holds the connection open to the `TrashService`. This is a sandboxed
   // process that performs parsing of the trashinfo files.
@@ -103,6 +104,6 @@
   base::WeakPtrFactory<TrashInfoValidator> weak_ptr_factory_{this};
 };
 
-}  // namespace file_manager
+}  // namespace file_manager::trash
 
 #endif  // CHROME_BROWSER_ASH_FILE_MANAGER_TRASH_INFO_VALIDATOR_H_
diff --git a/chrome/browser/ash/file_manager/trash_io_task.cc b/chrome/browser/ash/file_manager/trash_io_task.cc
index 79e08e2a..586b2dd 100644
--- a/chrome/browser/ash/file_manager/trash_io_task.cc
+++ b/chrome/browser/ash/file_manager/trash_io_task.cc
@@ -23,8 +23,7 @@
 #include "content/public/browser/browser_thread.h"
 #include "google_apis/common/task_util.h"
 
-namespace file_manager {
-namespace io_task {
+namespace file_manager::io_task {
 namespace {
 
 // Generates and updates the `entry` with the standard contents of the
@@ -127,7 +126,7 @@
   // Build the list of known paths that are enabled, for now Downloads is a bind
   // mount at MyFiles/Downloads so treat them as separate volumes.
   free_space_map_ =
-      GenerateEnabledTrashLocationsForProfile(profile_, base_path_);
+      trash::GenerateEnabledTrashLocationsForProfile(profile_, base_path_);
   progress_.state = State::kInProgress;
 
   UpdateTrashEntry(0);
@@ -153,7 +152,7 @@
   // sorted by key. base::FilePath keys will insert in lexicographical order
   // however in the case of nested directories, reverse lexicographical order is
   // preferred to ensure the closer parent path by depth is chosen.
-  const TrashPathsMap::reverse_iterator& trash_parent_path_it =
+  const trash::TrashPathsMap::reverse_iterator& trash_parent_path_it =
       std::find_if(free_space_map_.rbegin(), free_space_map_.rend(),
                    [&source_path](const auto& it) -> bool {
                      return it.first.IsParent(source_path);
@@ -168,7 +167,7 @@
     return;
   }
 
-  TrashLocation& trash_location = trash_parent_path_it->second;
+  trash::TrashLocation& trash_location = trash_parent_path_it->second;
   const base::FilePath trash_parent_path = trash_parent_path_it->first;
   TrashEntry& entry = trash_entries_[source_idx];
   entry.trash_mount_path = trash_parent_path;
@@ -194,7 +193,7 @@
 
 void TrashIOTask::ValidateAndDecrementFreeSpace(
     size_t source_idx,
-    const TrashPathsMap::reverse_iterator& it) {
+    const trash::TrashPathsMap::reverse_iterator& it) {
   size_t trash_contents_size =
       trash_entries_[source_idx].trash_info_contents.size();
   progress_.total_bytes += trash_contents_size;
@@ -253,8 +252,9 @@
   SetupSubDirectory(it, it->second.trash_files);
 }
 
-void TrashIOTask::GetFreeDiskSpace(size_t source_idx,
-                                   const TrashPathsMap::reverse_iterator& it) {
+void TrashIOTask::GetFreeDiskSpace(
+    size_t source_idx,
+    const trash::TrashPathsMap::reverse_iterator& it) {
   base::ThreadPool::PostTaskAndReplyWithResult(
       FROM_HERE, {base::MayBlock()},
       base::BindOnce(&base::SysInfo::AmountOfFreeDiskSpace,
@@ -278,17 +278,20 @@
   return base::FilePath(relative_path);
 }
 
-void TrashIOTask::GotFreeDiskSpace(size_t source_idx,
-                                   const TrashPathsMap::reverse_iterator& it,
-                                   int64_t free_space) {
-  TrashLocation& trash_location = it->second;
+void TrashIOTask::GotFreeDiskSpace(
+    size_t source_idx,
+    const trash::TrashPathsMap::reverse_iterator& it,
+    int64_t free_space) {
+  trash::TrashLocation& trash_location = it->second;
   const base::FilePath& trash_parent_path = it->first;
   base::FilePath trash_path = MakeRelativeFromBasePath(
       trash_parent_path.Append(trash_location.relative_folder_path));
-  trash_location.trash_files = CreateFileSystemURL(
-      progress_.sources[source_idx].url, trash_path.Append(kFilesFolderName));
-  trash_location.trash_info = CreateFileSystemURL(
-      progress_.sources[source_idx].url, trash_path.Append(kInfoFolderName));
+  trash_location.trash_files =
+      CreateFileSystemURL(progress_.sources[source_idx].url,
+                          trash_path.Append(trash::kFilesFolderName));
+  trash_location.trash_info =
+      CreateFileSystemURL(progress_.sources[source_idx].url,
+                          trash_path.Append(trash::kInfoFolderName));
   trash_location.free_space = free_space;
   trash_location.require_setup = true;
 
@@ -296,7 +299,7 @@
 }
 
 void TrashIOTask::SetupSubDirectory(
-    TrashPathsMap::const_iterator& it,
+    trash::TrashPathsMap::const_iterator& it,
     const storage::FileSystemURL trash_subdirectory) {
   // All enabled trash directories exist in the `free_space_map_` however some
   // may not be used for this IO task. Skip the ones that don't require setup.
@@ -325,7 +328,7 @@
 }
 
 void TrashIOTask::OnSetupSubDirectory(
-    TrashPathsMap::const_iterator& it,
+    trash::TrashPathsMap::const_iterator& it,
     const storage::FileSystemURL trash_subdirectory,
     base::File::Error error) {
   if (error != base::File::FILE_OK) {
@@ -360,7 +363,7 @@
   const TrashEntry& entry = trash_entries_[source_idx];
   const auto trash_path = MakeRelativeFromBasePath(
       entry.trash_mount_path.Append(entry.relative_trash_path)
-          .Append(kFilesFolderName));
+          .Append(trash::kFilesFolderName));
 
   const storage::FileSystemURL files_location =
       CreateFileSystemURL(progress_.sources[source_idx].url, trash_path);
@@ -388,8 +391,8 @@
   const std::string file_name =
       destination_result.value().path().BaseName().value();
 
-  const base::FilePath destination_path =
-      GenerateTrashPath(absolute_trash_path, kInfoFolderName, file_name);
+  const base::FilePath destination_path = trash::GenerateTrashPath(
+      absolute_trash_path, trash::kInfoFolderName, file_name);
   progress_.outputs.emplace_back(
       CreateFileSystemURL(progress_.sources[source_idx].url, destination_path),
       absl::nullopt);
@@ -517,5 +520,4 @@
   operation_id_.emplace(id);
 }
 
-}  // namespace io_task
-}  // namespace file_manager
+}  // namespace file_manager::io_task
diff --git a/chrome/browser/ash/file_manager/trash_io_task.h b/chrome/browser/ash/file_manager/trash_io_task.h
index 126588b0..bc331bf 100644
--- a/chrome/browser/ash/file_manager/trash_io_task.h
+++ b/chrome/browser/ash/file_manager/trash_io_task.h
@@ -24,8 +24,7 @@
 
 class Profile;
 
-namespace file_manager {
-namespace io_task {
+namespace file_manager::io_task {
 
 namespace {
 
@@ -96,24 +95,25 @@
       const base::FilePath& path);
   void SetCurrentOperationID(
       storage::FileSystemOperationRunner::OperationID id);
-  void ValidateAndDecrementFreeSpace(size_t source_idx,
-                                     const TrashPathsMap::reverse_iterator& it);
+  void ValidateAndDecrementFreeSpace(
+      size_t source_idx,
+      const trash::TrashPathsMap::reverse_iterator& it);
   // Get the free disk space for `trash_parent_path` to know whether the
   // metadata can be written. The `folder_name` is used to differentiate between
   // .Trash and .Trash-1000 folder names on various file systems (both are valid
   // in the XDG spec).
   void GetFreeDiskSpace(size_t source_idx,
-                        const TrashPathsMap::reverse_iterator& it);
+                        const trash::TrashPathsMap::reverse_iterator& it);
   void GotFreeDiskSpace(size_t source_idx,
-                        const TrashPathsMap::reverse_iterator& it,
+                        const trash::TrashPathsMap::reverse_iterator& it,
                         int64_t free_space);
 
   // Sets up the .Trash/files and .Trash/info subdirectories specified by the
   // `trash_subdirectory` parameter. Will create the parent directories as well
   // in the instance .Trash folder does not exist.
-  void SetupSubDirectory(TrashPathsMap::const_iterator& it,
+  void SetupSubDirectory(trash::TrashPathsMap::const_iterator& it,
                          const storage::FileSystemURL trash_subdirectory);
-  void OnSetupSubDirectory(TrashPathsMap::const_iterator& it,
+  void OnSetupSubDirectory(trash::TrashPathsMap::const_iterator& it,
                            const storage::FileSystemURL trash_subdirectory,
                            base::File::Error error);
   base::FilePath MakeRelativeFromBasePath(const base::FilePath& absolute_path);
@@ -160,7 +160,7 @@
 
   // Maintains the free space required to write all the metadata files along
   // with the underlying locations of the .Trash/{files,info} directories.
-  TrashPathsMap free_space_map_;
+  trash::TrashPathsMap free_space_map_;
 
   // Stores the size reported by the last progress update so we can compute the
   // delta on the next progress update.
@@ -189,7 +189,6 @@
   base::WeakPtrFactory<TrashIOTask> weak_ptr_factory_{this};
 };
 
-}  // namespace io_task
-}  // namespace file_manager
+}  // namespace file_manager::io_task
 
 #endif  // CHROME_BROWSER_ASH_FILE_MANAGER_TRASH_IO_TASK_H_
diff --git a/chrome/browser/ash/file_manager/trash_io_task_unittest.cc b/chrome/browser/ash/file_manager/trash_io_task_unittest.cc
index 3f3b366..0329edf 100644
--- a/chrome/browser/ash/file_manager/trash_io_task_unittest.cc
+++ b/chrome/browser/ash/file_manager/trash_io_task_unittest.cc
@@ -26,8 +26,7 @@
 #include "storage/common/file_system/file_system_types.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
-namespace file_manager {
-namespace io_task {
+namespace file_manager::io_task {
 namespace {
 
 using ::base::test::RunClosure;
@@ -69,10 +68,11 @@
 };
 
 void AssertTrashSetup(const base::FilePath& parent_path) {
-  base::FilePath trash_path = parent_path.Append(kTrashFolderName);
+  base::FilePath trash_path = parent_path.Append(trash::kTrashFolderName);
   ASSERT_TRUE(base::DirectoryExists(trash_path));
-  ASSERT_TRUE(base::DirectoryExists(trash_path.Append(kFilesFolderName)));
-  ASSERT_TRUE(base::DirectoryExists(trash_path.Append(kInfoFolderName)));
+  ASSERT_TRUE(
+      base::DirectoryExists(trash_path.Append(trash::kFilesFolderName)));
+  ASSERT_TRUE(base::DirectoryExists(trash_path.Append(trash::kInfoFolderName)));
 }
 
 void ExpectFileContents(const base::FilePath& path,
@@ -380,13 +380,13 @@
   const base::FilePath trash_path =
       crostini_dir_.AppendASCII(".local/share/Trash");
   const base::FilePath files_path =
-      GenerateTrashPath(trash_path, kFilesFolderName, file_name);
+      trash::GenerateTrashPath(trash_path, trash::kFilesFolderName, file_name);
   ExpectFileContents(files_path, foo_contents);
 
   // Ensure the contents of the files at
   // .local/share/Trash/info/foo.txt.trashinfo contains the expected content.
   const base::FilePath info_path =
-      GenerateTrashPath(trash_path, kInfoFolderName, file_name);
+      trash::GenerateTrashPath(trash_path, trash::kInfoFolderName, file_name);
   ExpectFileContents(info_path, file_trashinfo_contents);
 }
 
@@ -438,5 +438,4 @@
 }
 
 }  // namespace
-}  // namespace io_task
-}  // namespace file_manager
+}  // namespace file_manager::io_task
diff --git a/chrome/browser/ash/file_manager/trash_unittest_base.cc b/chrome/browser/ash/file_manager/trash_unittest_base.cc
index 62de372..99f9b733 100644
--- a/chrome/browser/ash/file_manager/trash_unittest_base.cc
+++ b/chrome/browser/ash/file_manager/trash_unittest_base.cc
@@ -19,8 +19,7 @@
 #include "storage/browser/file_system/external_mount_points.h"
 #include "storage/browser/test/test_file_system_context.h"
 
-namespace file_manager {
-namespace io_task {
+namespace file_manager::io_task {
 
 TrashBaseTest::TrashBaseTest() = default;
 
@@ -132,14 +131,16 @@
 
 const base::FilePath TrashBaseTest::GenerateInfoPath(
     const std::string& file_name) {
-  return GenerateTrashPath(downloads_dir_.Append(kTrashFolderName),
-                           kInfoFolderName, file_name);
+  return trash::GenerateTrashPath(
+      downloads_dir_.Append(trash::kTrashFolderName), trash::kInfoFolderName,
+      file_name);
 }
 
 const base::FilePath TrashBaseTest::GenerateFilesPath(
     const std::string& file_name) {
-  return GenerateTrashPath(downloads_dir_.Append(kTrashFolderName),
-                           kFilesFolderName, file_name);
+  return trash::GenerateTrashPath(
+      downloads_dir_.Append(trash::kTrashFolderName), trash::kFilesFolderName,
+      file_name);
 }
 
 const std::string TrashBaseTest::CreateTrashInfoContentsFromPath(
@@ -168,15 +169,14 @@
 
 bool TrashBaseTest::EnsureTrashDirectorySetup(
     const base::FilePath& parent_path) {
-  base::FilePath trash_path = parent_path.Append(kTrashFolderName);
-  if (!base::CreateDirectory(trash_path.Append(kInfoFolderName))) {
+  base::FilePath trash_path = parent_path.Append(trash::kTrashFolderName);
+  if (!base::CreateDirectory(trash_path.Append(trash::kInfoFolderName))) {
     return false;
   }
-  if (!base::CreateDirectory(trash_path.Append(kFilesFolderName))) {
+  if (!base::CreateDirectory(trash_path.Append(trash::kFilesFolderName))) {
     return false;
   }
   return true;
 }
 
-}  // namespace io_task
-}  // namespace file_manager
+}  // namespace file_manager::io_task
diff --git a/chrome/browser/ash/file_manager/trash_unittest_base.h b/chrome/browser/ash/file_manager/trash_unittest_base.h
index a86bcb7..c25642e 100644
--- a/chrome/browser/ash/file_manager/trash_unittest_base.h
+++ b/chrome/browser/ash/file_manager/trash_unittest_base.h
@@ -21,8 +21,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
 
-namespace file_manager {
-namespace io_task {
+namespace file_manager::io_task {
 
 inline constexpr size_t kTestFileSize = 32;
 
@@ -84,7 +83,6 @@
   scoped_refptr<storage::FileSystemContext> file_system_context_;
 };
 
-}  // namespace io_task
-}  // namespace file_manager
+}  // namespace file_manager::io_task
 
 #endif  // CHROME_BROWSER_ASH_FILE_MANAGER_TRASH_IO_TASK_H_
diff --git a/chrome/browser/ash/guest_os/guest_os_session_tracker.cc b/chrome/browser/ash/guest_os/guest_os_session_tracker.cc
index 0cadeb99..785dcf6 100644
--- a/chrome/browser/ash/guest_os/guest_os_session_tracker.cc
+++ b/chrome/browser/ash/guest_os/guest_os_session_tracker.cc
@@ -153,10 +153,20 @@
     return;
   }
   vms_.erase(signal.name());
-  base::EraseIf(guests_,
-                [name = signal.name()](std::pair<GuestId, GuestInfo> pair) {
-                  return pair.first.vm_name == name;
-                });
+  std::vector<GuestId> ids;
+  for (const auto& pair : guests_) {
+    if (pair.first.vm_name != signal.name()) {
+      continue;
+    }
+    ids.push_back(pair.first);
+  }
+  for (const auto& id : ids) {
+    guests_.erase(id);
+    auto cb_list = container_shutdown_callbacks_.find(id);
+    if (cb_list != container_shutdown_callbacks_.end()) {
+      cb_list->second->Notify();
+    }
+  }
 }
 
 // ash::CiceroneClient::Observer overrides.
@@ -204,10 +214,14 @@
   }
   GuestId id{VmType::UNKNOWN, signal.vm_name(), signal.container_name()};
   guests_.erase(id);
+  auto cb_list = container_shutdown_callbacks_.find(id);
+  if (cb_list != container_shutdown_callbacks_.end()) {
+    cb_list->second->Notify();
+  }
 }
 
 base::CallbackListSubscription GuestOsSessionTracker::RunOnceContainerStarted(
-    GuestId id,
+    const GuestId& id,
     base::OnceCallback<void(GuestInfo)> callback) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   auto iter = guests_.find(id);
@@ -227,4 +241,15 @@
   guests_.insert_or_assign(id, info);
 }
 
+base::CallbackListSubscription GuestOsSessionTracker::RunOnShutdown(
+    const GuestId& id,
+    base::OnceCallback<void()> callback) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  auto& cb_list = container_shutdown_callbacks_[id];
+  if (!cb_list) {
+    cb_list = std::make_unique<base::OnceCallbackList<void()>>();
+  }
+  return cb_list->Add(std::move(callback));
+}
+
 }  // namespace guest_os
diff --git a/chrome/browser/ash/guest_os/guest_os_session_tracker.h b/chrome/browser/ash/guest_os/guest_os_session_tracker.h
index 2a2f9bb3..8101277 100644
--- a/chrome/browser/ash/guest_os/guest_os_session_tracker.h
+++ b/chrome/browser/ash/guest_os/guest_os_session_tracker.h
@@ -53,9 +53,15 @@
   // RunOnceContainerStarted hangs forever. We need to list running containers
   // and adopt them, the same as we do for VMs.
   base::CallbackListSubscription RunOnceContainerStarted(
-      GuestId id,
+      const GuestId& id,
       base::OnceCallback<void(GuestInfo)> callback);
 
+  // Runs `callback` when the guest identified by `id` shuts down. To cancel the
+  // callback (e.g. upon timeout) destroy the returned subscription.
+  base::CallbackListSubscription RunOnShutdown(
+      const GuestId& id,
+      base::OnceCallback<void()> callback);
+
   // Returns information about a running guest. Returns nullopt if the guest
   // isn't recognised e.g. it's not running. If you just want to check if a
   // guest is running or not and don't need the info, use `IsRunning` instead
@@ -100,6 +106,8 @@
   base::flat_map<GuestId,
                  std::unique_ptr<base::OnceCallbackList<void(GuestInfo)>>>
       container_start_callbacks_;
+  base::flat_map<GuestId, std::unique_ptr<base::OnceCallbackList<void()>>>
+      container_shutdown_callbacks_;
 
   base::WeakPtrFactory<GuestOsSessionTracker> weak_ptr_factory_{this};
 };
diff --git a/chrome/browser/ash/guest_os/guest_os_session_tracker_unittest.cc b/chrome/browser/ash/guest_os/guest_os_session_tracker_unittest.cc
index 7b16295..55b435c 100644
--- a/chrome/browser/ash/guest_os/guest_os_session_tracker_unittest.cc
+++ b/chrome/browser/ash/guest_os/guest_os_session_tracker_unittest.cc
@@ -199,4 +199,27 @@
   EXPECT_FALSE(called);
 }
 
+TEST_F(GuestOsSessionTrackerTest, RunOnContainerShutdown) {
+  FakeConciergeClient()->NotifyVmStarted(vm_started_signal_);
+  FakeCiceroneClient()->NotifyContainerStarted(container_started_signal_);
+  GuestId id{VmType::UNKNOWN, "vm_name", "penguin"};
+  bool called = false;
+  auto _ = tracker_.RunOnShutdown(
+      id, base::BindLambdaForTesting([&called]() { called = true; }));
+  FakeCiceroneClient()->NotifyContainerShutdownSignal(
+      container_shutdown_signal_);
+  EXPECT_TRUE(called);
+}
+
+TEST_F(GuestOsSessionTrackerTest, RunOnVmShutdown) {
+  FakeConciergeClient()->NotifyVmStarted(vm_started_signal_);
+  FakeCiceroneClient()->NotifyContainerStarted(container_started_signal_);
+  GuestId id{VmType::UNKNOWN, "vm_name", "penguin"};
+  bool called = false;
+  auto _ = tracker_.RunOnShutdown(
+      id, base::BindLambdaForTesting([&called]() { called = true; }));
+  FakeConciergeClient()->NotifyVmStopped(vm_shutdown_signal_);
+  EXPECT_TRUE(called);
+}
+
 }  // namespace guest_os
diff --git a/chrome/browser/ash/hats/hats_config.cc b/chrome/browser/ash/hats/hats_config.cc
index 587bce2f..5d3e5727 100644
--- a/chrome/browser/ash/hats/hats_config.cc
+++ b/chrome/browser/ash/hats/hats_config.cc
@@ -147,4 +147,14 @@
         kHatsPersonalizationWallpaperSurveyCycleEndTs,  // cycle_end_timestamp_pref_name
 };
 
+// MediaApp PDF Editing experience survey -- shown after a user clicks `Save`
+// after editing a PDF in the MediaApp (Gallery), and the save is complete.
+const HatsConfig kHatsMediaAppPdfSurvey = {
+    ::features::kHappinessTrackingMediaAppPdf,        // feature
+    "Browser.ChromeOS.HatsSatisfaction.MediaAppPdf",  // histogram_name
+    base::Days(7),                                    // new_device_threshold
+    prefs::kHatsMediaAppPdfIsSelected,                // hatsIsSelectedPrefName
+    prefs::kHatsMediaAppPdfCycleEndTs,  // hatsCycleEndTimestampPrefName
+};
+
 }  // namespace ash
diff --git a/chrome/browser/ash/hats/hats_config.h b/chrome/browser/ash/hats/hats_config.h
index 973f761..542a806f 100644
--- a/chrome/browser/ash/hats/hats_config.h
+++ b/chrome/browser/ash/hats/hats_config.h
@@ -56,6 +56,7 @@
 extern const HatsConfig kHatsPersonalizationAvatarSurvey;
 extern const HatsConfig kHatsPersonalizationScreensaverSurvey;
 extern const HatsConfig kHatsPersonalizationWallpaperSurvey;
+extern const HatsConfig kHatsMediaAppPdfSurvey;
 
 }  // namespace ash
 
diff --git a/chrome/browser/ash/login/saml/in_session_password_sync_manager.cc b/chrome/browser/ash/login/saml/in_session_password_sync_manager.cc
index eddbf10b..254725f 100644
--- a/chrome/browser/ash/login/saml/in_session_password_sync_manager.cc
+++ b/chrome/browser/ash/login/saml/in_session_password_sync_manager.cc
@@ -291,6 +291,12 @@
   return lock_screen_start_reauth_dialog_->GetDialogWidth();
 }
 
+content::WebContents* InSessionPasswordSyncManager::GetDialogWebContents() {
+  if (!lock_screen_start_reauth_dialog_)
+    return nullptr;
+  return lock_screen_start_reauth_dialog_->GetWebContents();
+}
+
 bool InSessionPasswordSyncManager::IsReauthDialogLoadedForTesting(
     base::OnceClosure callback) {
   if (is_dialog_loaded_for_testing_)
diff --git a/chrome/browser/ash/login/saml/in_session_password_sync_manager.h b/chrome/browser/ash/login/saml/in_session_password_sync_manager.h
index 9793c707..4858656 100644
--- a/chrome/browser/ash/login/saml/in_session_password_sync_manager.h
+++ b/chrome/browser/ash/login/saml/in_session_password_sync_manager.h
@@ -117,6 +117,9 @@
   // Get lockscreen reauth dialog width.
   int GetDialogWidth();
 
+  // Get web contents of lockscreen reauth dialog.
+  content::WebContents* GetDialogWebContents();
+
   // Check if reauth dialog is loaded and ready for testing.
   bool IsReauthDialogLoadedForTesting(base::OnceClosure callback);
 
diff --git a/chrome/browser/ash/login/saml/lockscreen_reauth_dialog_test_helper.cc b/chrome/browser/ash/login/saml/lockscreen_reauth_dialog_test_helper.cc
index 38cc44ba..7ead5d8b 100644
--- a/chrome/browser/ash/login/saml/lockscreen_reauth_dialog_test_helper.cc
+++ b/chrome/browser/ash/login/saml/lockscreen_reauth_dialog_test_helper.cc
@@ -69,6 +69,29 @@
   return dialog_test_helper;
 }
 
+// static
+absl::optional<LockScreenReauthDialogTestHelper>
+LockScreenReauthDialogTestHelper::StartSamlAndWaitForIdpPageLoad() {
+  absl::optional<LockScreenReauthDialogTestHelper> reauth_dialog_helper =
+      LockScreenReauthDialogTestHelper::ShowDialogAndWait();
+  if (!reauth_dialog_helper.has_value()) {
+    return absl::nullopt;
+  }
+
+  reauth_dialog_helper->ForceSamlRedirect();
+
+  // Expect the 'Verify Account' screen (the first screen the dialog shows) to
+  // be visible and proceed to the SAML page.
+  reauth_dialog_helper->WaitForVerifyAccountScreen();
+  reauth_dialog_helper->ClickVerifyButton();
+
+  reauth_dialog_helper->WaitForSamlScreen();
+  reauth_dialog_helper->ExpectVerifyAccountScreenHidden();
+
+  reauth_dialog_helper->WaitForIdpPageLoad();
+  return reauth_dialog_helper;
+}
+
 bool LockScreenReauthDialogTestHelper::ShowDialogAndWaitImpl() {
   // Check precondition: Screen is locked.
   if (!session_manager::SessionManager::Get()->IsScreenLocked()) {
diff --git a/chrome/browser/ash/login/saml/lockscreen_reauth_dialog_test_helper.h b/chrome/browser/ash/login/saml/lockscreen_reauth_dialog_test_helper.h
index 4c227ec..cc6a362 100644
--- a/chrome/browser/ash/login/saml/lockscreen_reauth_dialog_test_helper.h
+++ b/chrome/browser/ash/login/saml/lockscreen_reauth_dialog_test_helper.h
@@ -36,6 +36,12 @@
   // Returns an empty `absl::optional` if the operation fails.
   static absl::optional<LockScreenReauthDialogTestHelper> ShowDialogAndWait();
 
+  // Triggers the online re-authentication dialog, clicks through VerifyAccount
+  // screen and waits for IdP page to load. Returns an empty `absl::optional` if
+  // the operation fails.
+  static absl::optional<LockScreenReauthDialogTestHelper>
+  StartSamlAndWaitForIdpPageLoad();
+
   ~LockScreenReauthDialogTestHelper();
 
   // Non-copyable, movable.
diff --git a/chrome/browser/ash/login/saml/saml_browsertest.cc b/chrome/browser/ash/login/saml/saml_browsertest.cc
index 1c4dc61..dc667c23 100644
--- a/chrome/browser/ash/login/saml/saml_browsertest.cc
+++ b/chrome/browser/ash/login/saml/saml_browsertest.cc
@@ -32,7 +32,9 @@
 #include "chrome/browser/ash/attestation/mock_machine_certificate_uploader.h"
 #include "chrome/browser/ash/attestation/tpm_challenge_key.h"
 #include "chrome/browser/ash/login/existing_user_controller.h"
+#include "chrome/browser/ash/login/lock/screen_locker_tester.h"
 #include "chrome/browser/ash/login/saml/fake_saml_idp_mixin.h"
+#include "chrome/browser/ash/login/saml/lockscreen_reauth_dialog_test_helper.h"
 #include "chrome/browser/ash/login/startup_utils.h"
 #include "chrome/browser/ash/login/test/device_state_mixin.h"
 #include "chrome/browser/ash/login/test/embedded_policy_test_server_mixin.h"
@@ -1677,16 +1679,49 @@
 // pages is controlled by the kLoginVideoCaptureAllowedUrls pref rather than the
 // underlying user content setting.
 IN_PROC_BROWSER_TEST_P(SAMLPolicyTest, TestLoginMediaPermission) {
-  fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html");
+  fake_saml_idp()->SetLoginHTMLTemplate("saml_api_login.html");
+  SetLoginBehaviorPolicyToSAMLInterstitial();
+  WaitForSigninScreen();
 
+  if (GetParam()) {
+    ShowSAMLLoginForm();
+    MaybeWaitForSAMLToLoad();
+  } else {
+    ShowSAMLInterstitial();
+    ClickNextOnSAMLInterstitialPage();
+  }
+
+  const GURL url0(fake_saml_idp()->GetSamlPageUrl());
   const GURL url1("https://google.com");
   const GURL url2("https://corp.example.com");
   const GURL url3("https://not-allowed.com");
-  SetLoginVideoCaptureAllowedUrls({url1, url2});
-  WaitForSigninScreen();
+  SetLoginVideoCaptureAllowedUrls({url0, url1, url2});
 
-  // Make sure WebUI is loaded.
-  LoginDisplayHost::default_host()->GetWizardController();
+  // Trigger the permission check from the js side.
+  {
+    bool get_user_media_success = false;
+    ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
+        SigninFrameJS().web_contents(),
+        "navigator.getUserMedia("
+        "    {video: true},"
+        "    function() { window.domAutomationController.send(true); },"
+        "    function() { window.domAutomationController.send(false); });",
+        &get_user_media_success));
+    ASSERT_TRUE(get_user_media_success);
+  }
+  {
+    bool get_user_media_success = true;
+    ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
+        SigninFrameJS().web_contents(),
+        "navigator.getUserMedia("
+        "    {audio: true},"
+        "    function() { window.domAutomationController.send(true); },"
+        "    function() { window.domAutomationController.send(false); });",
+        &get_user_media_success));
+    ASSERT_FALSE(get_user_media_success);
+  }
+
+  // Check permissions directly by calling the `CheckMediaAccessPermission`.
   content::WebContents* web_contents = GetLoginUI()->GetWebContents();
   content::WebContentsDelegate* web_contents_delegate =
       web_contents->GetDelegate();
@@ -1723,6 +1758,72 @@
       blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE));
 }
 
+// Tests that requesting webcam access from the lock screen works correctly.
+IN_PROC_BROWSER_TEST_P(SAMLPolicyTest, TestLockMediaPermission) {
+  SetSAMLOfflineSigninTimeLimitPolicy(0);
+  const GURL url0(fake_saml_idp()->GetSamlPageUrl());
+  const GURL url1("https://google.com");
+  const GURL url2("https://corp.example.com");
+  const GURL url3("https://not-allowed.com");
+  SetLoginVideoCaptureAllowedUrls({url0, url1, url2});
+  ShowGAIALoginForm();
+  LogInWithSAML(saml_test_users::kFirstUserCorpExampleComEmail,
+                kTestAuthSIDCookie1, kTestAuthLSIDCookie1);
+  ScreenLockerTester().Lock();
+
+  absl::optional<LockScreenReauthDialogTestHelper> reauth_dialog_helper =
+      LockScreenReauthDialogTestHelper::StartSamlAndWaitForIdpPageLoad();
+  ASSERT_TRUE(reauth_dialog_helper);
+
+  {
+    bool get_user_media_success = false;
+    ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
+        reauth_dialog_helper->SigninFrameJS().web_contents(),
+        "navigator.getUserMedia("
+        "    {video: true},"
+        "    function() { window.domAutomationController.send(true); },"
+        "    function() { window.domAutomationController.send(false); });",
+        &get_user_media_success));
+    ASSERT_TRUE(get_user_media_success);
+  }
+  {
+    bool get_user_media_success = true;
+    ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
+        reauth_dialog_helper->SigninFrameJS().web_contents(),
+        "navigator.getUserMedia("
+        "    {audio: true},"
+        "    function() { window.domAutomationController.send(true); },"
+        "    function() { window.domAutomationController.send(false); });",
+        &get_user_media_success));
+    ASSERT_FALSE(get_user_media_success);
+  }
+
+  // Check permissions directly by calling the `CheckMediaAccessPermission`.
+  // Video devices should be allowed from the lock screen for specified urls.
+  content::WebContents* web_contents =
+      reauth_dialog_helper->DialogWebContents();
+  content::WebContentsDelegate* web_contents_delegate =
+      web_contents->GetDelegate();
+
+  // Mic should always be blocked.
+  EXPECT_FALSE(web_contents_delegate->CheckMediaAccessPermission(
+      web_contents->GetPrimaryMainFrame(), url1,
+      blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE));
+
+  // Camera should be allowed if allowed by the allowlist, otherwise blocked.
+  EXPECT_TRUE(web_contents_delegate->CheckMediaAccessPermission(
+      web_contents->GetPrimaryMainFrame(), url1,
+      blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE));
+
+  EXPECT_TRUE(web_contents_delegate->CheckMediaAccessPermission(
+      web_contents->GetPrimaryMainFrame(), url2,
+      blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE));
+
+  EXPECT_FALSE(web_contents_delegate->CheckMediaAccessPermission(
+      web_contents->GetPrimaryMainFrame(), url3,
+      blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE));
+}
+
 INSTANTIATE_TEST_SUITE_P(All, SAMLPolicyTest, testing::Bool());
 
 class SAMLPasswordAttributesTest : public SAMLPolicyTest {
diff --git a/chrome/browser/ash/login/saml/saml_lockscreen_browsertest.cc b/chrome/browser/ash/login/saml/saml_lockscreen_browsertest.cc
index 89da72e..f59a588 100644
--- a/chrome/browser/ash/login/saml/saml_lockscreen_browsertest.cc
+++ b/chrome/browser/ash/login/saml/saml_lockscreen_browsertest.cc
@@ -148,29 +148,10 @@
 
   AccountId GetAccountId() { return logged_in_user_mixin_.GetAccountId(); }
 
-  absl::optional<LockScreenReauthDialogTestHelper>
-  StartSamlAndWaitForIdpPageLoad() {
-    absl::optional<LockScreenReauthDialogTestHelper> reauth_dialog_helper =
-        LockScreenReauthDialogTestHelper::ShowDialogAndWait();
-    DCHECK(reauth_dialog_helper);
-    reauth_dialog_helper->ForceSamlRedirect();
-
-    // Expect the 'Verify Account' screen (the first screen the dialog shows) to
-    // be visible and proceed to the SAML page.
-    reauth_dialog_helper->WaitForVerifyAccountScreen();
-    reauth_dialog_helper->ClickVerifyButton();
-
-    reauth_dialog_helper->WaitForSamlScreen();
-    reauth_dialog_helper->ExpectVerifyAccountScreenHidden();
-
-    reauth_dialog_helper->WaitForIdpPageLoad();
-    return reauth_dialog_helper;
-  }
-
   // Go through online authentication (with saml) flow on the lock screen.
   void UnlockWithSAML() {
     absl::optional<LockScreenReauthDialogTestHelper> reauth_dialog_helper =
-        StartSamlAndWaitForIdpPageLoad();
+        LockScreenReauthDialogTestHelper::StartSamlAndWaitForIdpPageLoad();
 
     // Fill-in the SAML IdP form and submit.
     test::JSChecker signin_frame_js = reauth_dialog_helper->SigninFrameJS();
@@ -254,7 +235,7 @@
   ScreenLockerTester().Lock();
 
   absl::optional<LockScreenReauthDialogTestHelper> reauth_dialog_helper =
-      StartSamlAndWaitForIdpPageLoad();
+      LockScreenReauthDialogTestHelper::StartSamlAndWaitForIdpPageLoad();
 
   reauth_dialog_helper->ClickCancelButtonOnSamlScreen();
 
@@ -273,7 +254,7 @@
   ScreenLockerTester().Lock();
 
   absl::optional<LockScreenReauthDialogTestHelper> reauth_dialog_helper =
-      StartSamlAndWaitForIdpPageLoad();
+      LockScreenReauthDialogTestHelper::StartSamlAndWaitForIdpPageLoad();
 
   content::DOMMessageQueue message_queue(
       reauth_dialog_helper->DialogWebContents());
@@ -316,7 +297,7 @@
   ScreenLockerTester().Lock();
 
   absl::optional<LockScreenReauthDialogTestHelper> reauth_dialog_helper =
-      StartSamlAndWaitForIdpPageLoad();
+      LockScreenReauthDialogTestHelper::StartSamlAndWaitForIdpPageLoad();
 
   test::JSChecker signin_frame_js = reauth_dialog_helper->SigninFrameJS();
   signin_frame_js.Evaluate(
@@ -350,7 +331,7 @@
   ScreenLockerTester().Lock();
 
   absl::optional<LockScreenReauthDialogTestHelper> reauth_dialog_helper =
-      StartSamlAndWaitForIdpPageLoad();
+      LockScreenReauthDialogTestHelper::StartSamlAndWaitForIdpPageLoad();
 
   // Fill-in the SAML IdP form and submit.
   test::JSChecker signin_frame_js = reauth_dialog_helper->SigninFrameJS();
@@ -387,7 +368,7 @@
   ScreenLockerTester().Lock();
 
   absl::optional<LockScreenReauthDialogTestHelper> reauth_dialog_helper =
-      StartSamlAndWaitForIdpPageLoad();
+      LockScreenReauthDialogTestHelper::StartSamlAndWaitForIdpPageLoad();
 
   // Fill-in the SAML IdP form and submit.
   test::JSChecker signin_frame_js = reauth_dialog_helper->SigninFrameJS();
@@ -427,7 +408,7 @@
   ScreenLockerTester().Lock();
 
   absl::optional<LockScreenReauthDialogTestHelper> reauth_dialog_helper =
-      StartSamlAndWaitForIdpPageLoad();
+      LockScreenReauthDialogTestHelper::StartSamlAndWaitForIdpPageLoad();
 
   // Authenticate in the IdP with another account other than the one used in
   // sign in.
diff --git a/chrome/browser/ash/login/test/js_checker.h b/chrome/browser/ash/login/test/js_checker.h
index 6b28cad..c72df1d 100644
--- a/chrome/browser/ash/login/test/js_checker.h
+++ b/chrome/browser/ash/login/test/js_checker.h
@@ -235,6 +235,8 @@
     web_contents_ = web_contents;
   }
 
+  content::WebContents* web_contents() { return web_contents_; }
+
  private:
   void GetBoolImpl(const std::string& expression, bool* result);
   void GetIntImpl(const std::string& expression, int* result);
diff --git a/chrome/browser/ash/login/webview_login_browsertest.cc b/chrome/browser/ash/login/webview_login_browsertest.cc
index bda313be..4425787 100644
--- a/chrome/browser/ash/login/webview_login_browsertest.cc
+++ b/chrome/browser/ash/login/webview_login_browsertest.cc
@@ -956,34 +956,6 @@
   EXPECT_FALSE(found_signin_frame_partition_1);
 }
 
-// Tests that requesting webcam access from the login screen works correctly.
-// This is needed for taking profile pictures.
-IN_PROC_BROWSER_TEST_F(WebviewLoginTest, RequestCamera) {
-  WaitForGaiaPageLoad();
-
-  // Video devices should be allowed from the login screen.
-  content::WebContents* web_contents = GetLoginUI()->GetWebContents();
-  bool getUserMediaSuccess = false;
-  ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
-      web_contents->GetPrimaryMainFrame(),
-      "navigator.getUserMedia("
-      "    {video: true},"
-      "    function() { window.domAutomationController.send(true); },"
-      "    function() { window.domAutomationController.send(false); });",
-      &getUserMediaSuccess));
-  EXPECT_TRUE(getUserMediaSuccess);
-
-  // Audio devices should be denied from the login screen.
-  ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
-      web_contents->GetPrimaryMainFrame(),
-      "navigator.getUserMedia("
-      "    {audio: true},"
-      "    function() { window.domAutomationController.send(true); },"
-      "    function() { window.domAutomationController.send(false); });",
-      &getUserMediaSuccess));
-  EXPECT_FALSE(getUserMediaSuccess);
-}
-
 enum class FrameUrlOrigin { kSameOrigin, kDifferentOrigin };
 
 // Parametrized test fixture that configures FakeGaia to server an iframe in the
diff --git a/chrome/browser/ash/system_extensions/system_extensions_browsertest.cc b/chrome/browser/ash/system_extensions/system_extensions_browsertest.cc
index 6cfcf25..11fc4176 100644
--- a/chrome/browser/ash/system_extensions/system_extensions_browsertest.cc
+++ b/chrome/browser/ash/system_extensions/system_extensions_browsertest.cc
@@ -6,11 +6,14 @@
 #include "ash/constants/ash_switches.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
+#include "base/memory/weak_ptr.h"
 #include "base/path_service.h"
+#include "base/scoped_observation.h"
 #include "base/test/bind.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/threading/thread_restrictions.h"
 #include "chrome/browser/ash/system_extensions/system_extensions_install_manager.h"
+#include "chrome/browser/ash/system_extensions/system_extensions_profile_utils.h"
 #include "chrome/browser/ash/system_extensions/system_extensions_provider.h"
 #include "chrome/browser/ash/system_extensions/system_extensions_provider_factory.h"
 #include "chrome/browser/profiles/profile.h"
@@ -19,6 +22,8 @@
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/service_worker_context.h"
+#include "content/public/browser/storage_partition.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/page_type.h"
 #include "content/public/test/browser_test.h"
@@ -44,48 +49,146 @@
   return test_dir.Append("system_extensions").Append("basic_system_extension");
 }
 
-// Class that returns the result of the first System Extension service worker it
-// sees.
-class ServiceWorkerRegistrationObserver
+// Wrapper around base::OneShotEvent that allows callers to signal with
+// arguments.
+template <typename... Args>
+class OneShotEventWrapper {
+ public:
+  OneShotEventWrapper() = default;
+
+  ~OneShotEventWrapper() = default;
+
+  void Signal(Args... args) {
+    run_with_args_ = base::BindRepeating(&OneShotEventWrapper::RunWithArgs,
+                                         weak_ptr_factory_.GetWeakPtr(),
+                                         std::forward<Args>(args)...);
+    one_shot_event_.Signal();
+  }
+
+  void Post(const base::Location& from_here,
+            base::OnceCallback<void(Args...)> task) {
+    one_shot_event_.Post(
+        from_here,
+        base::BindOnce(&OneShotEventWrapper::RunTask,
+                       weak_ptr_factory_.GetWeakPtr(), std::move(task)));
+  }
+
+ private:
+  void RunTask(base::OnceCallback<void(Args...)> task) {
+    run_with_args_.Run(std::move(task));
+  }
+
+  void RunWithArgs(Args... args, base::OnceCallback<void(Args...)> task) {
+    std::move(task).Run(std::forward<Args>(args)...);
+  }
+
+  base::OneShotEvent one_shot_event_;
+  base::RepeatingCallback<void(base::OnceCallback<void(Args...)>)>
+      run_with_args_;
+  base::WeakPtrFactory<OneShotEventWrapper> weak_ptr_factory_{this};
+};
+
+// Class that can be used to wait for SystemExtensionsInstallManager events.
+// If an event is triggered more than once, this class will CHECK.
+class TestInstallationEventsWaiter
     : public SystemExtensionsInstallManager::Observer {
  public:
-  explicit ServiceWorkerRegistrationObserver(SystemExtensionsProvider& provider)
+  explicit TestInstallationEventsWaiter(SystemExtensionsProvider& provider)
       : install_manager_(provider.install_manager()) {
-    install_manager_.AddObserver(this);
+    observation_.Observe(&install_manager_);
   }
-  ~ServiceWorkerRegistrationObserver() override {}
 
-  // Returns the saved result or waits to get a result and returns it.
+  ~TestInstallationEventsWaiter() override = default;
+
+  // Returns the result of a Service Worker registration. Waits if there hasn't
+  // been one yet.
   std::pair<SystemExtensionId, blink::ServiceWorkerStatusCode>
-  GetIdAndStatusCode() {
-    if (id_.has_value())
-      return {id_.value(), status_code_.value()};
+  WaitForServiceWorkerRegistered() {
+    absl::optional<SystemExtensionId> id;
+    absl::optional<blink::ServiceWorkerStatusCode> status_code;
 
-    run_loop_.Run();
-    return {id_.value(), status_code_.value()};
+    base::RunLoop run_loop;
+    on_service_worker_registered_.Post(
+        FROM_HERE,
+        base::BindLambdaForTesting(
+            [&](SystemExtensionId returned_id,
+                blink::ServiceWorkerStatusCode returned_status_code) {
+              id = returned_id;
+              status_code = returned_status_code;
+              run_loop.Quit();
+            }));
+    run_loop.Run();
+
+    return {id.value(), status_code.value()};
+  }
+
+  // Returns the result of a Service Worker unregistration. Waits if there
+  // hasn't been one yet.
+  std::pair<SystemExtensionId, bool> WaitForServiceWorkerUnregistered() {
+    absl::optional<SystemExtensionId> id;
+    absl::optional<bool> succeeded;
+
+    base::RunLoop run_loop;
+    on_service_worker_unregistered_.Post(
+        FROM_HERE, base::BindLambdaForTesting([&](SystemExtensionId returned_id,
+                                                  bool returned_succeeded) {
+          id = returned_id;
+          succeeded = returned_succeeded;
+          run_loop.Quit();
+        }));
+    run_loop.Run();
+
+    return {id.value(), succeeded.value()};
+  }
+
+  // Returns the result of a asset deletion operations. Waits if there
+  // hasn't been one yet.
+  std::pair<SystemExtensionId, bool> WaitForAssetsDeleted() {
+    absl::optional<SystemExtensionId> id;
+    absl::optional<bool> succeeded;
+
+    base::RunLoop run_loop;
+    on_assets_deleted_.Post(
+        FROM_HERE, base::BindLambdaForTesting([&](SystemExtensionId returned_id,
+                                                  bool returned_succeeded) {
+          id = returned_id;
+          succeeded = returned_succeeded;
+          run_loop.Quit();
+        }));
+    run_loop.Run();
+
+    return {id.value(), succeeded.value()};
   }
 
   // SystemExtensionsInstallManager::Observer
   void OnServiceWorkerRegistered(
       const SystemExtensionId& id,
       blink::ServiceWorkerStatusCode status_code) override {
-    install_manager_.RemoveObserver(this);
+    on_service_worker_registered_.Signal(id, status_code);
+  }
 
-    // Should happen because we unregistered as observers.
-    DCHECK(!id_.has_value());
+  void OnServiceWorkerUnregistered(const SystemExtensionId& id,
+                                   bool succeeded) override {
+    on_service_worker_unregistered_.Signal(id, succeeded);
+  }
 
-    id_ = id;
-    status_code_ = status_code;
-    run_loop_.Quit();
+  void OnSystemExtensionAssetsDeleted(const SystemExtensionId& id,
+                                      bool succeeded) override {
+    on_assets_deleted_.Signal(id, succeeded);
   }
 
  private:
+  OneShotEventWrapper<SystemExtensionId, blink::ServiceWorkerStatusCode>
+      on_service_worker_registered_;
+  OneShotEventWrapper<SystemExtensionId, bool> on_service_worker_unregistered_;
+  OneShotEventWrapper<SystemExtensionId, bool> on_assets_deleted_;
+
+  base::ScopedObservation<SystemExtensionsInstallManager,
+                          SystemExtensionsInstallManager::Observer>
+      observation_{this};
+
   // Should be present for the duration of the test.
   SystemExtensionsInstallManager& install_manager_;
-
-  base::RunLoop run_loop_;
-  absl::optional<SystemExtensionId> id_;
-  absl::optional<blink::ServiceWorkerStatusCode> status_code_;
 };
 
 class SystemExtensionsBrowserTest : public InProcessBrowserTest {
@@ -155,7 +258,7 @@
   auto& provider = SystemExtensionsProvider::Get(browser()->profile());
   auto& install_manager = provider.install_manager();
 
-  ServiceWorkerRegistrationObserver sw_registration_observer(provider);
+  TestInstallationEventsWaiter waiter(provider);
   base::RunLoop run_loop;
   install_manager.InstallUnpackedExtensionFromDir(
       GetBasicSystemExtensionDir(),
@@ -166,13 +269,81 @@
       }));
   run_loop.Run();
 
-  const auto [id, status_code] = sw_registration_observer.GetIdAndStatusCode();
+  const auto [id, status_code] = waiter.WaitForServiceWorkerRegistered();
   EXPECT_EQ(kTestSystemExtensionId, id);
   EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status_code);
 
   TestInstalledTestExtensionWorks();
 }
 
+IN_PROC_BROWSER_TEST_F(SystemExtensionsBrowserTest, Uninstall_Success) {
+  auto& provider = SystemExtensionsProvider::Get(browser()->profile());
+  auto& install_manager = provider.install_manager();
+
+  TestInstallationEventsWaiter waiter(provider);
+
+  {
+    // Install and wait for the service worker to be registered.
+    base::RunLoop run_loop;
+    install_manager.InstallUnpackedExtensionFromDir(
+        GetBasicSystemExtensionDir(),
+        base::BindLambdaForTesting(
+            [&](InstallStatusOrSystemExtensionId result) { run_loop.Quit(); }));
+    run_loop.Run();
+    waiter.WaitForServiceWorkerRegistered();
+  }
+
+  // Uninstall the extension.
+  install_manager.Uninstall(kTestSystemExtensionId);
+
+  auto& registry = provider.registry();
+  EXPECT_TRUE(registry.GetIds().empty());
+  EXPECT_FALSE(registry.GetById(kTestSystemExtensionId));
+
+  // Test that navigating to the System Extension's resources fails.
+  auto* tab = browser()->tab_strip_model()->GetActiveWebContents();
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(),
+                                           GURL(kTestSystemExtensionIndexURL)));
+  content::NavigationEntry* entry = tab->GetController().GetVisibleEntry();
+  EXPECT_EQ(content::PAGE_TYPE_ERROR, entry->GetPageType());
+
+  {
+    // Test that the resources have been deleted.
+    const auto [id, deletion_succeeded] = waiter.WaitForAssetsDeleted();
+    EXPECT_EQ(id, kTestSystemExtensionId);
+    EXPECT_TRUE(deletion_succeeded);
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    const base::FilePath system_extension_dir = GetDirectoryForSystemExtension(
+        *browser()->profile(), kTestSystemExtensionId);
+    EXPECT_FALSE(base::PathExists(system_extension_dir));
+  }
+
+  {
+    // Test that the service worker has been unregistered.
+    const auto [id, unregistration_succeeded] =
+        waiter.WaitForServiceWorkerUnregistered();
+    EXPECT_EQ(id, kTestSystemExtensionId);
+    EXPECT_TRUE(unregistration_succeeded);
+
+    const GURL scope(kTestSystemExtensionEmptyPathURL);
+    auto* worker_context = browser()
+                               ->profile()
+                               ->GetDefaultStoragePartition()
+                               ->GetServiceWorkerContext();
+
+    base::RunLoop run_loop;
+    worker_context->CheckHasServiceWorker(
+        scope, blink::StorageKey(url::Origin::Create(scope)),
+        base::BindLambdaForTesting(
+            [&](content::ServiceWorkerCapability capability) {
+              EXPECT_EQ(capability,
+                        content::ServiceWorkerCapability::NO_SERVICE_WORKER);
+              run_loop.Quit();
+            }));
+    run_loop.Run();
+  }
+}
+
 IN_PROC_BROWSER_TEST_F(SystemExtensionsSwitchBrowserTest, ExtensionInstalled) {
   auto& provider = SystemExtensionsProvider::Get(browser()->profile());
   auto& install_manager = provider.install_manager();
diff --git a/chrome/browser/ash/system_extensions/system_extensions_install_manager.cc b/chrome/browser/ash/system_extensions/system_extensions_install_manager.cc
index 41000e6e..fadb976 100644
--- a/chrome/browser/ash/system_extensions/system_extensions_install_manager.cc
+++ b/chrome/browser/ash/system_extensions/system_extensions_install_manager.cc
@@ -81,6 +81,7 @@
 void SystemExtensionsInstallManager::StartInstallation(
     OnceInstallCallback final_callback,
     const base::FilePath& unpacked_system_extension_dir) {
+  // Installation Step #1: Convert a manifest into a SystemExtension object.
   sandboxed_unpacker_.GetSystemExtensionFromDir(
       unpacked_system_extension_dir,
       base::BindOnce(
@@ -104,6 +105,8 @@
   const base::FilePath system_extensions_dir =
       GetSystemExtensionsProfileDir(*profile_);
 
+  // Installation Step #2: Copy the System Extensions assets to a profile
+  // directory.
   io_helper_.AsyncCall(&IOHelper::CopyExtensionAssets)
       .WithArgs(unpacked_system_extension_dir, dest_dir, system_extensions_dir)
       .Then(base::BindOnce(
@@ -122,12 +125,15 @@
     return;
   }
 
-  SystemExtensionId id = system_extension.id;
+  // Installation Step #3: Create a WebUIConfig so that resources are served.
   auto config = std::make_unique<SystemExtensionsWebUIConfig>(system_extension);
   content::WebUIConfigMap::GetInstance().AddUntrustedWebUIConfig(
       std::move(config));
 
+  // Installation Step #4: Add the System Extension to the registry.
+  SystemExtensionId id = system_extension.id;
   registry_manager_->AddSystemExtension(std::move(system_extension));
+
   std::move(final_callback).Run(std::move(id));
   base::ThreadTaskRunnerHandle::Get()->PostTask(
       FROM_HERE,
@@ -148,15 +154,17 @@
       blink::mojom::ServiceWorkerUpdateViaCache::kImports);
   blink::StorageKey key(url::Origin::Create(options.scope));
 
+  // Installation Step #5: Register a Service Worker for the System Extension.
   auto* worker_context =
       profile_->GetDefaultStoragePartition()->GetServiceWorkerContext();
   worker_context->RegisterServiceWorker(
       system_extension->service_worker_url, key, options,
-      base::BindOnce(&SystemExtensionsInstallManager::OnRegisterServiceWorker,
-                     weak_ptr_factory_.GetWeakPtr(), system_extension_id));
+      base::BindOnce(
+          &SystemExtensionsInstallManager::NotifyServiceWorkerRegistered,
+          weak_ptr_factory_.GetWeakPtr(), system_extension_id));
 }
 
-void SystemExtensionsInstallManager::OnRegisterServiceWorker(
+void SystemExtensionsInstallManager::NotifyServiceWorkerRegistered(
     const SystemExtensionId& system_extension_id,
     blink::ServiceWorkerStatusCode status_code) {
   if (status_code != blink::ServiceWorkerStatusCode::kOk) {
@@ -169,6 +177,64 @@
     observer.OnServiceWorkerRegistered(system_extension_id, status_code);
 }
 
+void SystemExtensionsInstallManager::Uninstall(
+    const SystemExtensionId& system_extension_id) {
+  auto* system_extension = registry_->GetById(system_extension_id);
+  if (!system_extension) {
+    return;
+  }
+
+  const GURL& scope = system_extension->base_url;
+  const url::Origin& origin = url::Origin::Create(system_extension->base_url);
+
+  // The un-installation steps are in reverse order of the installation steps.
+
+  // Uninstallation Step #1: Unregister the Service Worker.
+  auto* worker_context =
+      profile_->GetDefaultStoragePartition()->GetServiceWorkerContext();
+  blink::StorageKey key(origin);
+  worker_context->UnregisterServiceWorker(
+      scope, key,
+      base::BindOnce(
+          &SystemExtensionsInstallManager::NotifyServiceWorkerUnregistered,
+          weak_ptr_factory_.GetWeakPtr(), system_extension_id));
+
+  // Uninstallation Step #2: Remove the WebUIConfig for the System Extension.
+  content::WebUIConfigMap::GetInstance().RemoveConfig(origin);
+
+  // Uninstallation Step #3: Remove System Extension from the registry.
+  registry_manager_->RemoveSystemExtension(system_extension_id);
+
+  // Uninstallation Step #4: Delete the System Extension assets.
+  io_helper_.AsyncCall(&IOHelper::RemoveExtensionAssets)
+      .WithArgs(GetDirectoryForSystemExtension(*profile_, system_extension_id))
+      .Then(base::BindOnce(&SystemExtensionsInstallManager::NotifyAssetsRemoved,
+                           weak_ptr_factory_.GetWeakPtr(),
+                           system_extension_id));
+}
+
+void SystemExtensionsInstallManager::NotifyServiceWorkerUnregistered(
+    const SystemExtensionId& system_extension_id,
+    bool succeeded) {
+  // TODO(b/238578914): Consider changing UnregisterServiceWorker to pass a
+  // ServiceWorkerStatusCode instead of a bool.
+  if (!succeeded)
+    LOG(ERROR) << "Failed to unregister Service Worker.";
+
+  for (auto& observer : observers_)
+    observer.OnServiceWorkerUnregistered(system_extension_id, succeeded);
+}
+
+void SystemExtensionsInstallManager::NotifyAssetsRemoved(
+    const SystemExtensionId& system_extension_id,
+    bool succeeded) {
+  if (!succeeded)
+    LOG(ERROR) << "Failed to remove System Extension assets.";
+
+  for (auto& observer : observers_)
+    observer.OnSystemExtensionAssetsDeleted(system_extension_id, succeeded);
+}
+
 bool SystemExtensionsInstallManager::IOHelper::CopyExtensionAssets(
     const base::FilePath& unpacked_extension_dir,
     const base::FilePath& dest_dir,
@@ -196,11 +262,20 @@
   // `/{profile_path}/System Extensions/{system_extension_id}/`
   if (!base::CopyDirectory(unpacked_extension_dir, dest_dir,
                            /*recursive=*/true)) {
-    return false;
     LOG(ERROR) << "Failed to copy System Extension assets.";
+    return false;
   }
 
   return true;
 }
 
+bool SystemExtensionsInstallManager::IOHelper::RemoveExtensionAssets(
+    const base::FilePath& system_extension_dir) {
+  if (!base::DeletePathRecursively(system_extension_dir)) {
+    LOG(ERROR) << "Failed to delete System Extension assets.";
+    return false;
+  }
+  return true;
+}
+
 }  // namespace ash
diff --git a/chrome/browser/ash/system_extensions/system_extensions_install_manager.h b/chrome/browser/ash/system_extensions/system_extensions_install_manager.h
index 7678282c..fb12687 100644
--- a/chrome/browser/ash/system_extensions/system_extensions_install_manager.h
+++ b/chrome/browser/ash/system_extensions/system_extensions_install_manager.h
@@ -27,11 +27,25 @@
 
 class SystemExtensionsInstallManager {
  public:
+  // Observer for installation and uninstallation steps events. This should be
+  // used for classes that need to perform an action in response to an
+  // installation step.
+  // TODO(b/241308071): Once it's implemented, suggest using
+  // SystemExtensionsRegistry::Observer for clients that only care about when
+  // a System Extension is installed or uninstalled.
   class Observer : public base::CheckedObserver {
    public:
     virtual void OnServiceWorkerRegistered(
         const SystemExtensionId& system_extension_id,
         blink::ServiceWorkerStatusCode status_code) {}
+
+    virtual void OnServiceWorkerUnregistered(
+        const SystemExtensionId& system_extension_id,
+        bool succeeded) {}
+
+    virtual void OnSystemExtensionAssetsDeleted(
+        const SystemExtensionId& system_extension_id,
+        bool succeeded) {}
   };
 
   SystemExtensionsInstallManager(
@@ -44,6 +58,12 @@
       const SystemExtensionsInstallManager&) = delete;
   ~SystemExtensionsInstallManager();
 
+  void AddObserver(Observer* observer) { observers_.AddObserver(observer); }
+
+  void RemoveObserver(Observer* observer) {
+    observers_.RemoveObserver(observer);
+  }
+
   using OnceInstallCallback =
       base::OnceCallback<void(InstallStatusOrSystemExtensionId)>;
   void InstallUnpackedExtensionFromDir(
@@ -54,11 +74,15 @@
     return on_command_line_install_finished_;
   }
 
-  void AddObserver(Observer* observer) { observers_.AddObserver(observer); }
-
-  void RemoveObserver(Observer* observer) {
-    observers_.RemoveObserver(observer);
-  }
+  // Uninstallation always succeeds.
+  //
+  // Currently only two operations can fail, unregistering the Service Worker,
+  // and deleting the System Extension's assets. Regardless of these operations
+  // failing, the System Extension will be considered uninstalled. Both of these
+  // failure cases will be handled by a garbage collector outside of this class.
+  // TODO(b/241198799): Change this comment once the garbage collector is
+  // implemented.
+  void Uninstall(const SystemExtensionId& system_extension_id);
 
  private:
   // Helper class to run blocking IO operations on a separate thread.
@@ -67,6 +91,7 @@
     bool CopyExtensionAssets(const base::FilePath& unpacked_extension_dir,
                              const base::FilePath& dest_dir,
                              const base::FilePath& system_extensions_dir);
+    bool RemoveExtensionAssets(const base::FilePath& system_extension_dir);
   };
 
   void InstallFromCommandLineIfNecessary();
@@ -83,13 +108,18 @@
                                   SystemExtension system_extension,
                                   bool did_succeed);
   void RegisterServiceWorker(const SystemExtensionId& id);
-  void OnRegisterServiceWorker(const SystemExtensionId& id,
-                               blink::ServiceWorkerStatusCode status_code);
   void DispatchWindowManagerStartEvent(const SystemExtensionId& id,
                                        int64_t version_id,
                                        int process_id,
                                        int thread_id);
 
+  void NotifyServiceWorkerRegistered(
+      const SystemExtensionId& id,
+      blink::ServiceWorkerStatusCode status_code);
+  void NotifyServiceWorkerUnregistered(const SystemExtensionId& id,
+                                       bool succeeded);
+  void NotifyAssetsRemoved(const SystemExtensionId&, bool succeeded);
+
   // Safe because this class is owned by SystemExtensionsProvider which is owned
   // by the profile.
   raw_ptr<Profile> profile_;
diff --git a/chrome/browser/ash/system_extensions/system_extensions_internals_page_handler.cc b/chrome/browser/ash/system_extensions/system_extensions_internals_page_handler.cc
index 7b39756..81a712a 100644
--- a/chrome/browser/ash/system_extensions/system_extensions_internals_page_handler.cc
+++ b/chrome/browser/ash/system_extensions/system_extensions_internals_page_handler.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ash/system_extensions/system_extensions_internals_page_handler.h"
 
+#include "base/callback_helpers.h"
 #include "base/debug/stack_trace.h"
 #include "base/system/sys_info.h"
 #include "chrome/browser/ash/system_extensions/system_extensions_profile_utils.h"
@@ -51,6 +52,28 @@
                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
 }
 
+void SystemExtensionsInternalsPageHandler::IsSystemExtensionInstalled(
+    IsSystemExtensionInstalledCallback callback) {
+  auto& registry = SystemExtensionsProvider::Get(profile_).registry();
+
+  const bool is_installed = !registry.GetIds().empty();
+  std::move(callback).Run(is_installed);
+}
+
+void SystemExtensionsInternalsPageHandler::UninstallSystemExtension(
+    UninstallSystemExtensionCallback callback) {
+  auto scoped_callback_runner = base::ScopedClosureRunner(std::move(callback));
+
+  auto& provider = SystemExtensionsProvider::Get(profile_);
+  auto& registry = provider.registry();
+  const auto ids = registry.GetIds();
+
+  if (ids.empty())
+    return;
+
+  provider.install_manager().Uninstall(ids[0]);
+}
+
 void SystemExtensionsInternalsPageHandler::OnInstallFinished(
     InstallSystemExtensionFromDownloadsDirCallback callback,
     InstallStatusOrSystemExtensionId result) {
diff --git a/chrome/browser/ash/system_extensions/system_extensions_internals_page_handler.h b/chrome/browser/ash/system_extensions/system_extensions_internals_page_handler.h
index e087abf..c71a526 100644
--- a/chrome/browser/ash/system_extensions/system_extensions_internals_page_handler.h
+++ b/chrome/browser/ash/system_extensions/system_extensions_internals_page_handler.h
@@ -31,6 +31,10 @@
   void InstallSystemExtensionFromDownloadsDir(
       const base::SafeBaseName& system_extension_dir_name,
       InstallSystemExtensionFromDownloadsDirCallback callback) override;
+  void IsSystemExtensionInstalled(
+      IsSystemExtensionInstalledCallback callback) override;
+  void UninstallSystemExtension(
+      UninstallSystemExtensionCallback callback) override;
 
  private:
   void OnInstallFinished(
diff --git a/chrome/browser/ash/system_extensions/system_extensions_mutable_registry.cc b/chrome/browser/ash/system_extensions/system_extensions_mutable_registry.cc
index 4f284e6..d3d05136 100644
--- a/chrome/browser/ash/system_extensions/system_extensions_mutable_registry.cc
+++ b/chrome/browser/ash/system_extensions/system_extensions_mutable_registry.cc
@@ -20,6 +20,11 @@
   system_extensions_[id] = std::move(system_extension);
 }
 
+void SystemExtensionsMutableRegistry::RemoveSystemExtension(
+    const SystemExtensionId& system_extension_id) {
+  system_extensions_.erase(system_extension_id);
+}
+
 std::vector<SystemExtensionId> SystemExtensionsMutableRegistry::GetIds() {
   std::vector<SystemExtensionId> extension_ids;
   for (const auto& [id, system_extension] : system_extensions_) {
diff --git a/chrome/browser/ash/system_extensions/system_extensions_mutable_registry.h b/chrome/browser/ash/system_extensions/system_extensions_mutable_registry.h
index 87c00dc..b476ba9a 100644
--- a/chrome/browser/ash/system_extensions/system_extensions_mutable_registry.h
+++ b/chrome/browser/ash/system_extensions/system_extensions_mutable_registry.h
@@ -29,6 +29,10 @@
   // Adds |system_extension| to the map of installed System Extensions.
   void AddSystemExtension(SystemExtension system_extension);
 
+  // Removes the System Extension with `system_extension_id` from the
+  // map of installed System Extensions.
+  void RemoveSystemExtension(const SystemExtensionId& system_extension_id);
+
   // SystemExtensionsRegistry
   std::vector<SystemExtensionId> GetIds() override;
   const SystemExtension* GetById(
diff --git a/chrome/browser/ash/system_extensions/system_extensions_registry_manager.cc b/chrome/browser/ash/system_extensions/system_extensions_registry_manager.cc
index 26d7e434b..c6daa5f9 100644
--- a/chrome/browser/ash/system_extensions/system_extensions_registry_manager.cc
+++ b/chrome/browser/ash/system_extensions/system_extensions_registry_manager.cc
@@ -17,4 +17,9 @@
   registry_.AddSystemExtension(std::move(system_extension));
 }
 
+void SystemExtensionsRegistryManager::RemoveSystemExtension(
+    const SystemExtensionId& system_extension_id) {
+  registry_.RemoveSystemExtension(system_extension_id);
+}
+
 }  // namespace ash
diff --git a/chrome/browser/ash/system_extensions/system_extensions_registry_manager.h b/chrome/browser/ash/system_extensions/system_extensions_registry_manager.h
index 4dbadf29..ddd84aa 100644
--- a/chrome/browser/ash/system_extensions/system_extensions_registry_manager.h
+++ b/chrome/browser/ash/system_extensions/system_extensions_registry_manager.h
@@ -25,11 +25,15 @@
       const SystemExtensionsRegistryManager&) = delete;
   ~SystemExtensionsRegistryManager();
 
+  // Returns the registry this class uses to store SystemExtension instances.
+  SystemExtensionsRegistry& registry() { return registry_; }
+
   // Adds the `system_extension` to the SystemExtensionRegistry.
   void AddSystemExtension(SystemExtension system_extension);
 
-  // Returns the registry this class uses to store SystemExtension instances.
-  SystemExtensionsRegistry& registry() { return registry_; }
+  // Removes the System Extension with `system_extension_id` from the registry
+  // if it exists.
+  void RemoveSystemExtension(const SystemExtensionId& system_extension_id);
 
  private:
   SystemExtensionsMutableRegistry registry_;
diff --git a/chrome/browser/ash/system_web_apps/test_support/system_web_app_browsertest_base.h b/chrome/browser/ash/system_web_apps/test_support/system_web_app_browsertest_base.h
index a0e6464..aba84108 100644
--- a/chrome/browser/ash/system_web_apps/test_support/system_web_app_browsertest_base.h
+++ b/chrome/browser/ash/system_web_apps/test_support/system_web_app_browsertest_base.h
@@ -13,7 +13,7 @@
 #include "chrome/browser/web_applications/test/fake_web_app_provider.h"
 #include "chrome/browser/web_applications/test/profile_test_helper.h"
 #include "chrome/browser/web_applications/test/web_app_test_utils.h"
-#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/mixin_based_in_process_browser_test.h"
 
 class KeyedService;
 
@@ -30,7 +30,7 @@
 enum class SystemWebAppType;
 class SystemWebAppManager;
 
-class SystemWebAppBrowserTestBase : public InProcessBrowserTest {
+class SystemWebAppBrowserTestBase : public MixinBasedInProcessBrowserTest {
  public:
   // Performs common initialization for testing SystemWebAppManager features.
   // If true, |install_mock| installs a WebUIController that serves a mock
diff --git a/chrome/browser/ash/web_applications/demo_mode_app_integration_browsertest.cc b/chrome/browser/ash/web_applications/demo_mode_app_integration_browsertest.cc
index be22d220..eaa749a 100644
--- a/chrome/browser/ash/web_applications/demo_mode_app_integration_browsertest.cc
+++ b/chrome/browser/ash/web_applications/demo_mode_app_integration_browsertest.cc
@@ -45,7 +45,7 @@
   void SetUpOnMainThread() override {
     base::ScopedAllowBlockingForTesting allow_blocking;
     ASSERT_TRUE(component_dir_.CreateUniqueTempDir());
-    content::WebUIConfigMap::GetInstance().RemoveForTesting(
+    content::WebUIConfigMap::GetInstance().RemoveConfig(
         url::Origin::Create(GURL(ash::kChromeUntrustedUIDemoModeAppURL)));
     content::WebUIConfigMap::GetInstance().AddUntrustedWebUIConfig(
         std::make_unique<ash::DemoModeAppUntrustedUIConfig>(
diff --git a/chrome/browser/ash/web_applications/media_app/chrome_media_app_ui_delegate.cc b/chrome/browser/ash/web_applications/media_app/chrome_media_app_ui_delegate.cc
index 8f0a5984..2c7177c 100644
--- a/chrome/browser/ash/web_applications/media_app/chrome_media_app_ui_delegate.cc
+++ b/chrome/browser/ash/web_applications/media_app/chrome_media_app_ui_delegate.cc
@@ -16,6 +16,8 @@
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/ash/file_manager/fileapi_util.h"
 #include "chrome/browser/ash/file_manager/volume_manager.h"
+#include "chrome/browser/ash/hats/hats_config.h"
+#include "chrome/browser/ash/hats/hats_notification_controller.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
@@ -66,6 +68,17 @@
   }
 }
 
+void ChromeMediaAppUIDelegate::MaybeTriggerPdfHats() {
+  Profile* profile = Profile::FromWebUI(web_ui_);
+  const base::flat_map<std::string, std::string> product_specific_data;
+
+  if (ash::HatsNotificationController::ShouldShowSurveyToProfile(
+          profile, ash::kHatsMediaAppPdfSurvey)) {
+    hats_notification_controller_ = new ash::HatsNotificationController(
+        profile, ash::kHatsMediaAppPdfSurvey, product_specific_data);
+  }
+}
+
 void ChromeMediaAppUIDelegate::IsFileArcWritable(
     mojo::PendingRemote<blink::mojom::FileSystemAccessTransferToken> token,
     base::OnceCallback<void(bool)> is_file_arc_writable_callback) {
diff --git a/chrome/browser/ash/web_applications/media_app/chrome_media_app_ui_delegate.h b/chrome/browser/ash/web_applications/media_app/chrome_media_app_ui_delegate.h
index 6dada05..3814fe9 100644
--- a/chrome/browser/ash/web_applications/media_app/chrome_media_app_ui_delegate.h
+++ b/chrome/browser/ash/web_applications/media_app/chrome_media_app_ui_delegate.h
@@ -7,12 +7,17 @@
 
 #include "ash/webui/media_app_ui/media_app_ui_delegate.h"
 #include "base/callback.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "storage/browser/file_system/file_system_url.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/mojom/file_system_access/file_system_access_transfer_token.mojom.h"
 
+namespace ash {
+class HatsNotificationController;
+}
+
 namespace content {
 class WebUI;
 }
@@ -32,6 +37,7 @@
   // MediaAppUIDelegate:
   absl::optional<std::string> OpenFeedbackDialog() override;
   void ToggleBrowserFullscreenMode() override;
+  void MaybeTriggerPdfHats() override;
   void IsFileArcWritable(
       mojo::PendingRemote<blink::mojom::FileSystemAccessTransferToken> token,
       base::OnceCallback<void(bool)> is_file_arc_writable_callback) override;
@@ -49,6 +55,9 @@
                         absl::optional<storage::FileSystemURL> url);
 
   content::WebUI* web_ui_;  // Owns |this|.
+
+  scoped_refptr<ash::HatsNotificationController> hats_notification_controller_;
+
   base::WeakPtrFactory<ChromeMediaAppUIDelegate> weak_ptr_factory_{this};
 };
 
diff --git a/chrome/browser/ash/web_applications/media_app/media_app_integration_browsertest.cc b/chrome/browser/ash/web_applications/media_app/media_app_integration_browsertest.cc
index dd50509b3..2d9fb22 100644
--- a/chrome/browser/ash/web_applications/media_app/media_app_integration_browsertest.cc
+++ b/chrome/browser/ash/web_applications/media_app/media_app_integration_browsertest.cc
@@ -5,6 +5,7 @@
 #include <utility>
 
 #include "ash/constants/ash_features.h"
+#include "ash/constants/ash_switches.h"
 #include "ash/webui/media_app_ui/buildflags.h"
 #include "ash/webui/media_app_ui/test/media_app_ui_browsertest.h"
 #include "ash/webui/media_app_ui/url_constants.h"
@@ -27,11 +28,13 @@
 #include "chrome/browser/ash/file_manager/app_service_file_tasks.h"
 #include "chrome/browser/ash/file_manager/file_manager_test_util.h"
 #include "chrome/browser/ash/file_manager/volume_manager.h"
+#include "chrome/browser/ash/login/test/network_portal_detector_mixin.h"
 #include "chrome/browser/ash/system_web_apps/system_web_app_manager.h"
 #include "chrome/browser/ash/system_web_apps/test_support/system_web_app_integration_test.h"
 #include "chrome/browser/ash/web_applications/media_app/media_web_app_info.h"
 #include "chrome/browser/error_reporting/mock_chrome_js_error_report_processor.h"
 #include "chrome/browser/extensions/component_loader.h"
+#include "chrome/browser/notifications/notification_display_service.h"
 #include "chrome/browser/platform_util.h"
 #include "chrome/browser/ui/app_list/arc/arc_app_utils.h"
 #include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
@@ -58,6 +61,7 @@
 #include "ui/aura/window.h"
 #include "ui/aura/window_observer.h"
 #include "ui/gfx/color_palette.h"
+#include "ui/message_center/public/cpp/notification.h"
 
 using ash::SystemWebAppType;
 using platform_util::OpenOperationResult;
@@ -130,6 +134,11 @@
     // Use a fake audio stream. Some tests make noise otherwise, and could fight
     // with parallel tests for access to the audio device.
     command_line->AppendSwitch(switches::kDisableAudioOutput);
+
+    // Enable HaTS testing.
+    command_line->AppendSwitchASCII(
+        ash::switches::kForceHappinessTrackingSystem,
+        features::kHappinessTrackingMediaAppPdf.name);
   }
 
   void MediaAppLaunchWithFile();
@@ -142,6 +151,9 @@
   // Helper to initiate a test by launching with no files (zero state).
   content::WebContents* LaunchWithNoFiles();
 
+ protected:
+  ash::NetworkPortalDetectorMixin network_portal_detector_{&mixin_host_};
+
  private:
   base::test::ScopedFeatureList feature_list_;
   std::unique_ptr<file_manager::test::FolderInMyFiles> launch_folder_;
@@ -1715,6 +1727,57 @@
   EXPECT_FALSE(app_browser->window()->IsFullscreen());
 }
 
+// Tests that invoking the maybeTriggerPdfHats() MediaApp delegate method fires
+// the notification that asks the user whether to complete a HaTS survey.
+// Note kForceHappinessTrackingSystem is set in the test fixture to ignore the
+// "dice roll" that would normally only show the prompt by chance.
+IN_PROC_BROWSER_TEST_P(MediaAppIntegrationTest, MaybeTriggerPdfHats) {
+  content::WebContents* web_ui = LaunchWithOneTestFile(kFilePdfTall);
+
+  constexpr char kMaybeTriggerPdfHats[] = R"(
+      (async function triggerPdfHats() {
+        await customLaunchData.delegate.maybeTriggerPdfHats();
+        domAutomationController.send("success");
+      })();
+  )";
+
+  struct NotificationWatcher : public NotificationDisplayService::Observer {
+    base::RunLoop run_loop;
+    std::string seen_notification_id;
+
+    void OnNotificationDisplayed(
+        const message_center::Notification& notification,
+        const NotificationCommon::Metadata* const metadata) override {
+      seen_notification_id = notification.id();
+      if (run_loop.IsRunningOnCurrentThread()) {
+        run_loop.Quit();
+      }
+    }
+
+    void OnNotificationClosed(const std::string& notification_id) override {}
+    void OnNotificationDisplayServiceDestroyed(
+        NotificationDisplayService* service) override {}
+  } notification_watcher;
+
+  auto* notification_display_service =
+      NotificationDisplayService::GetForProfile(profile());
+  notification_display_service->AddObserver(&notification_watcher);
+
+  // Notifications only fire if the device is "online". Simulate that.
+  network_portal_detector_.SimulateDefaultNetworkState(
+      ash::NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE);
+
+  EXPECT_EQ("success",
+            ExtractStringInGlobalScope(web_ui, kMaybeTriggerPdfHats));
+
+  if (notification_watcher.seen_notification_id.empty()) {
+    notification_watcher.run_loop.Run();
+  }
+  notification_display_service->RemoveObserver(&notification_watcher);
+
+  EXPECT_EQ(notification_watcher.seen_notification_id, "hats_notification");
+}
+
 IN_PROC_BROWSER_TEST_P(MediaAppIntegrationTest, GuestCanReadLocalFonts) {
   // For this test, we first need to find a valid font to request from
   // /usr/share/fonts. build/linux/install-chromeos-fonts.py installs some known
diff --git a/chrome/browser/ash/web_applications/projector_system_web_app_info.cc b/chrome/browser/ash/web_applications/projector_system_web_app_info.cc
index 4dd21ac..6334481 100644
--- a/chrome/browser/ash/web_applications/projector_system_web_app_info.cc
+++ b/chrome/browser/ash/web_applications/projector_system_web_app_info.cc
@@ -7,11 +7,17 @@
 #include "ash/constants/ash_features.h"
 #include "ash/webui/grit/ash_projector_app_trusted_resources.h"
 #include "ash/webui/projector_app/public/cpp/projector_app_constants.h"
+#include "chrome/browser/apps/app_service/app_launch_params.h"
+#include "chrome/browser/ash/system_web_apps/types/system_web_app_type.h"
 #include "chrome/browser/ash/web_applications/system_web_app_install_utils.h"
 #include "chrome/browser/ui/ash/projector/projector_utils.h"
+#include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/web_applications/user_display_mode.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
 #include "chrome/grit/generated_resources.h"
+#include "content/public/browser/web_contents.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/chromeos/styles/cros_styles.h"
@@ -86,3 +92,30 @@
 bool ProjectorSystemWebAppDelegate::IsAppEnabled() const {
   return IsProjectorAppEnabled(profile_);
 }
+
+Browser* ProjectorSystemWebAppDelegate::LaunchAndNavigateSystemWebApp(
+    Profile* profile,
+    web_app::WebAppProvider* provider,
+    const GURL& url,
+    const apps::AppLaunchParams& params) const {
+  Browser* browser =
+      ash::FindSystemWebAppBrowser(profile, ash::SystemWebAppType::PROJECTOR);
+  // If the Projector app is not already open or we're not launching with files
+  // then proceed as usual.
+  if (!browser || params.launch_files.empty()) {
+    return SystemWebAppDelegate::LaunchAndNavigateSystemWebApp(
+        profile, provider, url, params);
+  }
+  // Otherwise, the app is already open and we're launching files. Suppose the
+  // user clicks on a share link for a transcoding screencast. The app's URL
+  // would be set to chrome://projector/app/screencastId. However, calling
+  // LaunchSystemWebAppAsync() always launches the app at the default start url
+  // of chrome://projector/app/. The launch event would reload the app back to
+  // the gallery view! To prevent this bug, we must match the app's current url
+  // to avoid a visible app reload. In general, launch events should be
+  // invisible to the user.
+  content::WebContents* web_contents =
+      browser->tab_strip_model()->GetActiveWebContents();
+  return SystemWebAppDelegate::LaunchAndNavigateSystemWebApp(
+      profile, provider, web_contents->GetURL(), params);
+}
diff --git a/chrome/browser/ash/web_applications/projector_system_web_app_info.h b/chrome/browser/ash/web_applications/projector_system_web_app_info.h
index efb6f8c..73b0340 100644
--- a/chrome/browser/ash/web_applications/projector_system_web_app_info.h
+++ b/chrome/browser/ash/web_applications/projector_system_web_app_info.h
@@ -22,6 +22,11 @@
   bool ShouldCaptureNavigations() const override;
   gfx::Size GetMinimumWindowSize() const override;
   bool IsAppEnabled() const override;
+  Browser* LaunchAndNavigateSystemWebApp(
+      Profile* profile,
+      web_app::WebAppProvider* provider,
+      const GURL& url,
+      const apps::AppLaunchParams& params) const override;
 };
 
 #endif  // CHROME_BROWSER_ASH_WEB_APPLICATIONS_PROJECTOR_SYSTEM_WEB_APP_INFO_H_
diff --git a/chrome/browser/ash/web_applications/terminal_source.cc b/chrome/browser/ash/web_applications/terminal_source.cc
index 9f7aa34..2c3ae2f6 100644
--- a/chrome/browser/ash/web_applications/terminal_source.cc
+++ b/chrome/browser/ash/web_applications/terminal_source.cc
@@ -212,6 +212,10 @@
         return "frame-src 'self';";
       case network::mojom::CSPDirectiveName::ObjectSrc:
         return "object-src 'self';";
+      case network::mojom::CSPDirectiveName::ScriptSrc:
+        return "script-src 'self' 'wasm-unsafe-eval';";
+      case network::mojom::CSPDirectiveName::WorkerSrc:
+        return "worker-src 'self';";
       default:
         break;
     }
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 9b1f52f..c43d1b2fb 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -2077,6 +2077,8 @@
     "app_mode/chrome_kiosk_external_loader_broker.h",
     "app_mode/kiosk_app_external_loader.cc",
     "app_mode/kiosk_app_external_loader.h",
+    "app_mode/kiosk_app_service_launcher.cc",
+    "app_mode/kiosk_app_service_launcher.h",
     "app_mode/kiosk_settings_navigation_throttle.cc",
     "app_mode/kiosk_settings_navigation_throttle.h",
     "app_mode/startup_app_launcher_update_checker.cc",
@@ -3471,6 +3473,7 @@
     "../ui/browser_finder_chromeos_unittest.cc",
     "app_mode/app_session_unittest.cc",
     "app_mode/chrome_kiosk_app_launcher_unittest.cc",
+    "app_mode/kiosk_app_service_launcher_unittest.cc",
     "extensions/active_tab_permission_granter_delegate_chromeos_unittest.cc",
     "extensions/default_app_order_unittest.cc",
     "extensions/device_local_account_external_policy_loader_unittest.cc",
diff --git a/chrome/browser/chromeos/app_mode/chrome_kiosk_app_launcher.cc b/chrome/browser/chromeos/app_mode/chrome_kiosk_app_launcher.cc
index 6526eb4..9d4d326 100644
--- a/chrome/browser/chromeos/app_mode/chrome_kiosk_app_launcher.cc
+++ b/chrome/browser/chromeos/app_mode/chrome_kiosk_app_launcher.cc
@@ -4,13 +4,18 @@
 
 #include "chrome/browser/chromeos/app_mode/chrome_kiosk_app_launcher.h"
 
+#include "base/bind.h"
+#include "base/feature_list.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/syslog_logging.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/apps/app_service/app_launch_params.h"
+#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
+#include "chrome/browser/chromeos/app_mode/kiosk_app_service_launcher.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/extensions/application_launch.h"
+#include "chrome/common/chrome_features.h"
 #include "components/services/app_service/public/cpp/app_launch_util.h"
 #include "extensions/browser/app_window/app_window.h"
 #include "extensions/browser/app_window/app_window_registry.h"
@@ -91,12 +96,20 @@
 
   SYSLOG(INFO) << "Attempt to launch app.";
 
-  // Always open the app in a window.
-  ::OpenApplication(
-      profile_,
-      apps::AppLaunchParams(
-          extension->id(), apps::LaunchContainer::kLaunchContainerWindow,
-          WindowOpenDisposition::NEW_WINDOW, apps::LaunchSource::kFromKiosk));
+  if (base::FeatureList::IsEnabled(features::kKioskEnableAppService)) {
+    app_service_launcher_ = std::make_unique<KioskAppServiceLauncher>(profile_);
+    app_service_launcher_->CheckAndMaybeLaunchApp(
+        extension->id(),
+        base::BindOnce(&ChromeKioskAppLauncher::OnAppServiceAppLaunched,
+                       weak_ptr_factory_.GetWeakPtr()));
+  } else {
+    // Always open the app in a window.
+    ::OpenApplication(
+        profile_,
+        apps::AppLaunchParams(
+            extension->id(), apps::LaunchContainer::kLaunchContainerWindow,
+            WindowOpenDisposition::NEW_WINDOW, apps::LaunchSource::kFromKiosk));
+  }
 
   WaitForAppWindow();
 }
@@ -119,6 +132,12 @@
   }
 }
 
+void ChromeKioskAppLauncher::OnAppServiceAppLaunched(bool success) {
+  if (!success) {
+    ReportLaunchFailure(LaunchResult::kUnableToLaunch);
+  }
+}
+
 void ChromeKioskAppLauncher::MaybeUpdateAppData() {
   // Skip copying meta data from the current installed primary app when
   // there is a pending update.
diff --git a/chrome/browser/chromeos/app_mode/chrome_kiosk_app_launcher.h b/chrome/browser/chromeos/app_mode/chrome_kiosk_app_launcher.h
index 776e431..f3624c2 100644
--- a/chrome/browser/chromeos/app_mode/chrome_kiosk_app_launcher.h
+++ b/chrome/browser/chromeos/app_mode/chrome_kiosk_app_launcher.h
@@ -8,6 +8,7 @@
 #include "base/callback.h"
 #include "base/memory/raw_ptr.h"
 #include "base/scoped_observation.h"
+#include "chrome/browser/chromeos/app_mode/kiosk_app_service_launcher.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chromeos/crosapi/mojom/chrome_app_kiosk_service.mojom.h"
 #include "extensions/browser/app_window/app_window.h"
@@ -35,6 +36,9 @@
   // AppWindowRegistry::Observer:
   void OnAppWindowAdded(extensions::AppWindow* app_window) override;
 
+  // |KioskAppServiceLauncher| callback.
+  void OnAppServiceAppLaunched(bool success);
+
   void WaitForAppWindow();
 
   void ReportLaunchSuccess();
@@ -60,7 +64,11 @@
                           extensions::AppWindowRegistry::Observer>
       app_window_observation_{this};
 
+  std::unique_ptr<KioskAppServiceLauncher> app_service_launcher_;
+
   LaunchCallback on_ready_callback_;
+
+  base::WeakPtrFactory<ChromeKioskAppLauncher> weak_ptr_factory_{this};
 };
 
 }  // namespace ash
diff --git a/chrome/browser/chromeos/app_mode/chrome_kiosk_app_launcher_unittest.cc b/chrome/browser/chromeos/app_mode/chrome_kiosk_app_launcher_unittest.cc
index f974819..21d1853 100644
--- a/chrome/browser/chromeos/app_mode/chrome_kiosk_app_launcher_unittest.cc
+++ b/chrome/browser/chromeos/app_mode/chrome_kiosk_app_launcher_unittest.cc
@@ -17,6 +17,7 @@
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/extension_service_test_base.h"
 #include "chrome/browser/ui/apps/chrome_app_delegate.h"
+#include "chrome/common/chrome_features.h"
 #include "chrome/common/chrome_switches.h"
 #include "content/public/test/browser_task_environment.h"
 #include "extensions/browser/app_window/app_window.h"
@@ -272,4 +273,29 @@
   EXPECT_TRUE(registry()->disabled_extensions().Contains(kExtraSecondaryAppId));
 }
 
+TEST_F(ChromeKioskAppLauncherTest, ShouldSucceedWithAppService) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(features::kKioskEnableAppService);
+
+  TestKioskExtensionBuilder primary_app_builder(Manifest::TYPE_PLATFORM_APP,
+                                                kTestPrimaryAppId);
+  primary_app_builder.set_version("1.0");
+  scoped_refptr<const extensions::Extension> primary_app =
+      primary_app_builder.Build();
+  service()->AddExtension(primary_app.get());
+
+  CreateLauncher(/*is_network_ready=*/true);
+
+  TestFuture<LaunchResult> future;
+  launcher_->LaunchApp(future.GetCallback());
+
+  SimulateAppWindowLaunch(primary_app.get());
+
+  ASSERT_THAT(future.Get(), Eq(LaunchResult::kSuccess));
+
+  EXPECT_THAT(app_launch_tracker_->launched_apps(),
+              ElementsAre(kTestPrimaryAppId));
+  EXPECT_TRUE(registry()->enabled_extensions().Contains(kTestPrimaryAppId));
+}
+
 }  // namespace ash
diff --git a/chrome/browser/chromeos/app_mode/kiosk_app_service_launcher.cc b/chrome/browser/chromeos/app_mode/kiosk_app_service_launcher.cc
new file mode 100644
index 0000000..acbfd26
--- /dev/null
+++ b/chrome/browser/chromeos/app_mode/kiosk_app_service_launcher.cc
@@ -0,0 +1,100 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/app_mode/kiosk_app_service_launcher.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/check.h"
+#include "base/notreached.h"
+#include "base/stl_util.h"
+#include "base/syslog_logging.h"
+#include "chrome/browser/apps/app_service/app_launch_params.h"
+#include "chrome/browser/apps/app_service/app_service_proxy.h"
+#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
+#include "chrome/browser/apps/app_service/launch_result_type.h"
+#include "components/services/app_service/public/cpp/app_types.h"
+#include "components/services/app_service/public/cpp/app_update.h"
+
+namespace ash {
+
+KioskAppServiceLauncher::KioskAppServiceLauncher(Profile* profile) {
+  app_service_ = apps::AppServiceProxyFactory::GetForProfile(profile);
+  DCHECK(app_service_);
+}
+
+KioskAppServiceLauncher::~KioskAppServiceLauncher() = default;
+
+void KioskAppServiceLauncher::CheckAndMaybeLaunchApp(
+    const std::string& app_id,
+    AppLaunchedCallback app_launched_callback) {
+  DCHECK(!app_launched_callback_)
+      << "CheckAndMaybeLaunchApp() should only be called once.";
+  app_launched_callback_ = std::move(app_launched_callback);
+
+  app_id_ = app_id;
+
+  apps::Readiness readiness = apps::Readiness::kUnknown;
+  app_service_->AppRegistryCache().ForOneApp(
+      app_id_,
+      [&readiness](apps::AppUpdate update) { readiness = update.Readiness(); });
+
+  switch (readiness) {
+    case apps::Readiness::kUnknown:
+    case apps::Readiness::kTerminated:
+      SYSLOG(WARNING) << "Kiosk app not ready yet: "
+                      << base::to_underlying(readiness);
+      app_registry_observation_.Observe(&app_service_->AppRegistryCache());
+      break;
+    case apps::Readiness::kReady:
+      LaunchAppInternal();
+      break;
+    case apps::Readiness::kDisabledByBlocklist:
+    case apps::Readiness::kDisabledByPolicy:
+    case apps::Readiness::kDisabledByUser:
+    case apps::Readiness::kUninstalledByUser:
+    case apps::Readiness::kRemoved:
+    case apps::Readiness::kUninstalledByMigration:
+      SYSLOG(ERROR) << "Kiosk app should not have readiness "
+                    << base::to_underlying(readiness);
+      if (app_launched_callback_.has_value()) {
+        std::move(app_launched_callback_.value()).Run(false);
+      }
+  }
+}
+
+void KioskAppServiceLauncher::OnAppUpdate(const apps::AppUpdate& update) {
+  if (update.AppId() != app_id_ ||
+      update.Readiness() != apps::Readiness::kReady) {
+    return;
+  }
+  app_registry_observation_.Reset();
+  LaunchAppInternal();
+}
+
+void KioskAppServiceLauncher::OnAppRegistryCacheWillBeDestroyed(
+    apps::AppRegistryCache* cache) {
+  app_registry_observation_.Reset();
+}
+
+void KioskAppServiceLauncher::LaunchAppInternal() {
+  SYSLOG(INFO) << "Kiosk app is ready to launch with App Service";
+  app_service_->LaunchAppWithParams(
+      apps::AppLaunchParams(
+          app_id_, apps::LaunchContainer::kLaunchContainerWindow,
+          WindowOpenDisposition::NEW_POPUP, apps::LaunchSource::kFromKiosk),
+      base::BindOnce(&KioskAppServiceLauncher::OnAppLaunched,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
+void KioskAppServiceLauncher::OnAppLaunched(apps::LaunchResult&& result) {
+  // App window is not active at this moment. We need to close splash screen
+  // after app window is activated which will be handled in subclasses.
+  if (app_launched_callback_.has_value()) {
+    std::move(app_launched_callback_.value()).Run(true);
+  }
+}
+
+}  // namespace ash
diff --git a/chrome/browser/chromeos/app_mode/kiosk_app_service_launcher.h b/chrome/browser/chromeos/app_mode/kiosk_app_service_launcher.h
new file mode 100644
index 0000000..53207f2
--- /dev/null
+++ b/chrome/browser/chromeos/app_mode/kiosk_app_service_launcher.h
@@ -0,0 +1,72 @@
+// 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_CHROMEOS_APP_MODE_KIOSK_APP_SERVICE_LAUNCHER_H_
+#define CHROME_BROWSER_CHROMEOS_APP_MODE_KIOSK_APP_SERVICE_LAUNCHER_H_
+
+#include <string>
+#include "base/callback_forward.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/scoped_observation.h"
+#include "chrome/browser/apps/app_service/app_service_proxy.h"
+#include "chrome/browser/apps/app_service/launch_result_type.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/services/app_service/public/cpp/app_registry_cache.h"
+#include "components/services/app_service/public/cpp/app_update.h"
+
+namespace ash {
+
+// This class launches a Kiosk app with the following steps:
+// 1. Checks if the app is ready to be launched. If not then observes the
+//    registry cache until the app is ready.
+// 2. Starts the app using |AppServiceProxy::LaunchAppWithParams()| interface
+//    and waits for the launch to complete.
+// It does not wait for app window to become active, which should be handled
+// in the caller of this class.
+class KioskAppServiceLauncher : public apps::AppRegistryCache::Observer {
+ public:
+  // Callback when the app is launched by App Service. App window instance is
+  // not active at this point. If called with false then the app launch has
+  // failed. Corresponds to |KioskLaunchController::OnAppLaunched()|.
+  using AppLaunchedCallback = base::OnceCallback<void(bool)>;
+
+  explicit KioskAppServiceLauncher(Profile* profile);
+  KioskAppServiceLauncher(const KioskAppServiceLauncher&) = delete;
+  KioskAppServiceLauncher& operator=(const KioskAppServiceLauncher&) = delete;
+  ~KioskAppServiceLauncher() override;
+
+  // Checks if the Kiosk app is ready to be launched by App Service. If it's
+  // ready then launches the app immediately. Otherwise waits for it to be ready
+  // and launches the app later. Should only be called once per Kiosk launch.
+  void CheckAndMaybeLaunchApp(const std::string& app_id,
+                              AppLaunchedCallback app_launched_callback);
+
+ private:
+  void LaunchAppInternal();
+
+  void OnAppLaunched(apps::LaunchResult&& result);
+
+  // apps::AppRegistryCache::Observer:
+  void OnAppUpdate(const apps::AppUpdate& update) override;
+  void OnAppRegistryCacheWillBeDestroyed(
+      apps::AppRegistryCache* cache) override;
+
+  std::string app_id_;
+
+  // A keyed service. Not owned by this class.
+  raw_ptr<apps::AppServiceProxy> app_service_;
+
+  absl::optional<AppLaunchedCallback> app_launched_callback_;
+
+  base::ScopedObservation<apps::AppRegistryCache,
+                          apps::AppRegistryCache::Observer>
+      app_registry_observation_{this};
+
+  base::WeakPtrFactory<KioskAppServiceLauncher> weak_ptr_factory_{this};
+};
+
+}  // namespace ash
+
+#endif  // CHROME_BROWSER_CHROMEOS_APP_MODE_KIOSK_APP_SERVICE_LAUNCHER_H_
diff --git a/chrome/browser/chromeos/app_mode/kiosk_app_service_launcher_unittest.cc b/chrome/browser/chromeos/app_mode/kiosk_app_service_launcher_unittest.cc
new file mode 100644
index 0000000..7acbf7a
--- /dev/null
+++ b/chrome/browser/chromeos/app_mode/kiosk_app_service_launcher_unittest.cc
@@ -0,0 +1,145 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/app_mode/kiosk_app_service_launcher.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "ash/constants/app_types.h"
+#include "base/test/mock_callback.h"
+#include "chrome/browser/apps/app_service/app_service_proxy.h"
+#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
+#include "chrome/browser/apps/app_service/app_service_test.h"
+#include "chrome/browser/apps/app_service/publishers/app_publisher.h"
+#include "chrome/test/base/browser_with_test_window_test.h"
+#include "components/services/app_service/public/cpp/app_types.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace ash {
+
+namespace {
+
+using ::testing::_;
+
+constexpr apps::AppType kTestAppType = apps::AppType::kChromeApp;
+constexpr char kTestAppId[] = "abcdefghabcdefghabcdefghabcdefgh";
+
+class FakePublisher final : public apps::AppPublisher {
+ public:
+  FakePublisher(apps::AppServiceProxy* proxy, apps::AppType app_type)
+      : AppPublisher(proxy) {
+    RegisterPublisher(app_type);
+  }
+
+  MOCK_METHOD4(Launch,
+               void(const std::string& app_id,
+                    int32_t event_flags,
+                    apps::LaunchSource launch_source,
+                    apps::WindowInfoPtr window_info));
+
+  void LaunchAppWithParams(apps::AppLaunchParams&& params,
+                           apps::LaunchCallback callback) override {
+    if (params.app_id == kTestAppId &&
+        params.launch_source == apps::LaunchSource::kFromKiosk) {
+      std::move(callback).Run(apps::LaunchResult());
+    }
+  }
+
+  MOCK_METHOD6(LoadIcon,
+               void(const std::string& app_id,
+                    const apps::IconKey& icon_key,
+                    apps::IconType icon_type,
+                    int32_t size_hint_in_dip,
+                    bool allow_placeholder_icon,
+                    apps::LoadIconCallback callback));
+};
+
+}  // namespace
+
+class KioskAppServiceLauncherTest : public BrowserWithTestWindowTest {
+ public:
+  KioskAppServiceLauncherTest() = default;
+
+  // testing::Test:
+  void SetUp() override {
+    BrowserWithTestWindowTest::SetUp();
+    app_service_test_.UninstallAllApps(profile());
+    app_service_test_.SetUp(profile());
+    app_service_test_.WaitForAppService();
+    app_service_ = apps::AppServiceProxyFactory::GetForProfile(profile());
+    publisher_ = std::make_unique<FakePublisher>(app_service_, kTestAppType);
+    launcher_ = std::make_unique<KioskAppServiceLauncher>(profile());
+  }
+
+  void TearDown() override {
+    publisher_.reset();
+    launcher_.reset();
+    BrowserWithTestWindowTest::TearDown();
+  }
+
+ protected:
+  void UpdateAppReadiness(apps::Readiness readiness) {
+    std::vector<apps::AppPtr> apps;
+    auto app = std::make_unique<apps::App>(kTestAppType, kTestAppId);
+    app->app_id = kTestAppId;
+    app->app_type = kTestAppType;
+    app->readiness = readiness;
+    apps.push_back(std::move(app));
+    app_service_->AppRegistryCache().OnApps(
+        std::move(apps), kTestAppType, false /* should_notify_initialized */);
+  }
+
+  apps::AppServiceTest app_service_test_;
+  apps::AppServiceProxy* app_service_ = nullptr;
+
+  std::unique_ptr<FakePublisher> publisher_;
+  std::unique_ptr<KioskAppServiceLauncher> launcher_;
+};
+
+TEST_F(KioskAppServiceLauncherTest, ShouldFailIfAppInInvalidReadiness) {
+  base::MockCallback<KioskAppServiceLauncher::AppLaunchedCallback>
+      launched_callback;
+
+  UpdateAppReadiness(apps::Readiness::kUninstalledByUser);
+  EXPECT_CALL(launched_callback, Run(false)).Times(1);
+  launcher_->CheckAndMaybeLaunchApp(kTestAppId, launched_callback.Get());
+}
+
+TEST_F(KioskAppServiceLauncherTest, ShouldWaitIfAppNotExist) {
+  base::MockCallback<KioskAppServiceLauncher::AppLaunchedCallback>
+      launched_callback;
+
+  EXPECT_CALL(launched_callback, Run(_)).Times(0);
+  launcher_->CheckAndMaybeLaunchApp(kTestAppId, launched_callback.Get());
+  testing::Mock::VerifyAndClearExpectations(&launched_callback);
+
+  EXPECT_CALL(launched_callback, Run(true)).Times(1);
+  UpdateAppReadiness(apps::Readiness::kReady);
+}
+
+TEST_F(KioskAppServiceLauncherTest, ShouldWaitIfAppNotReady) {
+  base::MockCallback<KioskAppServiceLauncher::AppLaunchedCallback>
+      launched_callback;
+
+  UpdateAppReadiness(apps::Readiness::kUnknown);
+  EXPECT_CALL(launched_callback, Run(_)).Times(0);
+  launcher_->CheckAndMaybeLaunchApp(kTestAppId, launched_callback.Get());
+  testing::Mock::VerifyAndClearExpectations(&launched_callback);
+
+  EXPECT_CALL(launched_callback, Run(true)).Times(1);
+  UpdateAppReadiness(apps::Readiness::kReady);
+}
+
+TEST_F(KioskAppServiceLauncherTest, ShouldLaunchIfAppReady) {
+  base::MockCallback<KioskAppServiceLauncher::AppLaunchedCallback>
+      launched_callback;
+
+  UpdateAppReadiness(apps::Readiness::kReady);
+  EXPECT_CALL(launched_callback, Run(true)).Times(1);
+  launcher_->CheckAndMaybeLaunchApp(kTestAppId, launched_callback.Get());
+}
+
+}  // namespace ash
diff --git a/chrome/browser/chromeos/extensions/login_screen/login/login_apitest.cc b/chrome/browser/chromeos/extensions/login_screen/login/login_apitest.cc
index a40d276..641a264 100644
--- a/chrome/browser/chromeos/extensions/login_screen/login/login_apitest.cc
+++ b/chrome/browser/chromeos/extensions/login_screen/login/login_apitest.cc
@@ -277,7 +277,8 @@
   RunTest(kLockManagedGuestSessionNotActive);
 }
 
-IN_PROC_BROWSER_TEST_F(LoginApitest, UnlockManagedGuestSession) {
+// Flaky. https://crbug.com/1351338
+IN_PROC_BROWSER_TEST_F(LoginApitest, DISABLED_UnlockManagedGuestSession) {
   SetUpDeviceLocalAccountPolicy();
   LogInWithPassword();
 
diff --git a/chrome/browser/device_reauth/mac/biometric_authenticator_mac.h b/chrome/browser/device_reauth/mac/biometric_authenticator_mac.h
index 94832f4f..e4c5891b 100644
--- a/chrome/browser/device_reauth/mac/biometric_authenticator_mac.h
+++ b/chrome/browser/device_reauth/mac/biometric_authenticator_mac.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_DEVICE_REAUTH_MAC_BIOMETRIC_AUTHENTICATOR_MAC_H_
 #define CHROME_BROWSER_DEVICE_REAUTH_MAC_BIOMETRIC_AUTHENTICATOR_MAC_H_
 
+#include "base/callback.h"
 #include "chrome/browser/device_reauth/chrome_biometric_authenticator_common.h"
 #include "chrome/browser/device_reauth/chrome_biometric_authenticator_factory.h"
 #include "components/device_reauth/biometric_authenticator.h"
@@ -53,9 +54,18 @@
   BiometricAuthenticatorMac();
   ~BiometricAuthenticatorMac() override;
 
+  // Called when the authentication compeletes with the result.
+  void OnAuthenticationCompleted(bool result);
+
+  // Callback to be executed after the authentication completes.
+  AuthenticateCallback callback_;
+
   // TouchId authenticator object that will handle biometric authentication
-  // itself
+  // itself.
   std::unique_ptr<device::fido::mac::TouchIdContext> touch_id_auth_context_;
+
+  // Factory for weak pointers to this class.
+  base::WeakPtrFactory<BiometricAuthenticatorMac> weak_ptr_factory_{this};
 };
 
 #endif  // CHROME_BROWSER_DEVICE_REAUTH_MAC_BIOMETRIC_AUTHENTICATOR_MAC_H_
diff --git a/chrome/browser/device_reauth/mac/biometric_authenticator_mac.mm b/chrome/browser/device_reauth/mac/biometric_authenticator_mac.mm
index 978084d2..0e51f583 100644
--- a/chrome/browser/device_reauth/mac/biometric_authenticator_mac.mm
+++ b/chrome/browser/device_reauth/mac/biometric_authenticator_mac.mm
@@ -5,7 +5,6 @@
 #include "chrome/browser/device_reauth/mac/biometric_authenticator_mac.h"
 
 #include "base/bind.h"
-#include "base/callback.h"
 #include "base/notreached.h"
 #include "components/device_reauth/biometric_authenticator.h"
 #include "device/fido/mac/touch_id_context.h"
@@ -37,18 +36,29 @@
     const std::u16string message,
     AuthenticateCallback callback) {
   if (!NeedsToAuthenticate()) {
+    DCHECK(callback_.is_null());
     std::move(callback).Run(/*success=*/true);
     return;
   }
 
-  // TODO(crbug.com/1350994): Clean the touchIdContext object after
-  // authentication is done.
+  if (callback_) {
+    std::move(callback_).Run(/*success=*/false);
+  }
+
   touch_id_auth_context_ = device::fido::mac::TouchIdContext::Create();
-  base::OnceCallback<bool(bool)> record_authentication_result =
-      base::BindOnce(&BiometricAuthenticatorMac::RecordAuthenticationResult,
-                     base::Unretained(this));
+  callback_ = std::move(callback);
 
   touch_id_auth_context_->PromptTouchId(
       message,
-      std::move(record_authentication_result).Then(std::move(callback)));
+      base::BindOnce(&BiometricAuthenticatorMac::OnAuthenticationCompleted,
+                     weak_ptr_factory_.GetWeakPtr()));
+}
+
+void BiometricAuthenticatorMac::OnAuthenticationCompleted(bool result) {
+  if (callback_.is_null()) {
+    return;
+  }
+
+  std::move(callback_).Run(RecordAuthenticationResult(result));
+  touch_id_auth_context_ = nullptr;
 }
diff --git a/chrome/browser/extensions/BUILD.gn b/chrome/browser/extensions/BUILD.gn
index b91ea09..54254f9 100644
--- a/chrome/browser/extensions/BUILD.gn
+++ b/chrome/browser/extensions/BUILD.gn
@@ -1008,6 +1008,8 @@
       "clipboard_extension_helper_chromeos.h",
       "extension_keeplist_chromeos.cc",
       "extension_keeplist_chromeos.h",
+      "system_display/display_info_provider_chromeos.cc",
+      "system_display/display_info_provider_chromeos.h",
       "system_display/display_info_provider_utils.cc",
       "system_display/display_info_provider_utils.h",
       "system_display/system_display_serialization.cc",
@@ -1124,8 +1126,6 @@
       "extension_assets_manager_chromeos.h",
       "extension_garbage_collector_chromeos.cc",
       "extension_garbage_collector_chromeos.h",
-      "system_display/display_info_provider_chromeos.cc",
-      "system_display/display_info_provider_chromeos.h",
       "updater/chromeos_extension_cache_delegate.cc",
       "updater/chromeos_extension_cache_delegate.h",
       "updater/extension_cache_impl.cc",
@@ -1236,8 +1236,6 @@
       "chrome_kiosk_delegate.cc",
       "preinstalled_apps.cc",
       "preinstalled_apps.h",
-      "system_display/display_info_provider_lacros.cc",
-      "system_display/display_info_provider_lacros.h",
     ]
     deps += [
       "//chromeos/lacros",
diff --git a/chrome/browser/extensions/active_tab_permission_granter.cc b/chrome/browser/extensions/active_tab_permission_granter.cc
index 4cd2e60..6f13be4 100644
--- a/chrome/browser/extensions/active_tab_permission_granter.cc
+++ b/chrome/browser/extensions/active_tab_permission_granter.cc
@@ -23,9 +23,9 @@
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/web_contents.h"
 #include "extensions/browser/extension_util.h"
+#include "extensions/browser/network_permissions_updater.h"
 #include "extensions/browser/process_manager.h"
 #include "extensions/browser/renderer_startup_helper.h"
-#include "extensions/common/cors_util.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_messages.h"
 #include "extensions/common/manifest_handlers/incognito_info.h"
@@ -118,21 +118,17 @@
                              const Extension& extension,
                              base::OnceClosure closure) {
   // To limit how far the new permissions reach, we only apply them to the
-  // ActiveTab's profile for split-mode extensions.  OTOH, spanning-mode
+  // ActiveTab's context for split-mode extensions.  OTOH, spanning-mode
   // extensions need to get the new permissions in all profiles (e.g. if the
   // ActiveTab is in an incognito window, than the [single/only/spanning]
   // background page in the regular profile also needs to get the new
   // permissions).
-  std::vector<content::BrowserContext*> target_contexts;
-  if (IncognitoInfo::IsSplitMode(&extension)) {
-    target_contexts = {browser_context};
-  } else {
-    target_contexts = util::GetAllRelatedProfiles(
-        Profile::FromBrowserContext(browser_context), extension);
-  }
-
-  util::SetCorsOriginAccessListForExtension(target_contexts, extension,
-                                            std::move(closure));
+  NetworkPermissionsUpdater::ContextSet context_set =
+      IncognitoInfo::IsSplitMode(&extension)
+          ? NetworkPermissionsUpdater::ContextSet::kCurrentContextOnly
+          : NetworkPermissionsUpdater::ContextSet::kAllRelatedContexts;
+  NetworkPermissionsUpdater::UpdateExtension(*browser_context, extension,
+                                             context_set, std::move(closure));
 }
 
 }  // namespace
diff --git a/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_browsertest.cc b/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_browsertest.cc
index 003d8a4..a9ad9597 100644
--- a/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_browsertest.cc
+++ b/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_browsertest.cc
@@ -25,6 +25,7 @@
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/url_loader_interceptor.h"
 #include "device/fido/features.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/pref_names.h"
 #include "extensions/common/extension_features.h"
 #include "net/dns/mock_host_resolver.h"
@@ -55,10 +56,16 @@
   CryptotokenBrowserTest()
       : base::test::WithFeatureOverride(
             extensions_features::kU2FSecurityKeyAPI) {
+    // Enable the feature flag to load the cryptoken component extension at
+    // startup.
+    scoped_feature_list_.InitWithFeatures(
+        /*enabled_features=*/{extensions_features::kLoadCryptoTokenExtension},
+        /*disabled_features=*/{
 #if BUILDFLAG(IS_WIN)
-    // Don't dispatch requests to the native Windows API.
-    scoped_feature_list_.InitAndDisableFeature(device::kWebAuthUseNativeWinApi);
+          // Don't dispatch requests to the native Windows API.
+          device::kWebAuthUseNativeWinApi
 #endif
+        });
   }
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
@@ -266,9 +273,7 @@
     return true;
   }
 
-#if BUILDFLAG(IS_WIN)
   base::test::ScopedFeatureList scoped_feature_list_;
-#endif
   std::unique_ptr<content::URLLoaderInterceptor> url_loader_interceptor_;
 };
 
@@ -405,5 +410,29 @@
 
 INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE(CryptotokenBrowserTest);
 
+// Test that `kLoadCryptoTokenExtension` controls loading of the component
+// extension.
+class CryptotokenLoadBrowserTest : public base::test::WithFeatureOverride,
+                                   public InProcessBrowserTest {
+ protected:
+  CryptotokenLoadBrowserTest()
+      : base::test::WithFeatureOverride(
+            extensions_features::kLoadCryptoTokenExtension) {}
+
+  void SetUp() override {
+    ComponentLoader::EnableBackgroundExtensionsForTesting();
+    InProcessBrowserTest::SetUp();
+  }
+};
+
+IN_PROC_BROWSER_TEST_P(CryptotokenLoadBrowserTest, IsLoaded) {
+  EXPECT_EQ(ExtensionRegistry::Get(browser()->profile())
+                    ->GenerateInstalledExtensionsSet()
+                    ->GetByID(kCryptoTokenExtensionId) != nullptr,
+            IsParamFeatureEnabled());
+}
+
+INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE(CryptotokenLoadBrowserTest);
+
 }  // namespace
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/scripting/scripting_apitest.cc b/chrome/browser/extensions/api/scripting/scripting_apitest.cc
index 08d10cc..3fb3235 100644
--- a/chrome/browser/extensions/api/scripting/scripting_apitest.cc
+++ b/chrome/browser/extensions/api/scripting/scripting_apitest.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "build/build_config.h"
 #include "chrome/browser/extensions/api/scripting/scripting_api.h"
 
 #include "base/test/bind.h"
@@ -518,7 +519,13 @@
   content::test::ScopedPrerenderFeatureList scoped_feature_list_;
 };
 
-IN_PROC_BROWSER_TEST_F(ScriptingAPIPrerenderingTest, Basic) {
+// TODO(crbug.com/1351648): disabled on Mac due to flakiness.
+#if BUILDFLAG(IS_MAC)
+#define MAYBE_Basic DISABLED_Basic
+#else  // BUILDFLAG(IS_MAC)
+#define MAYBE_Basic Basic
+#endif  // BUILDFLAG(IS_MAC)
+IN_PROC_BROWSER_TEST_F(ScriptingAPIPrerenderingTest, MAYBE_Basic) {
   ASSERT_TRUE(RunExtensionTest("scripting/prerendering")) << message_;
 }
 
diff --git a/chrome/browser/extensions/component_loader.cc b/chrome/browser/extensions/component_loader.cc
index b8289914..8c52b09 100644
--- a/chrome/browser/extensions/component_loader.cc
+++ b/chrome/browser/extensions/component_loader.cc
@@ -44,6 +44,7 @@
 #include "extensions/browser/extension_system.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/extension.h"
+#include "extensions/common/extension_features.h"
 #include "extensions/common/extension_l10n_util.h"
 #include "extensions/common/file_util.h"
 #include "extensions/common/manifest_constants.h"
@@ -548,8 +549,11 @@
 
 #endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
 
-  Add(IDR_CRYPTOTOKEN_MANIFEST,
-      base::FilePath(FILE_PATH_LITERAL("cryptotoken")));
+  if (base::FeatureList::IsEnabled(
+          extensions_features::kLoadCryptoTokenExtension)) {
+    Add(IDR_CRYPTOTOKEN_MANIFEST,
+        base::FilePath(FILE_PATH_LITERAL("cryptotoken")));
+  }
 }
 
 void ComponentLoader::
diff --git a/chrome/browser/extensions/permissions_updater.cc b/chrome/browser/extensions/permissions_updater.cc
index a0b999e..f0980b8 100644
--- a/chrome/browser/extensions/permissions_updater.cc
+++ b/chrome/browser/extensions/permissions_updater.cc
@@ -180,6 +180,7 @@
   // NotifyPermissionsUpdated if the profile is still valid.
   NetworkPermissionsUpdater::UpdateExtension(
       *browser_context, *extension,
+      NetworkPermissionsUpdater::ContextSet::kAllRelatedContexts,
       base::BindOnce(&NetworkPermissionsUpdateHelper::OnOriginAccessUpdated,
                      helper->weak_factory_.GetWeakPtr()));
 }
diff --git a/chrome/browser/extensions/system_display/display_info_provider_chromeos.cc b/chrome/browser/extensions/system_display/display_info_provider_chromeos.cc
index 57c6727..7278fef 100644
--- a/chrome/browser/extensions/system_display/display_info_provider_chromeos.cc
+++ b/chrome/browser/extensions/system_display/display_info_provider_chromeos.cc
@@ -7,7 +7,12 @@
 #include <stdint.h>
 #include <cmath>
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ash/public/ash_interfaces.h"
+#elif BUILDFLAG(IS_CHROMEOS_LACROS)
+#include "chromeos/lacros/lacros_service.h"
+#endif
+
 #include "base/bind.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/threading/thread_task_runner_handle.h"
@@ -637,6 +642,8 @@
   DispatchOnDisplayChangedEvent();
 }
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+
 std::unique_ptr<DisplayInfoProvider> CreateChromeDisplayInfoProvider() {
   mojo::PendingRemote<crosapi::mojom::CrosDisplayConfigController>
       display_config;
@@ -646,4 +653,25 @@
       std::move(display_config));
 }
 
+#elif BUILDFLAG(IS_CHROMEOS_LACROS)
+
+std::unique_ptr<DisplayInfoProvider> CreateChromeDisplayInfoProvider() {
+  // Assume that LacrosService has already been initialized.
+  auto* lacros_service = chromeos::LacrosService::Get();
+  if (lacros_service &&
+      lacros_service
+          ->IsAvailable<crosapi::mojom::CrosDisplayConfigController>()) {
+    auto& remote =
+        lacros_service
+            ->GetRemote<crosapi::mojom::CrosDisplayConfigController>();
+    return std::make_unique<DisplayInfoProviderChromeOS>(remote.Unbind());
+  }
+
+  LOG(ERROR) << "Cannot create DisplayInfoProvider in Lacros. "
+                "CrosDisplayConfigController interface is not available.";
+  return nullptr;
+}
+
+#endif
+
 }  // namespace extensions
diff --git a/chrome/browser/extensions/system_display/display_info_provider_lacros.cc b/chrome/browser/extensions/system_display/display_info_provider_lacros.cc
deleted file mode 100644
index b97229c..0000000
--- a/chrome/browser/extensions/system_display/display_info_provider_lacros.cc
+++ /dev/null
@@ -1,193 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/extensions/system_display/display_info_provider_lacros.h"
-
-#include <utility>
-
-#include "base/bind.h"
-#include "base/logging.h"
-#include "chrome/browser/extensions/system_display/display_info_provider_utils.h"
-#include "chrome/browser/extensions/system_display/system_display_serialization.h"
-#include "chromeos/crosapi/mojom/cros_display_config.mojom.h"
-#include "chromeos/lacros/lacros_service.h"
-#include "extensions/common/api/system_display.h"
-#include "ui/gfx/geometry/insets.h"
-#include "ui/gfx/geometry/mojom/geometry.mojom.h"
-#include "ui/gfx/geometry/rect.h"
-#include "ui/gfx/geometry/size.h"
-
-namespace extensions {
-
-DisplayInfoProviderLacros::DisplayInfoProviderLacros() {
-  // Relies on the fact that the instance is a singleton managed by
-  // DisplayInfoProvider::Get(), and assumes that instantiation takes place
-  // after LacrosService has been initialized.
-  auto* lacros_service = chromeos::LacrosService::Get();
-  DCHECK(lacros_service);
-  if (lacros_service->IsAvailable<crosapi::mojom::SystemDisplay>() &&
-      lacros_service->GetInterfaceVersion(
-          crosapi::mojom::SystemDisplay::Uuid_) >=
-          static_cast<int>(crosapi::mojom::SystemDisplay::
-                               kAddDisplayChangeObserverMinVersion)) {
-    lacros_service->GetRemote<crosapi::mojom::SystemDisplay>()
-        ->AddDisplayChangeObserver(
-            receiver_.BindNewPipeAndPassRemoteWithVersion());
-  }
-}
-
-DisplayInfoProviderLacros::~DisplayInfoProviderLacros() = default;
-
-void DisplayInfoProviderLacros::GetAllDisplaysInfo(
-    bool single_unified,
-    base::OnceCallback<void(DisplayUnitInfoList)> callback) {
-  auto* lacros_service = chromeos::LacrosService::Get();
-  if (lacros_service->IsAvailable<crosapi::mojom::SystemDisplay>()) {
-    auto cb =
-        base::BindOnce(&DisplayInfoProviderLacros::OnCrosapiResult,
-                       weak_ptr_factory_.GetWeakPtr(), std::move(callback));
-    lacros_service->GetRemote<crosapi::mojom::SystemDisplay>()
-        ->GetDisplayUnitInfoList(single_unified, std::move(cb));
-
-  } else {
-    std::move(callback).Run(DisplayUnitInfoList());
-  }
-}
-
-void DisplayInfoProviderLacros::OnCrosapiResult(
-    base::OnceCallback<void(DisplayUnitInfoList)> callback,
-    std::vector<crosapi::mojom::SysDisplayUnitInfoPtr> src_info_list) {
-  DisplayUnitInfoList dst_info_list(src_info_list.size());
-  for (size_t i = 0; i < src_info_list.size(); ++i) {
-    DCHECK(src_info_list[i]);
-    extensions::api::system_display::DeserializeDisplayUnitInfo(
-        *src_info_list[i], &dst_info_list[i]);
-  }
-  std::move(callback).Run(std::move(dst_info_list));
-}
-
-void DisplayInfoProviderLacros::OnCrosapiDisplayChanged() {
-  DispatchOnDisplayChangedEvent();
-}
-
-void DisplayInfoProviderLacros::GetDisplayLayout(
-    base::OnceCallback<void(DisplayLayoutList)> callback) {
-  auto* lacros_service = chromeos::LacrosService::Get();
-  if (lacros_service
-          ->IsAvailable<crosapi::mojom::CrosDisplayConfigController>()) {
-    auto& remote =
-        lacros_service
-            ->GetRemote<crosapi::mojom::CrosDisplayConfigController>();
-
-    remote->GetDisplayLayoutInfo(
-        base::BindOnce(&OnGetDisplayLayoutResult, std::move(callback)));
-
-  } else {
-    LOG(ERROR) << "Cros display config service is not available.";
-    std::move(callback).Run(DisplayLayoutList());
-  }
-}
-
-void DisplayInfoProviderLacros::SetDisplayProperties(
-    const std::string& display_id,
-    const api::system_display::DisplayProperties& properties,
-    ErrorCallback callback) {
-  constexpr char kTempError[] =
-      "SetDisplayProperties to be implemented in Lacros";
-  NOTIMPLEMENTED() << kTempError;
-  std::move(callback).Run(kTempError);
-}
-
-void DisplayInfoProviderLacros::SetDisplayLayout(
-    const DisplayLayoutList& layouts,
-    ErrorCallback callback) {
-  constexpr char kTempError[] = "SetDisplayLayout to be implemented in Lacros";
-  NOTIMPLEMENTED() << kTempError;
-  std::move(callback).Run(kTempError);
-}
-
-void DisplayInfoProviderLacros::EnableUnifiedDesktop(bool enable) {
-  constexpr char kTempError[] =
-      "EnableUnifiedDesktop to be implemented in Lacros";
-  NOTIMPLEMENTED() << kTempError;
-}
-
-bool DisplayInfoProviderLacros::OverscanCalibrationStart(
-    const std::string& id) {
-  constexpr char kTempError[] =
-      "OverscanCalibrationStart to be implemented in Lacros";
-  NOTIMPLEMENTED() << kTempError;
-  return false;
-}
-bool DisplayInfoProviderLacros::OverscanCalibrationAdjust(
-    const std::string& id,
-    const api::system_display::Insets& delta) {
-  constexpr char kTempError[] =
-      "OverscanCalibrationAdjust to be implemented in Lacros";
-  NOTIMPLEMENTED() << kTempError;
-  return false;
-}
-
-bool DisplayInfoProviderLacros::OverscanCalibrationReset(
-    const std::string& id) {
-  constexpr char kTempError[] =
-      "OverscanCalibrationAdjust to be implemented in Lacros";
-  NOTIMPLEMENTED() << kTempError;
-  return false;
-}
-
-bool DisplayInfoProviderLacros::OverscanCalibrationComplete(
-    const std::string& id) {
-  constexpr char kTempError[] =
-      "OverscanCalibrationComplete to be implemented in Lacros";
-  NOTIMPLEMENTED() << kTempError;
-  return false;
-}
-
-void DisplayInfoProviderLacros::ShowNativeTouchCalibration(
-    const std::string& id,
-    ErrorCallback callback) {
-  constexpr char kTempError[] =
-      "ShowNativeTouchCalibration to be implemented in Lacros";
-  NOTIMPLEMENTED() << kTempError;
-  std::move(callback).Run(kTempError);
-}
-
-bool DisplayInfoProviderLacros::StartCustomTouchCalibration(
-    const std::string& id) {
-  constexpr char kTempError[] =
-      "StartCustomTouchCalibration to be implemented in Lacros";
-  NOTIMPLEMENTED() << kTempError;
-  return false;
-}
-
-bool DisplayInfoProviderLacros::CompleteCustomTouchCalibration(
-    const api::system_display::TouchCalibrationPairQuad& pairs,
-    const api::system_display::Bounds& bounds) {
-  constexpr char kTempError[] =
-      "CompleteCustomTouchCalibration to be implemented in Lacros";
-  NOTIMPLEMENTED() << kTempError;
-  return false;
-}
-
-bool DisplayInfoProviderLacros::ClearTouchCalibration(const std::string& id) {
-  constexpr char kTempError[] =
-      "ClearTouchCalibration to be implemented in Lacros";
-  NOTIMPLEMENTED() << kTempError;
-  return false;
-}
-
-void DisplayInfoProviderLacros::SetMirrorMode(
-    const api::system_display::MirrorModeInfo& info,
-    ErrorCallback callback) {
-  constexpr char kTempError[] = "SetMirrorMode to be implemented in Lacros";
-  NOTIMPLEMENTED() << kTempError;
-  std::move(callback).Run(kTempError);
-}
-
-std::unique_ptr<DisplayInfoProvider> CreateChromeDisplayInfoProvider() {
-  return std::make_unique<DisplayInfoProviderLacros>();
-}
-
-}  // namespace extensions
diff --git a/chrome/browser/extensions/system_display/display_info_provider_lacros.h b/chrome/browser/extensions/system_display/display_info_provider_lacros.h
deleted file mode 100644
index 68d56dd5..0000000
--- a/chrome/browser/extensions/system_display/display_info_provider_lacros.h
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_EXTENSIONS_SYSTEM_DISPLAY_DISPLAY_INFO_PROVIDER_LACROS_H_
-#define CHROME_BROWSER_EXTENSIONS_SYSTEM_DISPLAY_DISPLAY_INFO_PROVIDER_LACROS_H_
-
-#include <vector>
-
-#include "base/callback.h"
-#include "base/memory/weak_ptr.h"
-#include "chromeos/crosapi/mojom/system_display.mojom.h"
-#include "extensions/browser/api/system_display/display_info_provider.h"
-#include "extensions/common/api/system_display.h"
-#include "mojo/public/cpp/bindings/receiver.h"
-
-namespace extensions {
-
-// DisplayInfoProvider used by lacros-chrome that uses crosapi to:
-// * Get DisplayUnitInfoList from ash-chrome, handling potential version skew.
-// * Pass display change events from ash-chrome to lacros-chrome.
-class DisplayInfoProviderLacros : public DisplayInfoProvider,
-                                  public crosapi::mojom::DisplayChangeObserver {
- public:
-  DisplayInfoProviderLacros();
-  ~DisplayInfoProviderLacros() override;
-  DisplayInfoProviderLacros(const DisplayInfoProviderLacros&) = delete;
-  const DisplayInfoProviderLacros& operator=(const DisplayInfoProviderLacros&) =
-      delete;
-
-  // DisplayInfoProvider:
-  void GetAllDisplaysInfo(
-      bool single_unified,
-      base::OnceCallback<void(DisplayUnitInfoList)> callback) override;
-
-  void GetDisplayLayout(
-      base::OnceCallback<void(DisplayLayoutList result)> callback) override;
-
-  void SetDisplayProperties(
-      const std::string& display_id,
-      const api::system_display::DisplayProperties& properties,
-      ErrorCallback callback) override;
-
-  void SetDisplayLayout(const DisplayLayoutList& layouts,
-                        ErrorCallback callback) override;
-
-  void EnableUnifiedDesktop(bool enable) override;
-
-  bool OverscanCalibrationStart(const std::string& id) override;
-  bool OverscanCalibrationAdjust(
-      const std::string& id,
-      const api::system_display::Insets& delta) override;
-  bool OverscanCalibrationReset(const std::string& id) override;
-  bool OverscanCalibrationComplete(const std::string& id) override;
-  void ShowNativeTouchCalibration(const std::string& id,
-                                  ErrorCallback callback) override;
-  bool StartCustomTouchCalibration(const std::string& id) override;
-  bool CompleteCustomTouchCalibration(
-      const api::system_display::TouchCalibrationPairQuad& pairs,
-      const api::system_display::Bounds& bounds) override;
-  bool ClearTouchCalibration(const std::string& id) override;
-  void SetMirrorMode(const api::system_display::MirrorModeInfo& info,
-                     ErrorCallback callback) override;
-
- private:
-  // Receiver for SystemDisplayAsh::GetDisplayUnitInfoList().
-  void OnCrosapiResult(
-      base::OnceCallback<void(DisplayUnitInfoList)> callback,
-      std::vector<crosapi::mojom::SysDisplayUnitInfoPtr> src_info_list);
-
-  // crosapi::mojom::DisplayChangeObserver:
-  void OnCrosapiDisplayChanged() override;
-
-  // Receives mojo messages from ash-chrome.
-  mojo::Receiver<crosapi::mojom::DisplayChangeObserver> receiver_{this};
-
-  base::WeakPtrFactory<DisplayInfoProviderLacros> weak_ptr_factory_{this};
-};
-
-}  // namespace extensions
-
-#endif  // CHROME_BROWSER_EXTENSIONS_SYSTEM_DISPLAY_DISPLAY_INFO_PROVIDER_LACROS_H_
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 7bf0828b..3638c46 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -4138,11 +4138,6 @@
     "expiry_milestone": 108
   },
   {
-    "name": "lazily-create-web-state-on-restoration",
-    "owners": ["olivierrobin", "sdefresne"],
-    "expiry_milestone": 105
-  },
-  {
     "name": "leak-detection-unauthenticated",
     "owners": ["rgod@google.com", "vasilii"],
     "expiry_milestone": 102
diff --git a/chrome/browser/installable/installed_webapp_bridge.cc b/chrome/browser/installable/installed_webapp_bridge.cc
index 61de4c4..1fbccb59 100644
--- a/chrome/browser/installable/installed_webapp_bridge.cc
+++ b/chrome/browser/installable/installed_webapp_bridge.cc
@@ -11,6 +11,7 @@
 #include "base/android/jni_utils.h"
 #include "chrome/android/chrome_jni_headers/InstalledWebappBridge_jni.h"
 #include "url/gurl.h"
+#include "url/origin.h"
 
 using base::android::ConvertJavaStringToUTF8;
 using base::android::ScopedJavaLocalRef;
@@ -81,3 +82,18 @@
       env, static_cast<int>(type), j_origin_url, j_last_committed_url,
       reinterpret_cast<jlong>(callback_ptr));
 }
+
+ContentSetting InstalledWebappBridge::GetPermission(ContentSettingsType type,
+                                                    const GURL& url) {
+  JNIEnv* env = base::android::AttachCurrentThread();
+
+  ScopedJavaLocalRef<jstring> java_origin =
+      base::android::ConvertUTF8ToJavaString(
+          env, url::Origin::Create(url).Serialize());
+
+  ContentSetting setting =
+      IntToContentSetting(Java_InstalledWebappBridge_getPermission(
+          env, static_cast<int>(type), java_origin));
+
+  return setting;
+}
diff --git a/chrome/browser/installable/installed_webapp_bridge.h b/chrome/browser/installable/installed_webapp_bridge.h
index 52c7438..9fa2895 100644
--- a/chrome/browser/installable/installed_webapp_bridge.h
+++ b/chrome/browser/installable/installed_webapp_bridge.h
@@ -30,6 +30,9 @@
                                const GURL& origin_url,
                                const GURL& last_committed_url,
                                PermissionCallback callback);
+
+  static ContentSetting GetPermission(ContentSettingsType type,
+                                      const GURL& origin);
 };
 
 #endif  // CHROME_BROWSER_INSTALLABLE_INSTALLED_WEBAPP_BRIDGE_H_
diff --git a/chrome/browser/lacros/DEPS b/chrome/browser/lacros/DEPS
index d376652..7a711be8 100644
--- a/chrome/browser/lacros/DEPS
+++ b/chrome/browser/lacros/DEPS
@@ -1,5 +1,6 @@
 include_rules = [
   "+chrome/browser/ui/views",
+  "+chromeos/ui/wm",
   "+components/account_manager_core",
   "+components/arc/lacros",
   "+components/memory_pressure",
diff --git a/chrome/browser/lacros/cert/cert_db_initializer_browsertest.cc b/chrome/browser/lacros/cert/cert_db_initializer_browsertest.cc
index 9c8e7a3..61784d4 100644
--- a/chrome/browser/lacros/cert/cert_db_initializer_browsertest.cc
+++ b/chrome/browser/lacros/cert/cert_db_initializer_browsertest.cc
@@ -181,9 +181,10 @@
 [[nodiscard]] std::vector<uint8_t> GenerateClientCertForPublicKey(
     const std::vector<uint8_t>& public_key_spki) {
   net::CertBuilder issuer(/*orig_cert=*/nullptr, /*issuer=*/nullptr);
+  issuer.GenerateRSAKey();
   auto cert_builder =
       net::CertBuilder::FromSubjectPublicKeyInfo(public_key_spki, &issuer);
-  cert_builder->SetSignatureAlgorithmRsaPkca1(net::DigestAlgorithm::Sha256);
+  cert_builder->SetSignatureAlgorithm(net::SignatureAlgorithm::kRsaPkcs1Sha256);
   cert_builder->SetValidity(base::Time::Now(),
                             base::Time::Now() + base::Days(30));
 
diff --git a/chrome/browser/lacros/chrome_browser_main_extra_parts_lacros.cc b/chrome/browser/lacros/chrome_browser_main_extra_parts_lacros.cc
index c6a4d18..283f1061 100644
--- a/chrome/browser/lacros/chrome_browser_main_extra_parts_lacros.cc
+++ b/chrome/browser/lacros/chrome_browser_main_extra_parts_lacros.cc
@@ -18,6 +18,7 @@
 #include "chrome/browser/lacros/drivefs_cache.h"
 #include "chrome/browser/lacros/field_trial_observer.h"
 #include "chrome/browser/lacros/force_installed_tracker_lacros.h"
+#include "chrome/browser/lacros/fullscreen_controller_client_lacros.h"
 #include "chrome/browser/lacros/lacros_butter_bar.h"
 #include "chrome/browser/lacros/lacros_extension_apps_controller.h"
 #include "chrome/browser/lacros/lacros_extension_apps_publisher.h"
@@ -85,6 +86,8 @@
   download_controller_client_ =
       std::make_unique<DownloadControllerClientLacros>();
   file_system_provider_ = std::make_unique<LacrosFileSystemProvider>();
+  fullscreen_controller_client_ =
+      std::make_unique<FullscreenControllerClientLacros>();
   kiosk_session_service_ = std::make_unique<KioskSessionServiceLacros>();
   network_change_manager_bridge_ =
       std::make_unique<NetworkChangeManagerBridge>();
diff --git a/chrome/browser/lacros/chrome_browser_main_extra_parts_lacros.h b/chrome/browser/lacros/chrome_browser_main_extra_parts_lacros.h
index b1ba8cdb..0702f10 100644
--- a/chrome/browser/lacros/chrome_browser_main_extra_parts_lacros.h
+++ b/chrome/browser/lacros/chrome_browser_main_extra_parts_lacros.h
@@ -19,6 +19,7 @@
 class DriveFsCache;
 class DownloadControllerClientLacros;
 class ForceInstalledTrackerLacros;
+class FullscreenControllerClientLacros;
 class LacrosButterBar;
 class LacrosExtensionAppsController;
 class LacrosExtensionAppsPublisher;
@@ -75,6 +76,10 @@
   // Handles requests for desk template data from ash-chrome.
   std::unique_ptr<DeskTemplateClientLacros> desk_template_client_;
 
+  // Handles queries regarding full screen control from ash-chrome.
+  std::unique_ptr<FullscreenControllerClientLacros>
+      fullscreen_controller_client_;
+
   // Handles search queries from ash-chrome.
   std::unique_ptr<crosapi::SearchControllerLacros> search_controller_;
 
diff --git a/chrome/browser/lacros/fullscreen_controller_client_lacros.cc b/chrome/browser/lacros/fullscreen_controller_client_lacros.cc
new file mode 100644
index 0000000..33f526f
--- /dev/null
+++ b/chrome/browser/lacros/fullscreen_controller_client_lacros.cc
@@ -0,0 +1,84 @@
+// 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/lacros/fullscreen_controller_client_lacros.h"
+
+#include "base/callback.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chromeos/lacros/lacros_service.h"
+#include "chromeos/ui/wm/fullscreen/keep_fullscreen_for_url_checker.h"
+#include "content/public/browser/web_contents.h"
+#include "extensions/browser/app_window/app_window.h"
+#include "extensions/browser/app_window/app_window_registry.h"
+#include "extensions/browser/app_window/native_app_window.h"
+#include "url/gurl.h"
+
+FullscreenControllerClientLacros::FullscreenControllerClientLacros() {
+  auto* const lacros_service = chromeos::LacrosService::Get();
+  if (lacros_service->IsAvailable<crosapi::mojom::FullscreenController>()) {
+    lacros_service->GetRemote<crosapi::mojom::FullscreenController>()
+        ->AddClient(receiver_.BindNewPipeAndPassRemote());
+  }
+}
+
+FullscreenControllerClientLacros::~FullscreenControllerClientLacros() = default;
+
+void FullscreenControllerClientLacros::ShouldExitFullscreenBeforeLock(
+    base::OnceCallback<void(bool)> callback) {
+  if (!keep_fullscreen_checker_) {
+    keep_fullscreen_checker_ =
+        std::make_unique<chromeos::KeepFullscreenForUrlChecker>(
+            ProfileManager::GetPrimaryUserProfile()->GetPrefs());
+  }
+
+  if (!keep_fullscreen_checker_
+           ->IsKeepFullscreenWithoutNotificationPolicySet()) {
+    std::move(callback).Run(/*should_exit_fullscreen=*/true);
+    return;
+  }
+
+  // Get the web content if the active window is a browser window.
+  content::WebContents* web_contents = nullptr;
+  Browser* browser = chrome::FindBrowserWithActiveWindow();
+  if (browser) {
+    web_contents = browser->tab_strip_model()->GetActiveWebContents();
+  }
+
+  // Get the web content if the active window is an app window.
+  if (!web_contents) {
+    web_contents = GetActiveAppWindowWebContents();
+  }
+
+  if (!web_contents) {
+    std::move(callback).Run(/*should_exit_fullscreen=*/true);
+    return;
+  }
+
+  // Check if it is allowed by user pref to keep full screen for the window URL.
+  GURL url = web_contents->GetLastCommittedURL();
+  std::move(callback).Run(
+      keep_fullscreen_checker_->ShouldExitFullscreenForUrl(url));
+}
+
+content::WebContents*
+FullscreenControllerClientLacros::GetActiveAppWindowWebContents() {
+  Profile* profile = ProfileManager::GetLastUsedProfile();
+  if (!profile) {
+    return nullptr;
+  }
+
+  const auto& app_windows =
+      extensions::AppWindowRegistry::Get(profile)->app_windows();
+  for (auto* app_window : app_windows) {
+    if (app_window->GetBaseWindow()->IsActive()) {
+      return app_window->web_contents();
+    }
+  }
+
+  return nullptr;
+}
diff --git a/chrome/browser/lacros/fullscreen_controller_client_lacros.h b/chrome/browser/lacros/fullscreen_controller_client_lacros.h
new file mode 100644
index 0000000..7479eef2
--- /dev/null
+++ b/chrome/browser/lacros/fullscreen_controller_client_lacros.h
@@ -0,0 +1,50 @@
+// 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_LACROS_FULLSCREEN_CONTROLLER_CLIENT_LACROS_H_
+#define CHROME_BROWSER_LACROS_FULLSCREEN_CONTROLLER_CLIENT_LACROS_H_
+
+#include "base/memory/weak_ptr.h"
+#include "chromeos/crosapi/mojom/fullscreen_controller.mojom.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+
+namespace chromeos {
+class KeepFullscreenForUrlChecker;
+}  // namespace chromeos
+
+namespace content {
+class WebContents;
+}  // namespace content
+
+// The lacros-chrome implementation of the full screen controller client crosapi
+// interface. Receives and processes requests from ash-chrome.
+class FullscreenControllerClientLacros
+    : public crosapi::mojom::FullscreenControllerClient {
+ public:
+  FullscreenControllerClientLacros();
+  FullscreenControllerClientLacros(const FullscreenControllerClientLacros&) =
+      delete;
+  FullscreenControllerClientLacros& operator=(
+      const FullscreenControllerClientLacros&) = delete;
+  ~FullscreenControllerClientLacros() override;
+
+ private:
+  // crosapi::mojom::FullscreenControllerClient:
+  void ShouldExitFullscreenBeforeLock(
+      base::OnceCallback<void(bool)> callback) override;
+
+  content::WebContents* GetActiveAppWindowWebContents();
+
+  std::unique_ptr<chromeos::KeepFullscreenForUrlChecker>
+      keep_fullscreen_checker_;
+
+  // Endpoint to communicate with Ash.
+  mojo::Receiver<crosapi::mojom::FullscreenControllerClient> receiver_{this};
+
+  base::WeakPtrFactory<FullscreenControllerClientLacros> weak_ptr_factory_{
+      this};
+};
+
+#endif  // CHROME_BROWSER_LACROS_FULLSCREEN_CONTROLLER_CLIENT_LACROS_H_
diff --git a/chrome/browser/lacros/fullscreen_controller_client_lacros_unittest.cc b/chrome/browser/lacros/fullscreen_controller_client_lacros_unittest.cc
new file mode 100644
index 0000000..b0595a6
--- /dev/null
+++ b/chrome/browser/lacros/fullscreen_controller_client_lacros_unittest.cc
@@ -0,0 +1,264 @@
+// 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/lacros/fullscreen_controller_client_lacros.h"
+
+#include "base/callback.h"
+#include "base/test/test_future.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/ui/apps/chrome_app_delegate.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/views/apps/chrome_native_app_window_views_aura.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/browser_with_test_window_test.h"
+#include "chrome/test/base/test_browser_window.h"
+#include "chromeos/crosapi/mojom/fullscreen_controller.mojom.h"
+#include "chromeos/ui/wm/fullscreen/pref_names.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/test/navigation_simulator.h"
+#include "content/public/test/web_contents_tester.h"
+#include "extensions/browser/app_window/app_window.h"
+#include "extensions/browser/app_window/app_window_registry.h"
+#include "extensions/browser/app_window/test_app_window_contents.h"
+#include "extensions/common/extension_builder.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/aura/window.h"
+#include "ui/views/controls/webview/webview.h"
+#include "url/gurl.h"
+
+namespace chromeos {
+
+using crosapi::mojom::FullscreenController;
+using crosapi::mojom::FullscreenControllerClient;
+
+namespace {
+
+constexpr char kActiveUrl[] = "https://wwww.test.com";
+
+constexpr char kNonMatchingPattern[] = "google.com";
+constexpr char kMatchingPattern[] = "test.com";
+constexpr char kWildcardPattern[] = "*";
+
+enum class TestWebContentsChoice {
+  kAppWindow,
+  kBrowserWindow,
+};
+
+std::string ParamToString(
+    const testing::TestParamInfo<TestWebContentsChoice>& info) {
+  switch (info.param) {
+    case TestWebContentsChoice::kAppWindow:
+      return "AppWindow";
+    case TestWebContentsChoice::kBrowserWindow:
+      return "BrowserWindow";
+  }
+}
+
+class MockRemote : public FullscreenController {
+ public:
+  // FullscreenController:
+  void AddClient(
+      mojo::PendingRemote<FullscreenControllerClient> client) override {
+    remote_.Bind(std::move(client));
+  }
+
+  void ShouldExitFullscreenBeforeLock(base::OnceCallback<void(bool)> callback) {
+    remote_->ShouldExitFullscreenBeforeLock(
+        base::BindOnce(&MockRemote::OnShouldExitFullscreenBeforeLock,
+                       base::Unretained(this), std::move(callback)));
+  }
+
+  void OnShouldExitFullscreenBeforeLock(base::OnceCallback<void(bool)> callback,
+                                        bool should_exit_fullscreen) {
+    std::move(callback).Run(should_exit_fullscreen);
+  }
+
+ private:
+  mojo::Receiver<FullscreenController> receiver_{this};
+  mojo::Remote<FullscreenControllerClient> remote_;
+};
+
+class TestNativeAppWindow : public ChromeNativeAppWindowViewsAura {
+ public:
+  TestNativeAppWindow() {
+    set_web_view_for_testing(
+        AddChildView(std::make_unique<views::WebView>(nullptr)));
+  }
+  ~TestNativeAppWindow() override {}
+
+  bool IsActive() const override { return true; }
+};
+
+}  // namespace
+
+class FullscreenControllerClientLacrosTest : public BrowserWithTestWindowTest {
+ public:
+  void SetUp() override {
+    BrowserWithTestWindowTest::SetUp();
+
+    profile_ = ProfileManager::GetPrimaryUserProfile();
+
+    // Set the active profile.
+    PrefService* local_state = g_browser_process->local_state();
+    local_state->SetString(::prefs::kProfileLastUsed,
+                           profile_->GetBaseName().value());
+  }
+
+  void TearDown() override { BrowserWithTestWindowTest::TearDown(); }
+
+  void SetKeepFullscreenWithoutNotificationAllowList(
+      const std::string& pattern) {
+    base::Value list(base::Value::Type::LIST);
+    list.Append(base::Value(pattern));
+    profile_->GetPrefs()->Set(
+        prefs::kKeepFullscreenWithoutNotificationUrlAllowList, list);
+  }
+
+  void RunTest(bool expect_should_exit_fullscreen) {
+    base::test::TestFuture<bool> future;
+
+    FullscreenControllerClientLacros client;
+    mojo::Receiver<FullscreenControllerClient> receiver{&client};
+    mock_.AddClient(receiver.BindNewPipeAndPassRemote());
+    mock_.ShouldExitFullscreenBeforeLock(future.GetCallback());
+
+    bool should_exit_fullscreen = future.Take();
+    ASSERT_EQ(should_exit_fullscreen, expect_should_exit_fullscreen);
+  }
+
+ protected:
+  Profile* profile_ = nullptr;
+  testing::StrictMock<MockRemote> mock_;
+};
+
+// Test that ShouldExitFullscreenBeforeLock() returns true if the WebContent is
+// not found and the allow list is unset.
+TEST_F(FullscreenControllerClientLacrosTest,
+       ExitFullscreenIfWebContentsUnavailableUnsetPref) {
+  RunTest(/*expect_should_exit_fullscreen=*/true);
+}
+
+// Test that ShouldExitFullscreenBeforeLock() returns true if the WebContent is
+// not found and the allow list includes the wildcard character.
+TEST_F(FullscreenControllerClientLacrosTest,
+       ExitFullscreenIfWebContentsUnavailableWildcardPref) {
+  SetKeepFullscreenWithoutNotificationAllowList(kWildcardPattern);
+
+  RunTest(/*expect_should_exit_fullscreen=*/true);
+}
+
+class FullscreenControllerClientLacrosWebContentsTest
+    : public FullscreenControllerClientLacrosTest,
+      public testing::WithParamInterface<TestWebContentsChoice> {
+ public:
+  void SetUp() override {
+    FullscreenControllerClientLacrosTest::SetUp();
+
+    switch (GetParam()) {
+      case TestWebContentsChoice::kAppWindow:
+        AddAppWindow();
+        break;
+      case TestWebContentsChoice::kBrowserWindow:
+        AddBrowserWindow();
+        break;
+    }
+  }
+
+  void TearDown() override {
+    if (app_window_) {
+      app_window_->OnNativeClose();
+      app_window_ = nullptr;
+    }
+
+    FullscreenControllerClientLacrosTest::TearDown();
+  }
+
+  void AddAppWindow() {
+    // Create a new AppWindow
+    scoped_refptr<const extensions::Extension> extension =
+        extensions::ExtensionBuilder("test extension").Build();
+    app_window_ = new extensions::AppWindow(
+        profile_, std::make_unique<ChromeAppDelegate>(profile_, true),
+        extension.get());
+
+    // Set the active WebContents
+    std::unique_ptr<content::WebContents> contents(content::WebContents::Create(
+        content::WebContents::CreateParams(app_window_->browser_context())));
+    content::NavigationSimulator::NavigateAndCommitFromBrowser(
+        contents.get(), GURL(kActiveUrl));
+    app_window_->SetAppWindowContentsForTesting(
+        std::make_unique<extensions::TestAppWindowContents>(
+            std::move(contents)));
+
+    // Set the native app window
+    app_window_->SetNativeAppWindowForTesting(
+        std::make_unique<TestNativeAppWindow>());
+
+    extensions::AppWindowRegistry::Get(profile_)->AddAppWindow(app_window_);
+  }
+
+  void AddBrowserWindow() {
+    AddTab(browser(), GURL(kActiveUrl));
+    static_cast<TestBrowserWindow*>(browser()->window())->set_is_active(true);
+    ASSERT_TRUE(chrome::FindBrowserWithActiveWindow());
+  }
+
+ protected:
+  extensions::AppWindow* app_window_ = nullptr;
+};
+
+// Test that ShouldExitFullscreenBeforeLock() returns true if the allow list
+// pref is unset.
+TEST_P(FullscreenControllerClientLacrosWebContentsTest,
+       ExitFullscreenIfUnsetPref) {
+  RunTest(/*expect_should_exit_fullscreen=*/true);
+}
+
+// Test that ShouldExitFullscreenBeforeLock() returns true if the URL of the
+// active window does not match any patterns from the allow list.
+TEST_P(FullscreenControllerClientLacrosWebContentsTest,
+       ExitFullscreenIfNonMatchingPref) {
+  SetKeepFullscreenWithoutNotificationAllowList(kNonMatchingPattern);
+
+  RunTest(/*expect_should_exit_fullscreen=*/true);
+}
+
+// Test that ShouldExitFullscreenBeforeLock() returns false if the URL of the
+// active window matches a pattern from the allow list.
+TEST_P(FullscreenControllerClientLacrosWebContentsTest,
+       KeepFullscreenIfMatchingPref) {
+  // Set up the URL exempt list with one matching and one non-matching pattern.
+  base::Value list(base::Value::Type::LIST);
+  list.Append(base::Value(kNonMatchingPattern));
+  list.Append(base::Value(kMatchingPattern));
+  profile_->GetPrefs()->Set(
+      prefs::kKeepFullscreenWithoutNotificationUrlAllowList, list);
+
+  RunTest(/*expect_should_exit_fullscreen=*/false);
+}
+
+// Test that ShouldExitFullscreenBeforeLock() returns false if the allow list
+// includes the wildcard character.
+TEST_P(FullscreenControllerClientLacrosWebContentsTest,
+       KeepFullscreenIfWildcardPref) {
+  SetKeepFullscreenWithoutNotificationAllowList(kWildcardPattern);
+
+  RunTest(/*expect_should_exit_fullscreen=*/false);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    FullscreenControllerClientLacrosWebContentsTest,
+    ::testing::Values(TestWebContentsChoice::kAppWindow,
+                      TestWebContentsChoice::kBrowserWindow),
+    &ParamToString);
+
+}  // namespace chromeos
diff --git a/chrome/browser/lacros/keystore_service_lacros_browsertest.cc b/chrome/browser/lacros/keystore_service_lacros_browsertest.cc
index 4028d2b..4dcf327 100644
--- a/chrome/browser/lacros/keystore_service_lacros_browsertest.cc
+++ b/chrome/browser/lacros/keystore_service_lacros_browsertest.cc
@@ -112,9 +112,10 @@
     const std::vector<uint8_t>& public_key_spki) {
   auto issuer = std::make_unique<net::CertBuilder>(/*orig_cert=*/nullptr,
                                                    /*issuer=*/nullptr);
+  issuer->GenerateRSAKey();
   auto cert_builder =
       net::CertBuilder::FromSubjectPublicKeyInfo(public_key_spki, issuer.get());
-  cert_builder->SetSignatureAlgorithmRsaPkca1(net::DigestAlgorithm::Sha256);
+  cert_builder->SetSignatureAlgorithm(net::SignatureAlgorithm::kRsaPkcs1Sha256);
   cert_builder->SetValidity(base::Time::Now(),
                             base::Time::Now() + base::Days(30));
   return cert_builder->GetX509Certificate();
diff --git a/chrome/browser/lacros/standalone_browser_test_controller.cc b/chrome/browser/lacros/standalone_browser_test_controller.cc
index 8830b64..91c06202 100644
--- a/chrome/browser/lacros/standalone_browser_test_controller.cc
+++ b/chrome/browser/lacros/standalone_browser_test_controller.cc
@@ -54,6 +54,7 @@
     mojo::Remote<crosapi::mojom::TestController>& test_controller) {
   test_controller->RegisterStandaloneBrowserTestController(
       controller_receiver_.BindNewPipeAndPassRemoteWithVersion());
+  test_controller.FlushAsync();
 }
 
 StandaloneBrowserTestController::~StandaloneBrowserTestController() = default;
diff --git a/chrome/browser/media/chromeos_login_media_access_handler.cc b/chrome/browser/media/chromeos_login_and_lock_media_access_handler.cc
similarity index 64%
rename from chrome/browser/media/chromeos_login_media_access_handler.cc
rename to chrome/browser/media/chromeos_login_and_lock_media_access_handler.cc
index 2843e4c..ddab8af 100644
--- a/chrome/browser/media/chromeos_login_media_access_handler.cc
+++ b/chrome/browser/media/chromeos_login_and_lock_media_access_handler.cc
@@ -2,36 +2,53 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/media/chromeos_login_media_access_handler.h"
+#include "chrome/browser/media/chromeos_login_and_lock_media_access_handler.h"
 
 #include <string>
 
 #include "ash/components/settings/cros_settings_names.h"
 #include "base/logging.h"
 #include "base/values.h"
+#include "chrome/browser/ash/login/saml/in_session_password_sync_manager.h"
+#include "chrome/browser/ash/login/saml/in_session_password_sync_manager_factory.h"
 #include "chrome/browser/ash/login/ui/login_display_host.h"
-#include "chrome/browser/ash/login/ui/webui_login_view.h"
 #include "chrome/browser/ash/settings/cros_settings.h"
-#include "chrome/common/url_constants.h"
+#include "chrome/browser/profiles/profile_manager.h"
 #include "components/content_settings/core/common/content_settings_pattern.h"
 #include "content/public/browser/render_frame_host.h"
 #include "url/gurl.h"
 
-ChromeOSLoginMediaAccessHandler::ChromeOSLoginMediaAccessHandler() {}
+ChromeOSLoginAndLockMediaAccessHandler::
+    ChromeOSLoginAndLockMediaAccessHandler() = default;
 
-ChromeOSLoginMediaAccessHandler::~ChromeOSLoginMediaAccessHandler() {}
+ChromeOSLoginAndLockMediaAccessHandler::
+    ~ChromeOSLoginAndLockMediaAccessHandler() = default;
 
-bool ChromeOSLoginMediaAccessHandler::SupportsStreamType(
+bool ChromeOSLoginAndLockMediaAccessHandler::SupportsStreamType(
     content::WebContents* web_contents,
     const blink::mojom::MediaStreamType type,
     const extensions::Extension* extension) {
   if (!web_contents)
     return false;
+  // Check if the `web_contents` corresponds to the login screen.
   auto* host = ash::LoginDisplayHost::default_host();
-  return host && web_contents == host->GetOobeWebContents();
+  if (host && web_contents == host->GetOobeWebContents()) {
+    return true;
+  }
+  // Check if the `web_contents` corresponds to the reauthentication dialog that
+  // is shown on the lock screen. This is the case when there is an active user
+  // profile and InSessionPasswordSyncManager for this profile is showing reauth
+  // dialog with the same `web_contents`.
+  Profile* profile = ProfileManager::GetActiveUserProfile();
+  if (!profile)
+    return false;
+  auto* password_sync_manager =
+      ash::InSessionPasswordSyncManagerFactory::GetForProfile(profile);
+  return !!password_sync_manager &&
+         web_contents == password_sync_manager->GetDialogWebContents();
 }
 
-bool ChromeOSLoginMediaAccessHandler::CheckMediaAccessPermission(
+bool ChromeOSLoginAndLockMediaAccessHandler::CheckMediaAccessPermission(
     content::RenderFrameHost* render_frame_host,
     const GURL& security_origin,
     blink::mojom::MediaStreamType type,
@@ -39,11 +56,6 @@
   if (type != blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE)
     return false;
 
-  // When creating new user (including supervised user), we must be able to use
-  // the camera to capture a user image.
-  if (security_origin.spec() == chrome::kChromeUIOobeURL)
-    return true;
-
   const ash::CrosSettings* const settings = ash::CrosSettings::Get();
   if (!settings)
     return false;
@@ -55,7 +67,7 @@
     return false;
 
   DCHECK(list_value->is_list());
-  for (const auto& base_value : list_value->GetListDeprecated()) {
+  for (const auto& base_value : list_value->GetList()) {
     const std::string* value = base_value.GetIfString();
     if (value) {
       const ContentSettingsPattern pattern =
@@ -73,7 +85,7 @@
   return false;
 }
 
-void ChromeOSLoginMediaAccessHandler::HandleRequest(
+void ChromeOSLoginAndLockMediaAccessHandler::HandleRequest(
     content::WebContents* web_contents,
     const content::MediaStreamRequest& request,
     content::MediaResponseCallback callback,
diff --git a/chrome/browser/media/chromeos_login_media_access_handler.h b/chrome/browser/media/chromeos_login_and_lock_media_access_handler.h
similarity index 65%
rename from chrome/browser/media/chromeos_login_media_access_handler.h
rename to chrome/browser/media/chromeos_login_and_lock_media_access_handler.h
index 5c1dc19..1bf9392 100644
--- a/chrome/browser/media/chromeos_login_media_access_handler.h
+++ b/chrome/browser/media/chromeos_login_and_lock_media_access_handler.h
@@ -2,17 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_MEDIA_CHROMEOS_LOGIN_MEDIA_ACCESS_HANDLER_H_
-#define CHROME_BROWSER_MEDIA_CHROMEOS_LOGIN_MEDIA_ACCESS_HANDLER_H_
+#ifndef CHROME_BROWSER_MEDIA_CHROMEOS_LOGIN_AND_LOCK_MEDIA_ACCESS_HANDLER_H_
+#define CHROME_BROWSER_MEDIA_CHROMEOS_LOGIN_AND_LOCK_MEDIA_ACCESS_HANDLER_H_
 
 #include "chrome/browser/media/media_access_handler.h"
 
-// MediaAccessHandler for media requests coming from SAML login pages on
-// ChromeOS.
-class ChromeOSLoginMediaAccessHandler : public MediaAccessHandler {
+// MediaAccessHandler for media requests coming from SAML IdP pages on the
+// login/lock screen on ChromeOS.
+class ChromeOSLoginAndLockMediaAccessHandler : public MediaAccessHandler {
  public:
-  ChromeOSLoginMediaAccessHandler();
-  ~ChromeOSLoginMediaAccessHandler() override;
+  ChromeOSLoginAndLockMediaAccessHandler();
+  ~ChromeOSLoginAndLockMediaAccessHandler() override;
 
   // MediaAccessHandler implementation.
   bool SupportsStreamType(content::WebContents* web_contents,
@@ -29,4 +29,4 @@
                      const extensions::Extension* extension) override;
 };
 
-#endif  // CHROME_BROWSER_MEDIA_CHROMEOS_LOGIN_MEDIA_ACCESS_HANDLER_H_
+#endif  // CHROME_BROWSER_MEDIA_CHROMEOS_LOGIN_AND_LOCK_MEDIA_ACCESS_HANDLER_H_
diff --git a/chrome/browser/media/public_session_tab_capture_access_handler.cc b/chrome/browser/media/public_session_tab_capture_access_handler.cc
deleted file mode 100644
index 0318e28..0000000
--- a/chrome/browser/media/public_session_tab_capture_access_handler.cc
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright 2016 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/media/public_session_tab_capture_access_handler.h"
-
-#include <utility>
-
-#include "base/bind.h"
-#include "base/callback_helpers.h"
-#include "chrome/browser/chromeos/extensions/public_session_permission_helper.h"
-#include "chrome/browser/profiles/profiles_state.h"
-#include "chromeos/login/login_state/login_state.h"
-#include "content/public/browser/web_contents.h"
-#include "extensions/common/extension.h"
-#include "extensions/common/permissions/manifest_permission_set.h"
-#include "extensions/common/permissions/permission_set.h"
-#include "extensions/common/url_pattern_set.h"
-
-PublicSessionTabCaptureAccessHandler::PublicSessionTabCaptureAccessHandler() {}
-
-PublicSessionTabCaptureAccessHandler::~PublicSessionTabCaptureAccessHandler() {}
-
-bool PublicSessionTabCaptureAccessHandler::SupportsStreamType(
-    content::WebContents* web_contents,
-    const blink::mojom::MediaStreamType type,
-    const extensions::Extension* extension) {
-  return tab_capture_access_handler_.SupportsStreamType(web_contents, type,
-                                                        extension);
-}
-
-bool PublicSessionTabCaptureAccessHandler::CheckMediaAccessPermission(
-    content::RenderFrameHost* render_frame_host,
-    const GURL& security_origin,
-    blink::mojom::MediaStreamType type,
-    const extensions::Extension* extension) {
-  return tab_capture_access_handler_.CheckMediaAccessPermission(
-      render_frame_host, security_origin, type, extension);
-}
-
-void PublicSessionTabCaptureAccessHandler::HandleRequest(
-    content::WebContents* web_contents,
-    const content::MediaStreamRequest& request,
-    content::MediaResponseCallback callback,
-    const extensions::Extension* extension) {
-  // This class handles requests for Public Sessions only, outside of them just
-  // pass the request through to the original class.
-  if (!profiles::ArePublicSessionRestrictionsEnabled() || !extension ||
-      (request.audio_type !=
-           blink::mojom::MediaStreamType::GUM_TAB_AUDIO_CAPTURE &&
-       request.video_type !=
-           blink::mojom::MediaStreamType::GUM_TAB_VIDEO_CAPTURE)) {
-    return tab_capture_access_handler_.HandleRequest(
-        web_contents, request, std::move(callback), extension);
-  }
-
-  // This Unretained is safe because the lifetime of this object is until
-  // process exit (living inside a base::Singleton object).
-  auto prompt_resolved_callback =
-      base::BindOnce(&PublicSessionTabCaptureAccessHandler::ChainHandleRequest,
-                     base::Unretained(this), web_contents, request,
-                     std::move(callback), base::RetainedRef(extension));
-
-  extensions::permission_helper::HandlePermissionRequest(
-      *extension, {extensions::mojom::APIPermissionID::kTabCapture},
-      web_contents, std::move(prompt_resolved_callback),
-      extensions::permission_helper::PromptFactory());
-}
-
-void PublicSessionTabCaptureAccessHandler::ChainHandleRequest(
-    content::WebContents* web_contents,
-    const content::MediaStreamRequest& request,
-    content::MediaResponseCallback callback,
-    const extensions::Extension* extension,
-    const extensions::PermissionIDSet& allowed_permissions) {
-  content::MediaStreamRequest request_copy(request);
-
-  // If the user denied tab capture, here the request gets filtered out before
-  // being passed on to the actual implementation.
-  if (!allowed_permissions.ContainsID(
-          extensions::mojom::APIPermissionID::kTabCapture)) {
-    request_copy.audio_type = blink::mojom::MediaStreamType::NO_SERVICE;
-    request_copy.video_type = blink::mojom::MediaStreamType::NO_SERVICE;
-  }
-
-  // Pass the request through to the original class.
-  tab_capture_access_handler_.HandleRequest(web_contents, request_copy,
-                                            std::move(callback), extension);
-}
diff --git a/chrome/browser/media/public_session_tab_capture_access_handler.h b/chrome/browser/media/public_session_tab_capture_access_handler.h
deleted file mode 100644
index 27df402..0000000
--- a/chrome/browser/media/public_session_tab_capture_access_handler.h
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2016 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_MEDIA_PUBLIC_SESSION_TAB_CAPTURE_ACCESS_HANDLER_H_
-#define CHROME_BROWSER_MEDIA_PUBLIC_SESSION_TAB_CAPTURE_ACCESS_HANDLER_H_
-
-#include "chrome/browser/extensions/extension_install_prompt.h"
-#include "chrome/browser/media/capture_access_handler_base.h"
-#include "chrome/browser/media/webrtc/tab_capture_access_handler.h"
-#include "extensions/common/extension_id.h"
-#include "extensions/common/permissions/api_permission_set.h"
-#include "third_party/blink/public/common/mediastream/media_stream_request.h"
-
-// MediaAccessHandler for TabCapture API in Public Sessions. This class is
-// implemented as a wrapper around TabCaptureAccessHandler. It allows for finer
-// access control to the TabCapture manifest permission feature inside of Public
-// Sessions.
-//
-// In Public Sessions, extensions (and apps) are force-installed by admin policy
-// so the user does not get a chance to review the permissions for these
-// extensions. This is not acceptable from a security/privacy standpoint, so
-// when an extension uses the TabCapture API for the first time, we show the
-// user a dialog where they can choose whether to allow the extension access to
-// the API.
-class PublicSessionTabCaptureAccessHandler : public CaptureAccessHandlerBase {
- public:
-  PublicSessionTabCaptureAccessHandler();
-
-  PublicSessionTabCaptureAccessHandler(
-      const PublicSessionTabCaptureAccessHandler&) = delete;
-  PublicSessionTabCaptureAccessHandler& operator=(
-      const PublicSessionTabCaptureAccessHandler&) = delete;
-
-  ~PublicSessionTabCaptureAccessHandler() override;
-
-  // MediaAccessHandler implementation.
-  bool SupportsStreamType(content::WebContents* web_contents,
-                          const blink::mojom::MediaStreamType type,
-                          const extensions::Extension* extension) override;
-  bool CheckMediaAccessPermission(
-      content::RenderFrameHost* render_frame_host,
-      const GURL& security_origin,
-      blink::mojom::MediaStreamType type,
-      const extensions::Extension* extension) override;
-  void HandleRequest(content::WebContents* web_contents,
-                     const content::MediaStreamRequest& request,
-                     content::MediaResponseCallback callback,
-                     const extensions::Extension* extension) override;
-
- private:
-  // Helper function used to chain the HandleRequest call into the original
-  // inside of TabCaptureAccessHandler.
-  void ChainHandleRequest(
-      content::WebContents* web_contents,
-      const content::MediaStreamRequest& request,
-      content::MediaResponseCallback callback,
-      const extensions::Extension* extension,
-      const extensions::PermissionIDSet& allowed_permissions);
-
-  TabCaptureAccessHandler tab_capture_access_handler_;
-};
-
-#endif  // CHROME_BROWSER_MEDIA_PUBLIC_SESSION_TAB_CAPTURE_ACCESS_HANDLER_H_
diff --git a/chrome/browser/media/webrtc/media_capture_devices_dispatcher.cc b/chrome/browser/media/webrtc/media_capture_devices_dispatcher.cc
index 64c0e25..35472c6 100644
--- a/chrome/browser/media/webrtc/media_capture_devices_dispatcher.cc
+++ b/chrome/browser/media/webrtc/media_capture_devices_dispatcher.cc
@@ -46,9 +46,8 @@
 #endif  //  BUILDFLAG(IS_ANDROID)
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-#include "chrome/browser/media/chromeos_login_media_access_handler.h"
+#include "chrome/browser/media/chromeos_login_and_lock_media_access_handler.h"
 #include "chrome/browser/media/public_session_media_access_handler.h"
-#include "chrome/browser/media/public_session_tab_capture_access_handler.h"
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
@@ -93,7 +92,7 @@
 #if BUILDFLAG(ENABLE_EXTENSIONS)
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   media_access_handlers_.push_back(
-      std::make_unique<ChromeOSLoginMediaAccessHandler>());
+      std::make_unique<ChromeOSLoginAndLockMediaAccessHandler>());
   // Wrapper around ExtensionMediaAccessHandler used in Public Sessions.
   media_access_handlers_.push_back(
       std::make_unique<PublicSessionMediaAccessHandler>());
@@ -103,14 +102,8 @@
 #endif
   media_access_handlers_.push_back(
       std::make_unique<DesktopCaptureAccessHandler>());
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  // Wrapper around TabCaptureAccessHandler used in Public Sessions.
-  media_access_handlers_.push_back(
-      std::make_unique<PublicSessionTabCaptureAccessHandler>());
-#else
   media_access_handlers_.push_back(std::make_unique<TabCaptureAccessHandler>());
 #endif
-#endif
   media_access_handlers_.push_back(
       std::make_unique<PermissionBubbleMediaAccessHandler>());
 }
diff --git a/chrome/browser/policy/configuration_policy_handler_list_factory.cc b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
index 30a326e..2627478 100644
--- a/chrome/browser/policy/configuration_policy_handler_list_factory.cc
+++ b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
@@ -139,6 +139,7 @@
 #include "chrome/browser/policy/browser_signin_policy_handler.h"
 #else
 #include "chrome/browser/policy/system_features_disable_list_policy_handler.h"
+#include "chromeos/ui/wm/fullscreen/pref_names.h"
 #endif  // !BUILDFLAG(IS_CHROMEOS)
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
@@ -937,9 +938,6 @@
   { key::kFullscreenAlertEnabled,
     ash::prefs::kFullscreenAlertEnabled,
     base::Value::Type::BOOLEAN },
-  { key::kKeepFullscreenWithoutNotificationUrlAllowList,
-    ash::prefs::kKeepFullscreenWithoutNotificationUrlAllowList,
-    base::Value::Type::LIST },
   { key::kDeviceLoginScreenDefaultLargeCursorEnabled,
     nullptr,
     base::Value::Type::BOOLEAN },
@@ -1597,6 +1595,9 @@
   { key::kEnableSyncConsent,
     prefs::kEnableSyncConsent,
     base::Value::Type::BOOLEAN },
+  { key::kKeepFullscreenWithoutNotificationUrlAllowList,
+    chromeos::prefs::kKeepFullscreenWithoutNotificationUrlAllowList,
+    base::Value::Type::LIST },
   { key::kRestrictedManagedGuestSessionExtensionCleanupExemptList,
     prefs::kRestrictedManagedGuestSessionExtensionCleanupExemptList,
     base::Value::Type::LIST },
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 08a3d7d..d797800 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -285,6 +285,7 @@
 #include "chrome/browser/policy/networking/policy_cert_service.h"
 #include "chrome/browser/policy/system_features_disable_list_policy_handler.h"
 #include "chrome/browser/ui/webui/certificates_handler.h"
+#include "chromeos/ui/wm/fullscreen/pref_names.h"
 #if defined(USE_CUPS)
 #include "chrome/browser/extensions/api/printing/printing_api_handler.h"
 #endif  // defined(USE_CUPS)
@@ -1422,6 +1423,9 @@
   registry->RegisterBooleanPref(prefs::kInsightsExtensionEnabled, false);
   // By default showing Sync Consent is set to true. It can changed by policy.
   registry->RegisterBooleanPref(prefs::kEnableSyncConsent, true);
+  registry->RegisterListPref(
+      chromeos::prefs::kKeepFullscreenWithoutNotificationUrlAllowList,
+      PrefRegistry::PUBLIC);
 #if defined(USE_CUPS)
   extensions::PrintingAPIHandler::RegisterProfilePrefs(registry);
 #endif  // defined(USE_CUPS)
diff --git a/chrome/browser/privacy_budget/privacy_budget_reid_score_estimator_unittest.cc b/chrome/browser/privacy_budget/privacy_budget_reid_score_estimator_unittest.cc
index 53b728a..ed33a917 100644
--- a/chrome/browser/privacy_budget/privacy_budget_reid_score_estimator_unittest.cc
+++ b/chrome/browser/privacy_budget/privacy_budget_reid_score_estimator_unittest.cc
@@ -20,7 +20,8 @@
 #include "third_party/blink/public/common/privacy_budget/identifiable_token.h"
 #include "third_party/blink/public/common/privacy_budget/scoped_identifiability_test_sample_collector.h"
 
-class PrivacyBudgetReidScoreEstimatorStandaloneTest : public ::testing::Test {
+class PrivacyBudgetReidScoreEstimatorStandaloneTest
+    : public ::testing::TestWithParam<std::tuple<uint32_t, int, int, bool>> {
  public:
   PrivacyBudgetReidScoreEstimatorStandaloneTest() {
     prefs::RegisterPrivacyBudgetPrefs(pref_service_.registry());
@@ -30,6 +31,38 @@
 
   void RunUntilIdle() { task_environment_.RunUntilIdle(); }
 
+  void ProcessReidRecords(IdentifiabilityStudyGroupSettings* settings,
+                          bool fixedToken) {
+    for (int i = 0; i < kNumIterations; ++i) {
+      PrivacyBudgetReidScoreEstimator reid_storage(settings, pref_service());
+      reid_storage.ResetPersistedState();
+      reid_storage.Init();
+      int64_t token1 =
+          fixedToken ? 1 : static_cast<int64_t>(base::RandUint64());
+      int64_t token2 =
+          fixedToken ? 2 : static_cast<int64_t>(base::RandUint64());
+      // Process values for 2 surfaces.
+      reid_storage.ProcessForReidScore(kSurface_1,
+                                       blink::IdentifiableToken(token1));
+      reid_storage.ProcessForReidScore(kSurface_2,
+                                       blink::IdentifiableToken(token2));
+    }
+  }
+
+  // Example surfaces for testing.
+  const blink::IdentifiableSurface kSurface_1 =
+      blink::IdentifiableSurface::FromMetricHash(2077075229u);
+  const blink::IdentifiableSurface kSurface_2 =
+      blink::IdentifiableSurface::FromMetricHash(1122849309u);
+
+  // Expected Reid surface key to be reported based on the example surfaces
+  // defined (kSurface_1, kSurface_2) using the function
+  // IdentifiableSurface::FromTypeAndToken() with type
+  // IdentifiableSurface::Type::kReidScoreEstimator.
+  const uint64_t kExpectedSurface = 11332616172707669541u;
+
+  const int kNumIterations = 50;
+
  private:
   TestingPrefServiceSimple pref_service_;
   base::test::SingleThreadTaskEnvironment task_environment_;
@@ -56,8 +89,9 @@
   reid_storage.Init();
 }
 
-TEST_F(PrivacyBudgetReidScoreEstimatorStandaloneTest,
-       ReportReidFixedTokenRandomSalt) {
+TEST_P(PrivacyBudgetReidScoreEstimatorStandaloneTest,
+       ReportReidwithParameters) {
+  const auto [salt_ranges, bits, noise, fixed_token] = GetParam();
   auto settings = IdentifiabilityStudyGroupSettings::InitFrom(
       /*enabled=*/true,
       /*expected_surface_count=*/0,
@@ -66,177 +100,22 @@
       /*blocks_weights=*/"",
       /*allowed_random_types=*/"",
       /*reid_blocks=*/"2077075229;1122849309",
-      /*reid_blocks_salts_ranges=*/"1000000",
-      /*reid_blocks_bits=*/"1",
-      /*reid_blocks_noise_probabilities=*/"0");
+      /*reid_blocks_salts_ranges=*/base::NumberToString(salt_ranges),
+      /*reid_blocks_bits=*/base::NumberToString(bits),
+      /*reid_blocks_noise_probabilities=*/base::NumberToString(noise));
 
-  constexpr auto surface_1 =
-      blink::IdentifiableSurface::FromMetricHash(2077075229u);
-  constexpr auto surface_2 =
-      blink::IdentifiableSurface::FromMetricHash(1122849309u);
-
-  int64_t token1 = 1234;
-  int64_t token2 = 12345;
-  constexpr int num_iterations = 50;
   ukm::TestAutoSetUkmRecorder test_recorder;
   base::RunLoop run_loop;
   test_recorder.SetOnAddEntryCallback(
       ukm::builders::Identifiability::kEntryName,
-      base::BarrierClosure(num_iterations, run_loop.QuitClosure()));
+      base::BarrierClosure(kNumIterations, run_loop.QuitClosure()));
   blink::test::ScopedIdentifiabilityTestSampleCollector collector;
-  for (int i = 0; i < num_iterations; ++i) {
-    PrivacyBudgetReidScoreEstimator reid_storage(&settings, pref_service());
-    reid_storage.ResetPersistedState();
-    reid_storage.Init();
-    // Process values for 2 surfaces.
-    reid_storage.ProcessForReidScore(surface_1,
-                                     blink::IdentifiableToken(token1));
-    reid_storage.ProcessForReidScore(surface_2,
-                                     blink::IdentifiableToken(token2));
-  }
+  ProcessReidRecords(&settings, fixed_token);
   // This should let the async tasks run.
   run_loop.Run();
   const auto& entries = collector.entries();
   bool has_value_0 = false;
   bool has_value_1 = false;
-  int count = 0;
-  for (auto& entry : entries) {
-    for (auto& metric : entry.metrics) {
-      auto surface = metric.surface;
-      if (surface.GetType() ==
-          blink::IdentifiableSurface::Type::kReidScoreEstimator) {
-        EXPECT_EQ(metric.surface.ToUkmMetricHash(), 11332616172707669541u);
-        ++count;
-        uint64_t hash = static_cast<uint64_t>(metric.value.ToUkmMetricValue());
-        uint32_t reid_bits = hash & 0xFFFFFFFF;
-        EXPECT_TRUE(reid_bits == 0 || reid_bits == 1);
-        if (reid_bits == 0)
-          has_value_0 = true;
-        else if (reid_bits == 1)
-          has_value_1 = true;
-        uint32_t salt = (hash >> 32);
-        EXPECT_LT(salt, 1000000u);
-      }
-    }
-  }
-  EXPECT_EQ(count, num_iterations);
-  // Since the 1 bit should be random, the probability of it being always 0 or
-  // always 1 is 2/(2^num_iterations), hence it should be negligible.
-  EXPECT_TRUE(has_value_0);
-  EXPECT_TRUE(has_value_1);
-}
-
-TEST_F(PrivacyBudgetReidScoreEstimatorStandaloneTest,
-       ReportReidRandomTokenFixedSalt) {
-  auto settings = IdentifiabilityStudyGroupSettings::InitFrom(
-      /*enabled=*/true,
-      /*expected_surface_count=*/0,
-      /*surface_budget=*/0,
-      /*blocks=*/"",
-      /*blocks_weights=*/"",
-      /*allowed_random_types=*/"",
-      /*reid_blocks=*/"2077075229;1122849309",
-      /*reid_blocks_salts_ranges=*/"1",
-      /*reid_blocks_bits=*/"1",
-      /*reid_blocks_noise_probabilities=*/"0");
-
-  constexpr auto surface_1 =
-      blink::IdentifiableSurface::FromMetricHash(2077075229u);
-  constexpr auto surface_2 =
-      blink::IdentifiableSurface::FromMetricHash(1122849309u);
-  constexpr int num_iterations = 50;
-  ukm::TestAutoSetUkmRecorder test_recorder;
-  base::RunLoop run_loop;
-  test_recorder.SetOnAddEntryCallback(
-      ukm::builders::Identifiability::kEntryName,
-      base::BarrierClosure(num_iterations, run_loop.QuitClosure()));
-  blink::test::ScopedIdentifiabilityTestSampleCollector collector;
-  for (int i = 0; i < num_iterations; ++i) {
-    PrivacyBudgetReidScoreEstimator reid_storage(&settings, pref_service());
-    reid_storage.ResetPersistedState();
-    reid_storage.Init();
-    // Create random tokens.
-    int64_t token1 = static_cast<int64_t>(base::RandUint64());
-    int64_t token2 = static_cast<int64_t>(base::RandUint64());
-    // Process values for 2 surfaces.
-    reid_storage.ProcessForReidScore(surface_1,
-                                     blink::IdentifiableToken(token1));
-    reid_storage.ProcessForReidScore(surface_2,
-                                     blink::IdentifiableToken(token2));
-  }
-  // This should let the async tasks run.
-  run_loop.Run();
-  const auto& entries = collector.entries();
-  bool has_value_0 = false;
-  bool has_value_1 = false;
-  int count = 0;
-  for (auto& entry : entries) {
-    for (auto& metric : entry.metrics) {
-      auto surface = metric.surface;
-      if (surface.GetType() ==
-          blink::IdentifiableSurface::Type::kReidScoreEstimator) {
-        EXPECT_EQ(metric.surface.ToUkmMetricHash(), 11332616172707669541u);
-        ++count;
-        uint64_t hash = static_cast<uint64_t>(metric.value.ToUkmMetricValue());
-        uint32_t reid_bits = hash & 0xFFFFFFFF;
-        EXPECT_TRUE(reid_bits == 0 || reid_bits == 1);
-        if (reid_bits == 0)
-          has_value_0 = true;
-        else if (reid_bits == 1)
-          has_value_1 = true;
-        uint32_t salt = (hash >> 32);
-        EXPECT_EQ(salt, 0u);
-      }
-    }
-  }
-  EXPECT_EQ(count, num_iterations);
-  // Since the 1 bit should be random, the probability of it being always 0 or
-  // always 1 is 2/(2^num_iterations), hence it should be negligible.
-  EXPECT_TRUE(has_value_0);
-  EXPECT_TRUE(has_value_1);
-}
-
-TEST_F(PrivacyBudgetReidScoreEstimatorStandaloneTest,
-       ReportReidFixedTokenFixedSaltAllNoise) {
-  auto settings = IdentifiabilityStudyGroupSettings::InitFrom(
-      /*enabled=*/true,
-      /*expected_surface_count=*/0,
-      /*surface_budget=*/0,
-      /*blocks=*/"",
-      /*blocks_weights=*/"",
-      /*allowed_random_types=*/"",
-      /*reid_blocks=*/"2077075229;1122849309",
-      /*reid_blocks_salts_ranges=*/"1",
-      /*reid_blocks_bits=*/"32",
-      /*reid_blocks_noise_probabilities=*/"1");
-
-  constexpr auto surface_1 =
-      blink::IdentifiableSurface::FromMetricHash(2077075229u);
-  constexpr auto surface_2 =
-      blink::IdentifiableSurface::FromMetricHash(1122849309u);
-
-  int64_t token1 = 1234;
-  int64_t token2 = 12345;
-  constexpr int num_iterations = 50;
-  ukm::TestAutoSetUkmRecorder test_recorder;
-  base::RunLoop run_loop;
-  test_recorder.SetOnAddEntryCallback(
-      ukm::builders::Identifiability::kEntryName,
-      base::BarrierClosure(num_iterations, run_loop.QuitClosure()));
-  blink::test::ScopedIdentifiabilityTestSampleCollector collector;
-  for (int i = 0; i < num_iterations; ++i) {
-    PrivacyBudgetReidScoreEstimator reid_storage(&settings, pref_service());
-    reid_storage.ResetPersistedState();
-    reid_storage.Init();
-    // Process values for 2 surfaces.
-    reid_storage.ProcessForReidScore(surface_1,
-                                     blink::IdentifiableToken(token1));
-    reid_storage.ProcessForReidScore(surface_2,
-                                     blink::IdentifiableToken(token2));
-  }
-  // This should let the async tasks run.
-  run_loop.Run();
-  const auto& entries = collector.entries();
   base::flat_set<uint32_t> reid_results;
   int count = 0;
   for (auto& entry : entries) {
@@ -244,21 +123,41 @@
       auto surface = metric.surface;
       if (surface.GetType() ==
           blink::IdentifiableSurface::Type::kReidScoreEstimator) {
-        EXPECT_EQ(metric.surface.ToUkmMetricHash(), 11332616172707669541u);
+        EXPECT_EQ(metric.surface.ToUkmMetricHash(), kExpectedSurface);
         ++count;
         uint64_t hash = static_cast<uint64_t>(metric.value.ToUkmMetricValue());
         uint32_t reid_bits = hash & 0xFFFFFFFF;
-        // Result should be noise i.e. didn't appeared before.
-        EXPECT_FALSE(reid_results.contains(reid_bits));
-        reid_results.insert(reid_bits);
+        if (noise == 1) {
+          // Result should be noise i.e. didn't appear before.
+          EXPECT_FALSE(reid_results.contains(reid_bits));
+          reid_results.insert(reid_bits);
+        } else {
+          EXPECT_TRUE(reid_bits == 0 || reid_bits == 1);
+          if (reid_bits == 0)
+            has_value_0 = true;
+          else if (reid_bits == 1)
+            has_value_1 = true;
+        }
         uint32_t salt = (hash >> 32);
-        EXPECT_EQ(salt, 0u);
+        EXPECT_TRUE((salt >= 0) && (salt < salt_ranges));
       }
     }
   }
-  EXPECT_EQ(count, num_iterations);
+  EXPECT_EQ(count, kNumIterations);
+  // Since the 1 bit should be random, the probability of it being always 0 or
+  // always 1 is 2/(2^kNumIterations), hence it should be negligible.
+  if (noise != 1) {
+    EXPECT_TRUE(has_value_0);
+    EXPECT_TRUE(has_value_1);
+  }
 }
 
+INSTANTIATE_TEST_SUITE_P(PrivacyBudgetReidScoreEstimatorParameterizedTest,
+                         PrivacyBudgetReidScoreEstimatorStandaloneTest,
+                         ::testing::Values(std::make_tuple(1000000, 1, 0, true),
+                                           std::make_tuple(1, 1, 0, false),
+                                           std::make_tuple(1, 32, 1, true)));
+
 TEST_F(PrivacyBudgetReidScoreEstimatorStandaloneTest,
        ReidHashIsReportedOnlyOnce) {
   auto settings = IdentifiabilityStudyGroupSettings::InitFrom(
@@ -273,11 +172,6 @@
       /*reid_blocks_bits=*/"1",
       /*reid_blocks_noise_probabilities=*/"0");
 
-  constexpr auto surface_1 =
-      blink::IdentifiableSurface::FromMetricHash(2077075229u);
-  constexpr auto surface_2 =
-      blink::IdentifiableSurface::FromMetricHash(1122849309u);
-
   ukm::TestAutoSetUkmRecorder test_recorder;
 
   {
@@ -291,8 +185,8 @@
       blink::test::ScopedIdentifiabilityTestSampleCollector collector;
 
       // Process values for 2 surfaces.
-      reid_storage.ProcessForReidScore(surface_1, blink::IdentifiableToken(1));
-      reid_storage.ProcessForReidScore(surface_2, blink::IdentifiableToken(2));
+      reid_storage.ProcessForReidScore(kSurface_1, blink::IdentifiableToken(1));
+      reid_storage.ProcessForReidScore(kSurface_2, blink::IdentifiableToken(2));
 
       // This should let the async tasks run.
       run_loop.Run();
@@ -301,7 +195,7 @@
       EXPECT_EQ(entries.size(), 1u);
       EXPECT_EQ(entries[0].metrics.size(), 1u);
       EXPECT_EQ(entries[0].metrics[0].surface.ToUkmMetricHash(),
-                11332616172707669541u);
+                kExpectedSurface);
     }
 
     // Now check that the reid hash is not reported again if we see again the
@@ -309,8 +203,8 @@
     {
       blink::test::ScopedIdentifiabilityTestSampleCollector collector;
 
-      reid_storage.ProcessForReidScore(surface_1, blink::IdentifiableToken(1));
-      reid_storage.ProcessForReidScore(surface_2, blink::IdentifiableToken(2));
+      reid_storage.ProcessForReidScore(kSurface_1, blink::IdentifiableToken(1));
+      reid_storage.ProcessForReidScore(kSurface_2, blink::IdentifiableToken(2));
 
       RunUntilIdle();
       const auto& entries = collector.entries();
@@ -327,8 +221,8 @@
     {
       blink::test::ScopedIdentifiabilityTestSampleCollector collector;
 
-      reid_storage.ProcessForReidScore(surface_1, blink::IdentifiableToken(1));
-      reid_storage.ProcessForReidScore(surface_2, blink::IdentifiableToken(2));
+      reid_storage.ProcessForReidScore(kSurface_1, blink::IdentifiableToken(1));
+      reid_storage.ProcessForReidScore(kSurface_2, blink::IdentifiableToken(2));
 
       RunUntilIdle();
 
@@ -347,8 +241,8 @@
           ukm::builders::Identifiability::kEntryName, run_loop.QuitClosure());
 
       blink::test::ScopedIdentifiabilityTestSampleCollector collector;
-      reid_storage.ProcessForReidScore(surface_1, blink::IdentifiableToken(1));
-      reid_storage.ProcessForReidScore(surface_2, blink::IdentifiableToken(2));
+      reid_storage.ProcessForReidScore(kSurface_1, blink::IdentifiableToken(1));
+      reid_storage.ProcessForReidScore(kSurface_2, blink::IdentifiableToken(2));
 
       run_loop.Run();
 
@@ -356,7 +250,7 @@
       EXPECT_EQ(entries.size(), 1u);
       EXPECT_EQ(entries[0].metrics.size(), 1u);
       EXPECT_EQ(entries[0].metrics[0].surface.ToUkmMetricHash(),
-                11332616172707669541u);
+                kExpectedSurface);
     }
   }
 }
diff --git a/chrome/browser/profiles/profile_shortcut_manager_win.cc b/chrome/browser/profiles/profile_shortcut_manager_win.cc
index 11f19c4..741bbd13 100644
--- a/chrome/browser/profiles/profile_shortcut_manager_win.cc
+++ b/chrome/browser/profiles/profile_shortcut_manager_win.cc
@@ -76,7 +76,7 @@
 // Incrementing this number will cause profile icons to be regenerated on
 // profile startup (it should be incremented whenever the product/avatar icons
 // change, etc).
-const int kCurrentProfileIconVersion = 9;
+const int kCurrentProfileIconVersion = 10;
 
 bool disabled_for_unit_tests = false;
 bool disable_unpinning_for_unit_tests = false;
diff --git a/chrome/browser/push_messaging/push_messaging_service_impl.cc b/chrome/browser/push_messaging/push_messaging_service_impl.cc
index de0d1a13..993582aa0 100644
--- a/chrome/browser/push_messaging/push_messaging_service_impl.cc
+++ b/chrome/browser/push_messaging/push_messaging_service_impl.cc
@@ -78,7 +78,7 @@
 #if BUILDFLAG(IS_ANDROID)
 #include "base/android/jni_android.h"
 #include "chrome/android/chrome_jni_headers/PushMessagingServiceObserver_jni.h"
-#include "chrome/browser/android/shortcut_helper.h"
+#include "chrome/browser/installable/installed_webapp_bridge.h"
 #include "components/permissions/android/android_permission_util.h"
 #include "components/prefs/pref_service.h"
 #endif
@@ -441,28 +441,23 @@
     return false;
   }
 
-  bool has_app_level_notification_permission =
-      enabled_app_level_notification_permission_for_testing_.has_value()
-          ? enabled_app_level_notification_permission_for_testing_.value()
-          : permissions::AreAppLevelNotificationsEnabled();
+  bool webapp_can_display_notifications =
+      InstalledWebappBridge::GetPermission(ContentSettingsType::NOTIFICATIONS,
+                                           app_identifier.origin()) ==
+      ContentSetting::CONTENT_SETTING_ALLOW;
 
-  bool contains_webapk = ShortcutHelper::DoesOriginContainAnyInstalledWebApk(
-      app_identifier.origin());
-  bool contains_twa =
-      ShortcutHelper::DoesOriginContainAnyInstalledTrustedWebActivity(
-          app_identifier.origin());
-  bool contains_installed_webapp = contains_twa || contains_webapk;
-
-  // If Notifications permission delegation is enabled, for the
-  // `app_identifier.origin()`, we should not revoke permissions because
-  // Notifications permissions are automatically synced with an installed app.
-  if (contains_installed_webapp)
+  // An incoming push message will be displayed by an installed webapp.
+  if (webapp_can_display_notifications)
     return false;
 
   PrefService* prefs = prefs_for_testing_.has_value()
                            ? prefs_for_testing_.value()
                            : g_browser_process->local_state();
 
+  bool has_app_level_notification_permission =
+      enabled_app_level_notification_permission_for_testing_.value_or(
+          permissions::AreAppLevelNotificationsEnabled());
+
   if (has_app_level_notification_permission) {
     // Chrome has app-level Notifications permission. Reset the grace period
     // flag and continue as normal.
diff --git a/chrome/browser/reputation/local_heuristics.cc b/chrome/browser/reputation/local_heuristics.cc
index 581267a..dd83ff8 100644
--- a/chrome/browser/reputation/local_heuristics.cc
+++ b/chrome/browser/reputation/local_heuristics.cc
@@ -111,8 +111,9 @@
           reputation::HeuristicLaunchConfig::HEURISTIC_CHARACTER_SWAP_TOP_SITES,
           navigated_domain.domain_and_registry, chrome::GetChannel());
     case LookalikeUrlMatchType::kComboSquatting:
+      return true;
     case LookalikeUrlMatchType::kComboSquattingSiteEngagement:
-      return false;
+      return true;
     case LookalikeUrlMatchType::kNone:
       NOTREACHED();
   }
diff --git a/chrome/browser/resources/settings/autofill_page/credit_card_edit_dialog.html b/chrome/browser/resources/settings/autofill_page/credit_card_edit_dialog.html
index 19592bf..4270d7b 100644
--- a/chrome/browser/resources/settings/autofill_page/credit_card_edit_dialog.html
+++ b/chrome/browser/resources/settings/autofill_page/credit_card_edit_dialog.html
@@ -77,7 +77,7 @@
       <div slot="title">[[title_]]</div>
       <div slot="body">
         <cr-input id="numberInput" label="$i18n{creditCardNumber}"
-            value="{{creditCard.cardNumber}}" autofocus>
+            value="{{cardNumber_}}" autofocus>
         </cr-input>
         <!-- aria-hidden for creditCardExpiration label since
           creditCardExpirationMonth and creditCardExpirationYear provide
@@ -104,15 +104,15 @@
         <div id="expiredError">$i18n{creditCardExpired}</div>
         <!-- Place cardholder name field and nickname field after expiration.-->
         <cr-input id="nameInput" label="$i18n{creditCardName}"
-            value="{{creditCard.name}}" spellcheck="false">
+            value="{{name_}}" spellcheck="false">
         </cr-input>
         <cr-input id="nicknameInput" label="$i18n{creditCardNickname}"
-            value="{{creditCard.nickname}}" spellcheck="false" maxlength="25"
+            value="{{nickname_}}" spellcheck="false" maxlength="25"
             on-input="validateNickname_"
             invalid="[[nicknameInvalid_]]"
             error-message="$i18n{creditCardNicknameInvalid}">
             <div id="charCount" slot="suffix">
-              [[computeNicknameCharCount_(creditCard.nickname)]]/25
+              [[computeNicknameCharCount_(nickname_)]]/25
             </div>
         </cr-input>
         <div id="saved-to-this-device-only-label">
@@ -124,8 +124,8 @@
             on-click="onCancelButtonTap_">$i18n{cancel}</cr-button>
         <cr-button id="saveButton" class="action-button"
             on-click="onSaveButtonTap_"
-            disabled="[[!saveEnabled_(nicknameInvalid_, creditCard.*,
-                expired_)]]">
+            disabled="[[!saveEnabled_(nicknameInvalid_,
+                expired_, name_, cardNumber_, nickname_)]]">
           $i18n{save}
         </cr-button>
       </div>
diff --git a/chrome/browser/resources/settings/autofill_page/credit_card_edit_dialog.ts b/chrome/browser/resources/settings/autofill_page/credit_card_edit_dialog.ts
index 1527c0e..7682fb9 100644
--- a/chrome/browser/resources/settings/autofill_page/credit_card_edit_dialog.ts
+++ b/chrome/browser/resources/settings/autofill_page/credit_card_edit_dialog.ts
@@ -100,6 +100,9 @@
       /** The list of years to show in the dropdown. */
       yearList_: Array,
 
+      name_: String,
+      cardNumber_: String,
+      nickname_: String,
       expirationYear_: String,
       expirationMonth_: String,
 
@@ -122,6 +125,9 @@
   private title_: string;
   private monthList_: string[];
   private yearList_: string[];
+  private name_?: string;
+  private cardNumber_?: string;
+  private nickname_?: string;
   private expirationYear_?: string;
   private expirationMonth_?: string;
   private nicknameInvalid_: boolean;
@@ -179,6 +185,9 @@
     microTask.run(() => {
       this.expirationYear_ = selectedYear.toString();
       this.expirationMonth_ = this.creditCard.expirationMonth;
+      this.name_ = this.creditCard.name;
+      this.cardNumber_ = this.creditCard.cardNumber;
+      this.nickname_ = this.creditCard.nickname;
       this.$.dialog.showModal();
     });
   }
@@ -205,6 +214,9 @@
 
     this.creditCard.expirationYear = this.expirationYear_;
     this.creditCard.expirationMonth = this.expirationMonth_;
+    this.creditCard.name = this.name_;
+    this.creditCard.cardNumber = this.cardNumber_;
+    this.creditCard.nickname = this.nickname_;
     this.trimCreditCard_();
     this.dispatchEvent(new CustomEvent(
         'save-credit-card',
@@ -222,12 +234,11 @@
 
   private saveEnabled_() {
     // The save button is enabled if:
-    // There is and name or number for the card
+    // There is a name or number for the card
     // and the expiration date is valid
     // and the nickname is valid if present.
-    return ((this.creditCard.name && this.creditCard.name.trim()) ||
-            (this.creditCard.cardNumber &&
-             this.creditCard.cardNumber.trim())) &&
+    return ((this.name_ && this.name_.trim()) ||
+            (this.cardNumber_ && this.cardNumber_.trim())) &&
         !this.expired_ && !this.nicknameInvalid_;
   }
 
@@ -258,8 +269,7 @@
    * the save button when invalid.
    */
   private validateNickname_() {
-    this.nicknameInvalid_ =
-        NICKNAME_INVALID_REGEX.test(this.creditCard.nickname!);
+    this.nicknameInvalid_ = NICKNAME_INVALID_REGEX.test(this.nickname_!);
   }
 
   /**
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/cellular_networks_list.js b/chrome/browser/resources/settings/chromeos/internet_page/cellular_networks_list.js
index a43b907..cb3d5e6 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/cellular_networks_list.js
+++ b/chrome/browser/resources/settings/chromeos/internet_page/cellular_networks_list.js
@@ -607,10 +607,10 @@
   /*
    * Returns the add esim button. If the device does not have an EUICC, no eSIM
    * slot, or policies prohibit users from adding a network, null is returned.
-   * @return {?CrIconButtonElement}
+   * @return {?HTMLElement}
    */
   getAddEsimButton() {
-    return /** @type {?CrIconButtonElement} */ (
+    return /** @type {?HTMLElement} */ (
         this.shadowRoot.querySelector('#addESimButton'));
   }
 
diff --git a/chrome/browser/ui/android/fast_checkout/BUILD.gn b/chrome/browser/ui/android/fast_checkout/BUILD.gn
index ff8eda15..b2d0155 100644
--- a/chrome/browser/ui/android/fast_checkout/BUILD.gn
+++ b/chrome/browser/ui/android/fast_checkout/BUILD.gn
@@ -26,3 +26,23 @@
     "java/src/org/chromium/chrome/browser/ui/fast_checkout/data/FastCheckoutCreditCard.java",
   ]
 }
+
+robolectric_library("junit") {
+  testonly = true
+
+  sources = [ "junit/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutControllerTest.java" ]
+  deps = [
+    ":java",
+    "//base:base_junit_test_support",
+    "//chrome/browser/ui/android/fast_checkout/internal:java",
+    "//components/autofill/android:main_autofill_java",
+    "//components/autofill_assistant/android:public_java",
+    "//components/browser_ui/bottomsheet/android:java",
+    "//third_party/androidx:androidx_recyclerview_recyclerview_java",
+    "//third_party/hamcrest:hamcrest_library_java",
+    "//third_party/junit:junit",
+    "//third_party/mockito:mockito_java",
+    "//ui/android:ui_java_test_support",
+    "//ui/android:ui_no_recycler_view_java",
+  ]
+}
diff --git a/chrome/browser/ui/android/fast_checkout/junit/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutControllerTest.java b/chrome/browser/ui/android/fast_checkout/junit/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutControllerTest.java
new file mode 100644
index 0000000..a9c4ac8
--- /dev/null
+++ b/chrome/browser/ui/android/fast_checkout/junit/java/src/org/chromium/chrome/browser/ui/fast_checkout/FastCheckoutControllerTest.java
@@ -0,0 +1,119 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.ui.fast_checkout;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import static org.chromium.chrome.browser.ui.fast_checkout.FastCheckoutModel.CURRENT_SCREEN;
+import static org.chromium.chrome.browser.ui.fast_checkout.FastCheckoutModel.VISIBLE;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.ui.fast_checkout.FastCheckoutModel.ScreenType;
+import org.chromium.chrome.browser.ui.fast_checkout.data.FastCheckoutAutofillProfile;
+import org.chromium.chrome.browser.ui.fast_checkout.data.FastCheckoutCreditCard;
+import org.chromium.components.autofill.VirtualCardEnrollmentState;
+import org.chromium.components.autofill_assistant.AutofillAssistantPublicTags;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
+import org.chromium.ui.modelutil.PropertyModel;
+
+/**
+ * Controller tests verify that the Fast Checkout controller modifies the model if the API is used
+ * properly.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class FastCheckoutControllerTest {
+    private static final FastCheckoutAutofillProfile[] DUMMY_PROFILES = {
+            createDummyProfile("John Doe", "john@gmail.com")};
+    private static final FastCheckoutCreditCard[] DUMMY_CARDS = {
+            createDummyCreditCard("https://example.com", "4111111111111111")};
+
+    @Mock
+    RecyclerView mMockParentView;
+    @Mock
+    private FastCheckoutComponent.Delegate mMockDelegate;
+    @Mock
+    private BottomSheetContent mMockBottomSheetContent;
+    @Mock
+    private BottomSheetController mMockBottomSheetController;
+
+    private FastCheckoutMediator mMediator = new FastCheckoutMediator();
+
+    private final PropertyModel mModel = FastCheckoutModel.createDefaultModel();
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mMediator.initialize(mMockDelegate, mModel, mMockBottomSheetController);
+    }
+
+    @Test
+    public void testCreatesValidDefaultModel() {
+        assertThat(mModel.get(VISIBLE), is(false));
+        assertThat(mModel.get(CURRENT_SCREEN), is(ScreenType.HOME_SCREEN));
+    }
+
+    @Test
+    public void testShowOptionsSetsVisibile() {
+        mMediator.showOptions(DUMMY_PROFILES, DUMMY_CARDS);
+
+        verify(mMockBottomSheetController, never()).hideContent(any(), anyBoolean());
+        assertThat(mModel.get(VISIBLE), is(true));
+    }
+
+    @Test
+    public void testAssistantOnboardingGetsHiddenIfShowing() {
+        when(mMockParentView.getTag())
+                .thenReturn(
+                        AutofillAssistantPublicTags.AUTOFILL_ASSISTANT_BOTTOM_SHEET_CONTENT_TAG);
+        when(mMockBottomSheetContent.getContentView()).thenReturn(mMockParentView);
+        when(mMockBottomSheetController.getCurrentSheetContent())
+                .thenReturn(mMockBottomSheetContent);
+
+        mMediator.showOptions(DUMMY_PROFILES, DUMMY_CARDS);
+
+        verify(mMockBottomSheetController).hideContent(any(), eq(true));
+    }
+
+    private static FastCheckoutAutofillProfile createDummyProfile(String name, String email) {
+        return new FastCheckoutAutofillProfile(/* guid= */ "", /* origin= */ "",
+                /* isLocal= */ true,
+                /* honorificPrefix= */ "", name,
+                /* companyName= */ "", /* streetAddress= */ "", /* region= */ "",
+                /* locality= */ "",
+                /* dependentLocality= */ "", /* postalCode= */ "", /* sortingCode= */ "",
+                /* countryCode= */ "", /* countryName= */ "", /* phoneNumber= */ "", email,
+                /* languageCode= */ "en-US");
+    }
+
+    private static FastCheckoutCreditCard createDummyCreditCard(String origin, String number) {
+        return new FastCheckoutCreditCard(/* guid= */ "john", origin, /* isLocal= */ true,
+                /* isCached= */ true, "John Doe", number, "1111", "12", "2050", "visa",
+                /* billingAddressId= */
+                "",
+                /* billingAddressId= */ "john",
+                /* serverId= */ "",
+                /* instrumentId= */ 0, /* nickname= */ "", /* cardArtUrl= */ null,
+                /* virtualCardEnrollmentState= */ VirtualCardEnrollmentState.UNSPECIFIED,
+                /* productDescription= */ "");
+    }
+}
diff --git a/chrome/browser/ui/ash/chrome_shell_delegate.cc b/chrome/browser/ui/ash/chrome_shell_delegate.cc
index b70e755f..6c9c130 100644
--- a/chrome/browser/ui/ash/chrome_shell_delegate.cc
+++ b/chrome/browser/ui/ash/chrome_shell_delegate.cc
@@ -21,6 +21,9 @@
 #include "chrome/browser/ash/arc/arc_util.h"
 #include "chrome/browser/ash/arc/session/arc_session_manager.h"
 #include "chrome/browser/ash/assistant/assistant_util.h"
+#include "chrome/browser/ash/crosapi/crosapi_ash.h"
+#include "chrome/browser/ash/crosapi/crosapi_manager.h"
+#include "chrome/browser/ash/crosapi/fullscreen_controller_ash.h"
 #include "chrome/browser/ash/file_manager/path_util.h"
 #include "chrome/browser/ash/multidevice_setup/multidevice_setup_service_factory.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
@@ -358,3 +361,11 @@
 std::string ChromeShellDelegate::GetVersionString() {
   return version_info::GetVersionNumber();
 }
+
+void ChromeShellDelegate::ShouldExitFullscreenBeforeLock(
+    ChromeShellDelegate::ShouldExitFullscreenCallback callback) {
+  crosapi::CrosapiManager::Get()
+      ->crosapi_ash()
+      ->fullscreen_controller_ash()
+      ->ShouldExitFullscreenBeforeLock(std::move(callback));
+}
diff --git a/chrome/browser/ui/ash/chrome_shell_delegate.h b/chrome/browser/ui/ash/chrome_shell_delegate.h
index 56ee35b..db4519b 100644
--- a/chrome/browser/ui/ash/chrome_shell_delegate.h
+++ b/chrome/browser/ui/ash/chrome_shell_delegate.h
@@ -67,6 +67,8 @@
   void ForceSkipWarningUserOnClose(
       const std::vector<aura::Window*>& windows) override;
   std::string GetVersionString() override;
+  void ShouldExitFullscreenBeforeLock(
+      ShouldExitFullscreenCallback callback) override;
 };
 
 #endif  // CHROME_BROWSER_UI_ASH_CHROME_SHELL_DELEGATE_H_
diff --git a/chrome/browser/ui/ash/projector/projector_client_impl.cc b/chrome/browser/ui/ash/projector/projector_client_impl.cc
index 18d230a7..72400cd 100644
--- a/chrome/browser/ui/ash/projector/projector_client_impl.cc
+++ b/chrome/browser/ui/ash/projector/projector_client_impl.cc
@@ -126,8 +126,7 @@
 }
 
 void ProjectorClientImpl::OpenProjectorApp() const {
-  auto* profile = ProfileManager::GetActiveUserProfile();
-  ash::LaunchSystemWebAppAsync(profile, ash::SystemWebAppType::PROJECTOR);
+  LaunchProjectorAppWithFiles(/*files=*/{});
 }
 
 void ProjectorClientImpl::MinimizeProjectorApp() const {
diff --git a/chrome/browser/ui/ash/projector/projector_client_impl_browsertest.cc b/chrome/browser/ui/ash/projector/projector_client_impl_browsertest.cc
index b1c97d2d..3ff4956c 100644
--- a/chrome/browser/ui/ash/projector/projector_client_impl_browsertest.cc
+++ b/chrome/browser/ui/ash/projector/projector_client_impl_browsertest.cc
@@ -14,6 +14,7 @@
 #include "ash/webui/projector_app/public/cpp/projector_app_constants.h"
 #include "base/bind.h"
 #include "base/callback_forward.h"
+#include "base/files/file_path.h"
 #include "base/run_loop.h"
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/apps/app_service/app_icon/app_icon_factory.h"
@@ -27,8 +28,10 @@
 #include "chrome/browser/ash/system_web_apps/types/system_web_app_type.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/ash/projector/projector_app_client_impl.h"
+#include "chrome/browser/ui/ash/projector/projector_utils.h"
 #include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
 #include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/web_applications/web_app_id.h"
@@ -198,6 +201,66 @@
             content::PAGE_TYPE_NORMAL);
 }
 
+// This test covers launching the Projector app with files for the first time.
+IN_PROC_BROWSER_TEST_F(ProjectorClientTest, LaunchProjectorAppWithFiles) {
+  auto* profile = browser()->profile();
+  SystemWebAppManager::GetForTest(profile)->InstallSystemAppsForTesting();
+
+  base::FilePath file1("test1"), file2("test2");
+  LaunchProjectorAppWithFiles({file1, file2});
+  FlushSystemWebAppLaunchesForTesting(profile);
+
+  // Verify that Projector App is opened.
+  Browser* app_browser =
+      FindSystemWebAppBrowser(profile, SystemWebAppType::PROJECTOR);
+  ASSERT_TRUE(app_browser);
+  content::WebContents* tab =
+      app_browser->tab_strip_model()->GetActiveWebContents();
+  ASSERT_TRUE(tab);
+  EXPECT_EQ(tab->GetController().GetVisibleEntry()->GetPageType(),
+            content::PAGE_TYPE_NORMAL);
+}
+
+// This test covers launching the Projector app with files when the app is
+// already open. The launch event should recycle the existing window and should
+// not open a new window.
+IN_PROC_BROWSER_TEST_F(ProjectorClientTest,
+                       LaunchProjectorAppWithFilesWhenAppAlreadyOpen) {
+  const size_t starting_browser_count = chrome::GetTotalBrowserCount();
+
+  auto* profile = browser()->profile();
+  SystemWebAppManager::GetForTest(profile)->InstallSystemAppsForTesting();
+
+  // Launch the app for the first time.
+  client()->OpenProjectorApp();
+  FlushSystemWebAppLaunchesForTesting(profile);
+
+  // Verify that Projector App is opened.
+  Browser* app_browser1 =
+      FindSystemWebAppBrowser(profile, SystemWebAppType::PROJECTOR);
+  ASSERT_TRUE(app_browser1);
+  EXPECT_EQ(chrome::GetTotalBrowserCount(), starting_browser_count + 1);
+
+  base::FilePath file1("test1"), file2("test2");
+  // Launch the app again with files. This operation should recycle the same
+  // window.
+  LaunchProjectorAppWithFiles({file1, file2});
+  FlushSystemWebAppLaunchesForTesting(profile);
+
+  // Verify that the Projector App is still open.
+  Browser* app_browser2 =
+      FindSystemWebAppBrowser(profile, SystemWebAppType::PROJECTOR);
+  // Launching the app with files should not open a new window.
+  EXPECT_EQ(app_browser1, app_browser2);
+  EXPECT_EQ(chrome::GetTotalBrowserCount(), starting_browser_count + 1);
+
+  content::WebContents* tab =
+      app_browser2->tab_strip_model()->GetActiveWebContents();
+  ASSERT_TRUE(tab);
+  EXPECT_EQ(tab->GetController().GetVisibleEntry()->GetPageType(),
+            content::PAGE_TYPE_NORMAL);
+}
+
 IN_PROC_BROWSER_TEST_F(ProjectorClientTest, MinimizeProjectorApp) {
   auto* profile = browser()->profile();
   SystemWebAppManager::GetForTest(profile)->InstallSystemAppsForTesting();
diff --git a/chrome/browser/ui/ash/projector/projector_utils.cc b/chrome/browser/ui/ash/projector/projector_utils.cc
index 4310463..44b4156 100644
--- a/chrome/browser/ui/ash/projector/projector_utils.cc
+++ b/chrome/browser/ui/ash/projector/projector_utils.cc
@@ -6,10 +6,13 @@
 
 #include "ash/constants/ash_features.h"
 #include "ash/constants/ash_pref_names.h"
+#include "base/files/file_path.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
+#include "chrome/browser/ash/system_web_apps/types/system_web_app_type.h"
 #include "chrome/browser/policy/profile_policy_connector.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
 #include "components/prefs/pref_service.h"
 
 namespace {
@@ -56,3 +59,11 @@
          (ash::features::IsProjectorManagedUserIgnorePolicyEnabled() ||
           profile->GetPrefs()->GetBoolean(ash::prefs::kProjectorAllowByPolicy));
 }
+
+void LaunchProjectorAppWithFiles(std::vector<base::FilePath> files) {
+  auto* profile = ProfileManager::GetActiveUserProfile();
+  ash::SystemAppLaunchParams params;
+  params.launch_paths = std::move(files);
+  ash::LaunchSystemWebAppAsync(profile, ash::SystemWebAppType::PROJECTOR,
+                               params);
+}
diff --git a/chrome/browser/ui/ash/projector/projector_utils.h b/chrome/browser/ui/ash/projector/projector_utils.h
index dc20ffb..28b4d1a 100644
--- a/chrome/browser/ui/ash/projector/projector_utils.h
+++ b/chrome/browser/ui/ash/projector/projector_utils.h
@@ -5,6 +5,12 @@
 #ifndef CHROME_BROWSER_UI_ASH_PROJECTOR_PROJECTOR_UTILS_H_
 #define CHROME_BROWSER_UI_ASH_PROJECTOR_PROJECTOR_UTILS_H_
 
+#include <vector>
+
+namespace base {
+class FilePath;
+}  // namespace base
+
 class Profile;
 
 // Returns whether Projector is allowed for given `profile`.
@@ -13,4 +19,8 @@
 // Returns whether the Projector app is enabled.
 bool IsProjectorAppEnabled(const Profile* profile);
 
+// Launches the Projector SWA with the specified files. If the app is already
+// open, then reuse the existing window.
+void LaunchProjectorAppWithFiles(std::vector<base::FilePath> files);
+
 #endif  // CHROME_BROWSER_UI_ASH_PROJECTOR_PROJECTOR_UTILS_H_
diff --git a/chrome/browser/ui/ash/projector/screencast_manager.cc b/chrome/browser/ui/ash/projector/screencast_manager.cc
index f5926da3..42f7fbf44 100644
--- a/chrome/browser/ui/ash/projector/screencast_manager.cc
+++ b/chrome/browser/ui/ash/projector/screencast_manager.cc
@@ -14,6 +14,7 @@
 #include "base/strings/stringprintf.h"
 #include "chrome/browser/ash/drive/drive_integration_service.h"
 #include "chrome/browser/ui/ash/projector/projector_drivefs_provider.h"
+#include "chrome/browser/ui/ash/projector/projector_utils.h"
 
 namespace ash {
 
@@ -51,7 +52,12 @@
     return;
   }
 
-  // TODO(b/237089852): Launch the file.
+  const base::FilePath& mounted_path =
+      ProjectorDriveFsProvider::GetDriveFsMountPointPath();
+  const base::FilePath& video_path = mounted_path.Append(relative_drivefs_path);
+  // TODO(b/205334821): Find the video duration and validate the media file.
+  LaunchProjectorAppWithFiles({video_path});
+
   std::move(callback).Run(std::move(video), /*error_message=*/std::string());
 }
 
@@ -66,10 +72,8 @@
     ProjectorAppClient::OnGetVideoCallback callback) const {
   auto video = std::make_unique<ProjectorScreencastVideo>();
   video->file_id = video_file_id;
-  // TODO(b/237089852):
-  // 1. Find the video duration.
-  // 2. Launch the app with the video file.
-  // 3. Handle the resource key once LocateFilesByItemIds() supports it.
+  // TODO(b/237089852): Handle the resource key once LocateFilesByItemIds()
+  // supports it.
 
   drive::DriveIntegrationService* integration_service =
       ProjectorDriveFsProvider::GetActiveDriveIntegrationService();
diff --git a/chrome/browser/ui/ash/shelf/browser_app_shelf_controller_browsertest.cc b/chrome/browser/ui/ash/shelf/browser_app_shelf_controller_browsertest.cc
index a433fa3..fa07691 100644
--- a/chrome/browser/ui/ash/shelf/browser_app_shelf_controller_browsertest.cc
+++ b/chrome/browser/ui/ash/shelf/browser_app_shelf_controller_browsertest.cc
@@ -22,20 +22,18 @@
 #include "chrome/browser/apps/app_service/browser_app_instance.h"
 #include "chrome/browser/apps/app_service/browser_app_instance_observer.h"
 #include "chrome/browser/apps/app_service/browser_app_instance_registry.h"
+#include "chrome/browser/ash/crosapi/ash_requires_lacros_browsertestbase.h"
 #include "chrome/browser/ash/crosapi/browser_manager.h"
-#include "chrome/browser/ash/crosapi/crosapi_manager.h"
-#include "chrome/browser/ash/crosapi/test_controller_ash.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/ash/shelf/chrome_shelf_controller.h"
 #include "chrome/browser/ui/ash/shelf/chrome_shelf_controller_test_util.h"
 #include "chrome/browser/ui/browser.h"
+#include "chrome/browser/web_applications/test/app_registration_waiter.h"
 #include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
 #include "chrome/browser/web_applications/web_app_helpers.h"
 #include "chrome/browser/web_applications/web_app_id.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
-#include "chrome/test/base/chromeos/ash_browser_test_starter.h"
-#include "chrome/test/base/in_process_browser_test.h"
 #include "chromeos/crosapi/mojom/test_controller.mojom-test-utils.h"
 #include "components/app_constants/constants.h"
 #include "components/services/app_service/public/cpp/app_registry_cache.h"
@@ -170,7 +168,8 @@
   return os << i.command_id << ", " << i.title;
 }
 
-class BrowserAppShelfControllerBrowserTest : public InProcessBrowserTest {
+class BrowserAppShelfControllerBrowserTest
+    : public crosapi::AshRequiresLacrosBrowserTestBase {
  public:
   BrowserAppShelfControllerBrowserTest() {
     scoped_feature_list_.InitAndEnableFeature(ash::features::kLacrosPrimary);
@@ -193,23 +192,15 @@
                                   GURL(start_url));
   }
 
-  void SetUpInProcessBrowserTestFixture() override {
-    if (!ash_starter_.HasLacrosArgument()) {
-      return;
-    }
-    ASSERT_TRUE(ash_starter_.PrepareEnvironmentForLacros());
-  }
-
   void SetUpOnMainThread() override {
-    if (!ash_starter_.HasLacrosArgument()) {
+    crosapi::AshRequiresLacrosBrowserTestBase::SetUpOnMainThread();
+    if (!HasLacrosArgument()) {
       return;
     }
-    auto* manager = crosapi::CrosapiManager::Get();
-    test_controller_ash_ = std::make_unique<crosapi::TestControllerAsh>();
-    manager->crosapi_ash()->SetTestControllerForTesting(
-        test_controller_ash_.get());
 
-    ash_starter_.StartLacros(this);
+    web_app::AppTypeInitializationWaiter(browser()->profile(),
+                                         apps::AppType::kWeb)
+        .Await();
 
     auto* registry = AppServiceProxy()->BrowserAppInstanceRegistry();
     ASSERT_NE(registry, nullptr);
@@ -232,15 +223,17 @@
   std::string InstallWebApp(const std::string& start_url,
                             apps::WindowMode mode) {
     crosapi::mojom::StandaloneBrowserTestControllerAsyncWaiter waiter(
-        test_controller_ash_->GetStandaloneBrowserTestController().get());
+        GetStandaloneBrowserTestController());
     std::string app_id;
     waiter.InstallWebApp(start_url, mode, &app_id);
 
     // Wait until the app is installed: app service publisher updates may arrive
     // out of order with the web app installation reply, so we wait until the
     // state of the app service is consistent.
-    WAIT_FOR(AppServiceProxy()->AppRegistryCache().GetAppType(app_id) ==
-             apps::AppType::kWeb);
+    web_app::AppRegistrationWaiter(browser()->profile(), app_id).Await();
+    EXPECT_EQ(AppServiceProxy()->AppRegistryCache().GetAppType(app_id),
+              apps::AppType::kWeb);
+
     return app_id;
   }
 
@@ -321,14 +314,11 @@
   }
 
   base::test::ScopedFeatureList scoped_feature_list_;
-  test::AshBrowserTestStarter ash_starter_;
   apps::BrowserAppInstanceRegistry* registry_{nullptr};
-
-  std::unique_ptr<crosapi::TestControllerAsh> test_controller_ash_;
 };
 
 IN_PROC_BROWSER_TEST_F(BrowserAppShelfControllerBrowserTest, TabbedApps) {
-  if (!ash_starter_.HasLacrosArgument()) {
+  if (!HasLacrosArgument()) {
     return;
   }
 
@@ -442,7 +432,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(BrowserAppShelfControllerBrowserTest, WindowedApps) {
-  if (!ash_starter_.HasLacrosArgument()) {
+  if (!HasLacrosArgument()) {
     return;
   }
 
@@ -538,7 +528,7 @@
 
 IN_PROC_BROWSER_TEST_F(BrowserAppShelfControllerBrowserTest,
                        ActivateAndMinimizeTabs) {
-  if (!ash_starter_.HasLacrosArgument()) {
+  if (!HasLacrosArgument()) {
     return;
   }
 
@@ -617,7 +607,7 @@
 
 IN_PROC_BROWSER_TEST_F(BrowserAppShelfControllerBrowserTest,
                        ActivateAndMinimizeWindows) {
-  if (!ash_starter_.HasLacrosArgument()) {
+  if (!HasLacrosArgument()) {
     return;
   }
 
@@ -670,7 +660,7 @@
 
 IN_PROC_BROWSER_TEST_F(BrowserAppShelfControllerBrowserTest,
                        MultipleInstancesShowMenu) {
-  if (!ash_starter_.HasLacrosArgument()) {
+  if (!HasLacrosArgument()) {
     return;
   }
 
diff --git a/chrome/browser/ui/ash/shelf/standalone_browser_extension_app_shelf_item_controller.cc b/chrome/browser/ui/ash/shelf/standalone_browser_extension_app_shelf_item_controller.cc
index ac08746..0cfa9d7 100644
--- a/chrome/browser/ui/ash/shelf/standalone_browser_extension_app_shelf_item_controller.cc
+++ b/chrome/browser/ui/ash/shelf/standalone_browser_extension_app_shelf_item_controller.cc
@@ -41,9 +41,9 @@
     activation_client_observation_.Observe(activation_client);
 
   // Lacros is mutually exclusive with multi-signin. As such, there can only be
-  // a single ash profile active. We grab it from the shelf.
+  // a single ash profile active. We grab it from the profile manager.
   apps::AppServiceProxy* proxy = apps::AppServiceProxyFactory::GetForProfile(
-      ChromeShelfController::instance()->profile());
+      ProfileManager::GetPrimaryUserProfile());
 
   icon_loader_releaser_ = proxy->LoadIconFromIconKey(
       apps::AppType::kStandaloneBrowserChromeApp, shelf_id.app_id,
diff --git a/chrome/browser/ui/global_media_controls/presentation_request_notification_item.cc b/chrome/browser/ui/global_media_controls/presentation_request_notification_item.cc
index 0a9a2acd..03b1fc72 100644
--- a/chrome/browser/ui/global_media_controls/presentation_request_notification_item.cc
+++ b/chrome/browser/ui/global_media_controls/presentation_request_notification_item.cc
@@ -177,19 +177,20 @@
     return;
 
   // If we have metadata from the media session, use that.
-  if (metadata_.has_value()) {
-    view_->UpdateWithMediaMetadata(*metadata_);
-    return;
-  }
+  media_session::MediaMetadata data =
+      metadata_.value_or(media_session::MediaMetadata{});
 
   auto* web_contents = GetWebContentsFromPresentationRequest(request_);
   DCHECK(web_contents);
-
-  media_session::MediaMetadata data;
+  // `request_` has more accurate origin info than `metadata_` e.g. when the
+  // request is from within an iframe.
   data.source_title = url_formatter::FormatOriginForSecurityDisplay(
       request_.frame_origin, url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS);
-  data.artist = web_contents->GetTitle();
-
+  // If not empty, then `metadata_.artist` is likely to contain information
+  // more relevant than the page title.
+  if (data.artist.empty()) {
+    data.artist = web_contents->GetTitle();
+  }
   view_->UpdateWithMediaMetadata(data);
 }
 
diff --git a/chrome/browser/ui/global_media_controls/presentation_request_notification_item_unittest.cc b/chrome/browser/ui/global_media_controls/presentation_request_notification_item_unittest.cc
index 7f6e479..a7fc488 100644
--- a/chrome/browser/ui/global_media_controls/presentation_request_notification_item_unittest.cc
+++ b/chrome/browser/ui/global_media_controls/presentation_request_notification_item_unittest.cc
@@ -141,7 +141,9 @@
   const std::u16string title = u"This is the page title";
   web_contents()->UpdateTitleForEntry(controller().GetVisibleEntry(), title);
 
-  // The item should prioritize Media Session metadata.
+  // The item should prioritize Media Session metadata except for
+  // `source_title`, which should come from the Presentation Request.
+  data.source_title = u"google2.com";
   EXPECT_CALL(view, UpdateWithMediaMetadata(data));
 
   item->SetView(&view);
diff --git a/chrome/browser/ui/toolbar/media_router_contextual_menu.cc b/chrome/browser/ui/toolbar/media_router_contextual_menu.cc
index 26c5b3a0..ffdb6fb 100644
--- a/chrome/browser/ui/toolbar/media_router_contextual_menu.cc
+++ b/chrome/browser/ui/toolbar/media_router_contextual_menu.cc
@@ -77,8 +77,7 @@
   menu_model->AddCheckItemWithStringId(IDC_MEDIA_ROUTER_TOGGLE_MEDIA_REMOTING,
                                        IDS_MEDIA_ROUTER_TOGGLE_MEDIA_REMOTING);
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-  if (!browser_->profile()->IsOffTheRecord() &&
-      browser_->profile()->GetPrefs()->GetBoolean(
+  if (browser_->profile()->GetPrefs()->GetBoolean(
           prefs::kUserFeedbackAllowed)) {
     menu_model->AddSeparator(ui::NORMAL_SEPARATOR);
     menu_model->AddItemWithStringId(
diff --git a/chrome/browser/ui/toolbar/media_router_contextual_menu_unittest.cc b/chrome/browser/ui/toolbar/media_router_contextual_menu_unittest.cc
index 9eb66dc..b03ff349 100644
--- a/chrome/browser/ui/toolbar/media_router_contextual_menu_unittest.cc
+++ b/chrome/browser/ui/toolbar/media_router_contextual_menu_unittest.cc
@@ -161,7 +161,10 @@
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
 // "Report an issue" should be present for normal profiles but not for
 // incognito.
-TEST_F(MediaRouterContextualMenuUnitTest, EnableAndDisableReportIssue) {
+//
+// Disabled <https://crbug.com/1351616>.
+TEST_F(MediaRouterContextualMenuUnitTest,
+       DISABLED_EnableAndDisableReportIssue) {
   MediaRouterContextualMenu menu(browser(), kShownByPolicy, &observer_);
   EXPECT_TRUE(
       menu.CreateMenuModel()
diff --git a/chrome/browser/ui/views/global_media_controls/media_toolbar_button_contextual_menu.cc b/chrome/browser/ui/views/global_media_controls/media_toolbar_button_contextual_menu.cc
index f093a6f..68b2431 100644
--- a/chrome/browser/ui/views/global_media_controls/media_toolbar_button_contextual_menu.cc
+++ b/chrome/browser/ui/views/global_media_controls/media_toolbar_button_contextual_menu.cc
@@ -50,8 +50,7 @@
       IDS_MEDIA_TOOLBAR_CONTEXT_SHOW_OTHER_SESSIONS);
 
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-  if (!browser_->profile()->IsOffTheRecord() &&
-      browser_->profile()->GetPrefs()->GetBoolean(
+  if (browser_->profile()->GetPrefs()->GetBoolean(
           prefs::kUserFeedbackAllowed)) {
     menu_model->AddItemWithStringId(
         IDC_MEDIA_TOOLBAR_CONTEXT_REPORT_CAST_ISSUE,
diff --git a/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc b/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc
index 32def367..fb38b53 100644
--- a/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc
+++ b/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc
@@ -1321,32 +1321,115 @@
   CheckHeuristicsUkmRecord({kNavigatedUrl, {true, false, false}}, 1);
 }
 
-// Test that a Safety Tips is not shown and metrics are recorded when
+// Test that a Safety Tip is shown and metrics are recorded when
 // a combo squatting url is flagged with a hard-coded brand name.
+// This test case trigger `keyword` heuristic as well because of `google`
+// in the URL.
+// TODO(crbug.com/1343630): keyword (embedded keyword) heuristic should
+// be removed from the code including CheckHeuristicsUkmRecord.
 IN_PROC_BROWSER_TEST_F(SafetyTipPageInfoBubbleViewBrowserTest,
-                       DoesntTriggerOnComboSquatting) {
+                       TriggerOnComboSquatting) {
   base::HistogramTester histograms;
   const GURL kNavigatedUrl = GetURL("google-login.com");
   SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
 
   NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
-  EXPECT_FALSE(IsUIShowing());
 
   histograms.ExpectTotalCount(lookalikes::kHistogramName, 1);
   histograms.ExpectBucketCount(lookalikes::kHistogramName,
                                NavigationSuggestionEvent::kComboSquatting, 1);
 
-  // TODO(crbug.com/1343630): keyword (embedded keyword) heuristic should
-  // be removed from the code. The second `false` value in
-  // the input of CheckHeuristicsUkmRecord is correlated to this heuristic.
+  // Make sure that the UI is now showing, and that no UKM data has been
+  // recorded yet.
+  ASSERT_TRUE(IsUIShowing());
+  CheckRecordedHeuristicsUkmCount(0);
+
+  // Once we close the warning, ensure that the UI is no longer showing, and
+  // that UKM data has now been recorded.
+  CloseWarningLeaveSite(browser());
+  ASSERT_FALSE(IsUIShowing());
+
   CheckRecordedHeuristicsUkmCount(1);
-  CheckHeuristicsUkmRecord({kNavigatedUrl, {false, false, true}}, 0);
+  // Boolean values are /*blocklist*/ /*lookalike*/ /*keywords*/
+  // This test case expects triggering with lookalike heuristic and
+  // keywords heuristic.
+  CheckHeuristicsUkmRecord({kNavigatedUrl, {false, true, true}}, 0);
+
+  // Navigate to the same site again, but close the warning with an ignore
+  // instead of an accept. This should still record UKM data.
+  NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
+
+  ASSERT_TRUE(IsUIShowing());
+
+  // Make sure the already collected UKM data still exists.
+  CheckRecordedHeuristicsUkmCount(1);
+  CheckHeuristicsUkmRecord({kNavigatedUrl, {false, true, true}}, 0);
+
+  CloseWarningIgnore(views::Widget::ClosedReason::kCloseButtonClicked);
+  ASSERT_FALSE(IsUIShowing());
+  CheckRecordedHeuristicsUkmCount(2);
+  CheckHeuristicsUkmRecord({kNavigatedUrl, {false, true, true}}, 0);
+  CheckHeuristicsUkmRecord({kNavigatedUrl, {false, true, true}}, 1);
 }
 
-// Test that a Safety Tips is not shown and metrics are recorded when
-// a combo squatting url is flagged with a brand name from engaged sites.
+// Test that a Safety Tip is shown and metrics are recorded when
+// a combo squatting url is flagged with a hard-coded brand name.
+// In contrast with `TriggerOnComboSquatting`, this test case only
+// triggers `lookalike` heuristic.
 IN_PROC_BROWSER_TEST_F(SafetyTipPageInfoBubbleViewBrowserTest,
-                       DoesntTriggerOnComboSquattingSiteEngagement) {
+                       TriggerOnlyOnComboSquatting) {
+  base::HistogramTester histograms;
+  const GURL kNavigatedUrl = GetURL("costco-login.com");
+  SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
+
+  NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
+
+  histograms.ExpectTotalCount(lookalikes::kHistogramName, 1);
+  histograms.ExpectBucketCount(lookalikes::kHistogramName,
+                               NavigationSuggestionEvent::kComboSquatting, 1);
+
+  // Make sure that the UI is now showing, and that no UKM data has been
+  // recorded yet.
+  ASSERT_TRUE(IsUIShowing());
+  CheckRecordedHeuristicsUkmCount(0);
+
+  // Once we close the warning, ensure that the UI is no longer showing, and
+  // that UKM data has now been recorded.
+  CloseWarningLeaveSite(browser());
+  ASSERT_FALSE(IsUIShowing());
+
+  CheckRecordedHeuristicsUkmCount(1);
+  // Boolean values are /*blocklist*/ /*lookalike*/ /*keywords*/
+  CheckHeuristicsUkmRecord({kNavigatedUrl, {false, true, false}}, 0);
+
+  // Navigate to the same site again, but close the warning with an ignore
+  // instead of an accept. This should still record UKM data.
+  NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
+
+  ASSERT_TRUE(IsUIShowing());
+
+  // Make sure the already collected UKM data still exists.
+  CheckRecordedHeuristicsUkmCount(1);
+  CheckHeuristicsUkmRecord({kNavigatedUrl, {false, true, false}}, 0);
+
+  CloseWarningIgnore(views::Widget::ClosedReason::kCloseButtonClicked);
+  ASSERT_FALSE(IsUIShowing());
+  CheckRecordedHeuristicsUkmCount(2);
+  // Boolean values are /*blocklist*/ /*lookalike*/ /*keywords*/
+  // The last `false` is different from the previous test because
+  // `keywords heuristic` is not triggered by this test case.
+  CheckHeuristicsUkmRecord({kNavigatedUrl, {false, true, false}}, 0);
+  CheckHeuristicsUkmRecord({kNavigatedUrl, {false, true, false}}, 1);
+  // TODO(crbug.com/1343630): keyword (embedded keyword) heuristic should
+  // be removed from the code including CheckHeuristicsUkmRecord.
+}
+
+// Test that a Safety Tip is shown and metrics are recorded when
+// a combo squatting url is flagged with a brand name from engaged sites.
+// In this test case, engaged site is not one of the keywords in `keyword`
+// heuristic.
+IN_PROC_BROWSER_TEST_F(SafetyTipPageInfoBubbleViewBrowserTest,
+                       TriggerOnComboSquattingSiteEngagement) {
   base::HistogramTester histograms;
   const GURL kEngagedUrl = GetURL("example.com");
   const GURL kNavigatedUrl = GetURL("example-login.com");
@@ -1354,17 +1437,41 @@
   SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
 
   NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
-  EXPECT_FALSE(IsUIShowing());
 
   histograms.ExpectTotalCount(lookalikes::kHistogramName, 1);
   histograms.ExpectBucketCount(
       lookalikes::kHistogramName,
       NavigationSuggestionEvent::kComboSquattingSiteEngagement, 1);
 
-  // Heuristics with no UI don't record Safety Tips UKM.
+  // Make sure that the UI is now showing, and that no UKM data has been
+  // recorded yet.
+  ASSERT_TRUE(IsUIShowing());
   CheckRecordedHeuristicsUkmCount(0);
-  // TODO(crbug.com/1337475): Add CheckHeuristicsUkmRecord after showing the
-  // safety tip for this heuristic.
+
+  // Once we close the warning, ensure that the UI is no longer showing, and
+  // that UKM data has now been recorded.
+  CloseWarningLeaveSite(browser());
+  ASSERT_FALSE(IsUIShowing());
+
+  CheckRecordedHeuristicsUkmCount(1);
+  // Boolean values are /*blocklist*/ /*lookalike*/ /*keywords*/
+  CheckHeuristicsUkmRecord({kNavigatedUrl, {false, true, false}}, 0);
+
+  // Navigate to the same site again, but close the warning with an ignore
+  // instead of an accept. This should still record UKM data.
+  NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
+
+  ASSERT_TRUE(IsUIShowing());
+
+  // Make sure the already collected UKM data still exists.
+  CheckRecordedHeuristicsUkmCount(1);
+  CheckHeuristicsUkmRecord({kNavigatedUrl, {false, true, false}}, 0);
+
+  CloseWarningIgnore(views::Widget::ClosedReason::kCloseButtonClicked);
+  ASSERT_FALSE(IsUIShowing());
+  CheckRecordedHeuristicsUkmCount(2);
+  CheckHeuristicsUkmRecord({kNavigatedUrl, {false, true, false}}, 0);
+  CheckHeuristicsUkmRecord({kNavigatedUrl, {false, true, false}}, 1);
 }
 
 // Tests for Digital Asset Links for lookalike checks.
diff --git a/chrome/browser/ui/views/tabs/fake_tab_slot_controller.cc b/chrome/browser/ui/views/tabs/fake_tab_slot_controller.cc
index 157c1527..f432611 100644
--- a/chrome/browser/ui/views/tabs/fake_tab_slot_controller.cc
+++ b/chrome/browser/ui/views/tabs/fake_tab_slot_controller.cc
@@ -106,10 +106,6 @@
   return absl::nullopt;
 }
 
-gfx::Rect FakeTabSlotController::GetTabAnimationTargetBounds(const Tab* tab) {
-  return tab->bounds();
-}
-
 std::u16string FakeTabSlotController::GetAccessibleTabName(
     const Tab* tab) const {
   return std::u16string();
diff --git a/chrome/browser/ui/views/tabs/fake_tab_slot_controller.h b/chrome/browser/ui/views/tabs/fake_tab_slot_controller.h
index 7a5c364..82761af2 100644
--- a/chrome/browser/ui/views/tabs/fake_tab_slot_controller.h
+++ b/chrome/browser/ui/views/tabs/fake_tab_slot_controller.h
@@ -82,7 +82,6 @@
   SkColor GetTabForegroundColor(TabActive active) const override;
   absl::optional<int> GetCustomBackgroundId(
       BrowserFrameActiveState active_state) const override;
-  gfx::Rect GetTabAnimationTargetBounds(const Tab* tab) override;
   std::u16string GetAccessibleTabName(const Tab* tab) const override;
   float GetHoverOpacityForTab(float range_parameter) const override;
   float GetHoverOpacityForRadialHighlight() const override;
diff --git a/chrome/browser/ui/views/tabs/tab_container.h b/chrome/browser/ui/views/tabs/tab_container.h
index a7063da3..4c83fe7c 100644
--- a/chrome/browser/ui/views/tabs/tab_container.h
+++ b/chrome/browser/ui/views/tabs/tab_container.h
@@ -18,7 +18,6 @@
 #include "ui/views/view_model.h"
 
 class TabStrip;
-class TabStripLayoutHelper;
 
 // A View that contains a sequence of Tabs for the TabStrip.
 
@@ -43,6 +42,9 @@
   virtual void MoveTab(int from_model_index, int to_model_index) = 0;
   virtual void RemoveTab(int index, bool was_active) = 0;
   virtual void SetTabPinned(int model_index, TabPinned pinned) = 0;
+  // Changes the active tab from |prev_active_index| to |new_active_index|.
+  virtual void SetActiveTab(absl::optional<size_t> prev_active_index,
+                            absl::optional<size_t> new_active_index) = 0;
 
   // `view` is no longer being dragged. This TabContainer takes ownership of it
   // in the view hierarchy.
@@ -67,16 +69,13 @@
   virtual void NotifyTabGroupEditorBubbleClosed() = 0;
 
   virtual int GetModelIndexOf(const TabSlotView* slot_view) const = 0;
-
-  // TODO (1346023): Move callers down into TabContainer so this
-  // encapsulation-breaking getter can be removed.
-  virtual views::ViewModelT<Tab>* GetTabsViewModel() = 0;
-
-  // Returns the tab at `index` from the viewmodel.
   virtual Tab* GetTabAtModelIndex(int index) const = 0;
-
   virtual int GetTabCount() const = 0;
 
+  // Returns the model index of the first tab after (or including) `tab` which
+  // is not closing.
+  virtual int GetModelIndexOfFirstNonClosingTab(Tab* tab) const = 0;
+
   virtual void UpdateHoverCard(
       Tab* tab,
       TabSlotController::HoverCardUpdateType update_type) = 0;
@@ -130,12 +129,22 @@
 
   virtual bool InTabClose() = 0;
 
-  // TODO (1346023): Move callers down into TabContainer so these
-  // encapsulation-breaking getters can be removed.
-  virtual TabStripLayoutHelper* GetLayoutHelper() const = 0;
   virtual std::map<tab_groups::TabGroupId, std::unique_ptr<TabGroupViews>>&
   GetGroupViews() = 0;
-  virtual views::BoundsAnimator& GetBoundsAnimator() = 0;
+
+  // Returns the current width of the active tab.
+  virtual int GetActiveTabWidth() const = 0;
+
+  // Returns the current width of inactive tabs.
+  virtual int GetInactiveTabWidth() const = 0;
+
+  // Returns ideal bounds for the tab at `model_index` in this TabContainer's
+  // coordinate space.
+  virtual gfx::Rect GetIdealBounds(int model_index) const = 0;
+
+  // Returns ideal bounds for the group header associated with `group` in this
+  // TabContainer's coordinate space.
+  virtual gfx::Rect GetIdealBounds(tab_groups::TabGroupId group) const = 0;
 
   // Views::View:
   // We're changing visibility of this to public for TabContainer.
diff --git a/chrome/browser/ui/views/tabs/tab_container_impl.cc b/chrome/browser/ui/views/tabs/tab_container_impl.cc
index 12d6993..da794a9 100644
--- a/chrome/browser/ui/views/tabs/tab_container_impl.cc
+++ b/chrome/browser/ui/views/tabs/tab_container_impl.cc
@@ -184,9 +184,9 @@
   SetEventTargeter(std::make_unique<views::ViewTargeter>(this));
 
   if (!gfx::Animation::ShouldRenderRichAnimation())
-    GetBoundsAnimator().SetAnimationDuration(base::TimeDelta());
+    bounds_animator_.SetAnimationDuration(base::TimeDelta());
 
-  GetBoundsAnimator().AddObserver(this);
+  bounds_animator_.AddObserver(this);
 
   if (g_drop_indicator_width == 0) {
     // Direction doesn't matter, both images are the same size.
@@ -279,6 +279,11 @@
   }
 }
 
+void TabContainerImpl::SetActiveTab(absl::optional<size_t> prev_active_index,
+                                    absl::optional<size_t> new_active_index) {
+  layout_helper_->SetActiveTab(prev_active_index, new_active_index);
+}
+
 void TabContainerImpl::StoppedDraggingView(TabSlotView* view) {
   AddChildView(view);
   Tab* tab = views::AsViewClass<Tab>(view);
@@ -439,10 +444,6 @@
   return index.has_value() ? static_cast<int>(index.value()) : -1;
 }
 
-views::ViewModelT<Tab>* TabContainerImpl::GetTabsViewModel() {
-  return &tabs_view_model_;
-}
-
 Tab* TabContainerImpl::GetTabAtModelIndex(int index) const {
   return tabs_view_model_.view_at(index);
 }
@@ -451,6 +452,25 @@
   return tabs_view_model_.view_size();
 }
 
+int TabContainerImpl::GetModelIndexOfFirstNonClosingTab(Tab* tab) const {
+  if (tab->closing()) {
+    // If the tab is already closing, close the next tab. We do this so that the
+    // user can rapidly close tabs by clicking the close button and not have
+    // the animations interfere with that.
+    std::vector<Tab*> all_tabs = layout_helper_->GetTabs();
+    auto it = std::find(all_tabs.begin(), all_tabs.end(), tab);
+    while (it < all_tabs.end() && (*it)->closing()) {
+      it++;
+    }
+
+    if (it == all_tabs.end())
+      return TabStripModel::kNoTab;
+    tab = *it;
+  }
+
+  return GetModelIndexOf(tab);
+}
+
 void TabContainerImpl::UpdateHoverCard(
     Tab* tab,
     TabSlotController::HoverCardUpdateType update_type) {
@@ -607,17 +627,25 @@
   return in_tab_close_;
 }
 
-TabStripLayoutHelper* TabContainerImpl::GetLayoutHelper() const {
-  return layout_helper_.get();
-}
-
 std::map<tab_groups::TabGroupId, std::unique_ptr<TabGroupViews>>&
 TabContainerImpl::GetGroupViews() {
   return group_views_;
 }
 
-views::BoundsAnimator& TabContainerImpl::GetBoundsAnimator() {
-  return bounds_animator_;
+int TabContainerImpl::GetActiveTabWidth() const {
+  return layout_helper_->active_tab_width();
+}
+
+int TabContainerImpl::GetInactiveTabWidth() const {
+  return layout_helper_->inactive_tab_width();
+}
+
+gfx::Rect TabContainerImpl::GetIdealBounds(int model_index) const {
+  return tabs_view_model_.ideal_bounds(model_index);
+}
+
+gfx::Rect TabContainerImpl::GetIdealBounds(tab_groups::TabGroupId group) const {
+  return layout_helper_->group_header_ideal_bounds().at(group);
 }
 
 void TabContainerImpl::Layout() {
@@ -894,6 +922,10 @@
   arrow_window_ = nullptr;
 }
 
+views::ViewModelT<Tab>* TabContainerImpl::GetTabsViewModel() {
+  return &tabs_view_model_;
+}
+
 void TabContainerImpl::UpdateIdealBounds() {
   if (GetTabCount() == 0)
     return;  // Should only happen during creation/destruction, ignore.
diff --git a/chrome/browser/ui/views/tabs/tab_container_impl.h b/chrome/browser/ui/views/tabs/tab_container_impl.h
index 5f37ca9..408a2a4 100644
--- a/chrome/browser/ui/views/tabs/tab_container_impl.h
+++ b/chrome/browser/ui/views/tabs/tab_container_impl.h
@@ -57,6 +57,8 @@
   void MoveTab(int from_model_index, int to_model_index) override;
   void RemoveTab(int index, bool was_active) override;
   void SetTabPinned(int model_index, TabPinned pinned) override;
+  void SetActiveTab(absl::optional<size_t> prev_active_index,
+                    absl::optional<size_t> new_active_index) override;
 
   void StoppedDraggingView(TabSlotView* view) override;
 
@@ -73,12 +75,9 @@
   void NotifyTabGroupEditorBubbleClosed() override;
 
   int GetModelIndexOf(const TabSlotView* slot_view) const override;
-
-  views::ViewModelT<Tab>* GetTabsViewModel() override;
-
   Tab* GetTabAtModelIndex(int index) const override;
-
   int GetTabCount() const override;
+  int GetModelIndexOfFirstNonClosingTab(Tab* tab) const override;
 
   void UpdateHoverCard(
       Tab* tab,
@@ -108,12 +107,14 @@
 
   bool InTabClose() override;
 
-  TabStripLayoutHelper* GetLayoutHelper() const override;
-
   std::map<tab_groups::TabGroupId, std::unique_ptr<TabGroupViews>>&
   GetGroupViews() override;
 
-  views::BoundsAnimator& GetBoundsAnimator() override;
+  int GetActiveTabWidth() const override;
+  int GetInactiveTabWidth() const override;
+
+  gfx::Rect GetIdealBounds(int model_index) const override;
+  gfx::Rect GetIdealBounds(tab_groups::TabGroupId group) const override;
 
   // views::View
   void Layout() override;
@@ -185,6 +186,8 @@
 
   class RemoveTabDelegate;
 
+  views::ViewModelT<Tab>* GetTabsViewModel();
+
   // Generates and sets the ideal bounds for each of the tabs as well as the new
   // tab button. Note: Does not animate the tabs to those bounds so callers can
   // use this information for other purposes - see AnimateToIdealBounds.
diff --git a/chrome/browser/ui/views/tabs/tab_container_unittest.cc b/chrome/browser/ui/views/tabs/tab_container_unittest.cc
index c73b2a0c..f130c6a 100644
--- a/chrome/browser/ui/views/tabs/tab_container_unittest.cc
+++ b/chrome/browser/ui/views/tabs/tab_container_unittest.cc
@@ -189,8 +189,8 @@
     tab_strip_controller_->RemoveTabFromGroup(model_index);
 
     bool group_is_empty = true;
-    for (Tab* tab : tab_container_->GetLayoutHelper()->GetTabs()) {
-      if (tab->group() == old_group)
+    for (int i = 0; i < tab_container_->GetTabCount(); i++) {
+      if (tab_container_->GetTabAtModelIndex(i)->group() == old_group)
         group_is_empty = false;
     }
 
@@ -304,8 +304,7 @@
 
   // Create just enough tabs so tabs are not full size.
   const int standard_width = TabStyleViews::GetStandardWidth();
-  while (tab_container_->GetLayoutHelper()->active_tab_width() ==
-         standard_width) {
+  while (tab_container_->GetActiveTabWidth() == standard_width) {
     AddTab(0);
     tab_container_->CompleteAnimationAndLayout();
   }
@@ -322,15 +321,13 @@
   // constraining tab widths to below full size.
   tab_container_->RemoveTab(tab_container_->GetTabCount() - 2, false);
   tab_container_->CompleteAnimationAndLayout();
-  ASSERT_LT(tab_container_->GetLayoutHelper()->active_tab_width(),
-            standard_width);
+  ASSERT_LT(tab_container_->GetActiveTabWidth(), standard_width);
 
   // Close the last tab; tab closing mode should allow tabs to resize to full
   // size.
   tab_container_->RemoveTab(tab_container_->GetTabCount() - 1, false);
   tab_container_->CompleteAnimationAndLayout();
-  EXPECT_EQ(tab_container_->GetLayoutHelper()->active_tab_width(),
-            standard_width);
+  EXPECT_EQ(tab_container_->GetActiveTabWidth(), standard_width);
 }
 
 // Verifies child view order matches model order.
diff --git a/chrome/browser/ui/views/tabs/tab_slot_controller.h b/chrome/browser/ui/views/tabs/tab_slot_controller.h
index 0daeedd..3dc42d8c 100644
--- a/chrome/browser/ui/views/tabs/tab_slot_controller.h
+++ b/chrome/browser/ui/views/tabs/tab_slot_controller.h
@@ -21,7 +21,6 @@
 
 namespace gfx {
 class Point;
-class Rect;
 }  // namespace gfx
 namespace tab_groups {
 enum class TabGroupColorId;
@@ -196,11 +195,6 @@
   virtual absl::optional<int> GetCustomBackgroundId(
       BrowserFrameActiveState active_state) const = 0;
 
-  // If the given tab is animating to its target destination, this returns the
-  // target bounds. If the tab isn't moving this will return the current bounds
-  // of the given tab.
-  virtual gfx::Rect GetTabAnimationTargetBounds(const Tab* tab) = 0;
-
   // Returns the accessible tab name for this tab.
   virtual std::u16string GetAccessibleTabName(const Tab* tab) const = 0;
 
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc
index 7325943..940b036 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip.cc
@@ -318,7 +318,7 @@
   }
 
   int GetPinnedTabCount() const override {
-    return tab_strip_->GetPinnedTabCount();
+    return tab_strip_->GetModelPinnedTabCount();
   }
 
   TabGroupHeader* GetTabGroupHeader(
@@ -456,7 +456,8 @@
     }
 
     if (!index) {
-      const int last_tab_right = ideal_bounds(GetTabCount() - 1).right();
+      const int last_tab_right =
+          tab_strip_->tab_container_->GetIdealBounds(GetTabCount() - 1).right();
       index = (dragged_bounds.right() > last_tab_right) ? GetTabCount() : 0;
     }
 
@@ -562,10 +563,11 @@
         tab_strip_->tab_container_->GetGroupViews()[header->group().value()]
             ->highlight()
             ->SetVisible(false);
-        ideal_bounds = tab_strip_->ideal_bounds(header->group().value());
-      } else {
         ideal_bounds =
-            tab_strip_->ideal_bounds(tab_strip_->GetModelIndexOf(view));
+            tab_strip_->tab_container_->GetIdealBounds(header->group().value());
+      } else {
+        ideal_bounds = tab_strip_->tab_container_->GetIdealBounds(
+            tab_strip_->GetModelIndexOf(view));
       }
 
       bounds_animator_.AnimateViewTo(
@@ -683,12 +685,6 @@
     const raw_ref<TabSlotView> slot_view_;
   };
 
-  gfx::Rect ideal_bounds(int i) const { return tab_strip_->ideal_bounds(i); }
-
-  gfx::Rect ideal_bounds(tab_groups::TabGroupId group) const {
-    return tab_strip_->ideal_bounds(group);
-  }
-
   // Determines the index to move the dragged tabs to. The dragged tabs must
   // already be in the tabstrip. |dragged_bounds| is the union of the bounds
   // of the dragged tabs and group header, if any. |first_dragged_tab_index| is
@@ -807,7 +803,8 @@
     const int tab_overlap = TabStyle::GetTabOverlap();
 
     // We'll insert just right of the tab at |candidate_index| - 1.
-    int ideal_x = ideal_bounds(candidate_index - 1).right();
+    int ideal_x =
+        tab_strip_->tab_container_->GetIdealBounds(candidate_index - 1).right();
 
     // If the dragged tabs are currently left of |candidate_index|, moving
     // them to |candidate_index| would move the tab at |candidate_index| - 1
@@ -850,19 +847,6 @@
     return 0;
   }
 
-  // Sets the ideal bounds x-coordinates to |positions|.
-  void SetIdealBoundsFromPositions(const std::vector<int>& positions) {
-    if (static_cast<size_t>(GetTabCount()) != positions.size())
-      return;
-
-    for (int i = 0; i < GetTabCount(); ++i) {
-      gfx::Rect bounds(ideal_bounds(i));
-      bounds.set_x(positions[i]);
-      tab_strip_->tab_container_->GetTabsViewModel()->set_ideal_bounds(i,
-                                                                       bounds);
-    }
-  }
-
   const raw_ptr<TabStrip> tab_strip_;
 
   // Responsible for animating tabs during drag sessions.
@@ -1155,8 +1139,8 @@
             : CloseTabSource::CLOSE_TAB_FROM_TOUCH;
 
     tab_container_->EnterTabClosingMode(
-        ideal_bounds(GetModelCount() - 1).right() - current_group_width +
-            collapsed_group_width,
+        tab_container_->GetIdealBounds(GetModelCount() - 1).right() -
+            current_group_width + collapsed_group_width,
         source);
   } else {
     tab_container_->ExitTabClosingMode();
@@ -1235,8 +1219,8 @@
     }
 
     new_active_tab->ActiveStateChanged();
-    tab_container_->GetLayoutHelper()->SetActiveTab(selected_tabs_.active(),
-                                                    new_selection.active());
+    tab_container_->SetActiveTab(selected_tabs_.active(),
+                                 new_selection.active());
     if (base::FeatureList::IsEnabled(features::kScrollableTabStrip)) {
       tab_container_->ScrollTabToVisible(new_selection.active().value());
     }
@@ -1318,12 +1302,18 @@
   return controller_->GetCount();
 }
 
-TabDragContext* TabStrip::GetDragContext() {
-  return &*drag_context_;
+int TabStrip::GetModelPinnedTabCount() const {
+  for (size_t i = 0; i < static_cast<size_t>(controller_->GetCount()); ++i) {
+    if (!controller_->IsTabPinned(static_cast<int>(i)))
+      return static_cast<int>(i);
+  }
+
+  // All tabs are pinned.
+  return controller_->GetCount();
 }
 
-int TabStrip::GetPinnedTabCount() const {
-  return tab_container_->GetLayoutHelper()->GetPinnedTabCount();
+TabDragContext* TabStrip::GetDragContext() {
+  return &*drag_context_;
 }
 
 bool TabStrip::IsAnimating() const {
@@ -1449,22 +1439,10 @@
 }
 
 void TabStrip::CloseTab(Tab* tab, CloseTabSource source) {
-  if (tab->closing()) {
-    // If the tab is already closing, close the next tab. We do this so that the
-    // user can rapidly close tabs by clicking the close button and not have
-    // the animations interfere with that.
-    std::vector<Tab*> all_tabs = tab_container_->GetLayoutHelper()->GetTabs();
-    auto it = std::find(all_tabs.begin(), all_tabs.end(), tab);
-    while (it < all_tabs.end() && (*it)->closing()) {
-      it++;
-    }
+  int index_to_close = tab_container_->GetModelIndexOfFirstNonClosingTab(tab);
 
-    if (it == all_tabs.end())
-      return;
-    tab = *it;
-  }
-
-  CloseTabInternal(GetModelIndexOf(tab), source);
+  if (IsValidModelIndex(index_to_close))
+    CloseTabInternal(index_to_close, source);
 }
 
 void TabStrip::ToggleTabAudioMute(Tab* tab) {
@@ -1758,10 +1736,6 @@
              : absl::nullopt;
 }
 
-gfx::Rect TabStrip::GetTabAnimationTargetBounds(const Tab* tab) {
-  return tab_container_->GetBoundsAnimator().GetTargetBounds(tab);
-}
-
 float TabStrip::GetHoverOpacityForTab(float range_parameter) const {
   return gfx::Tween::FloatValueBetween(range_parameter, hover_opacity_min_,
                                        hover_opacity_max_);
@@ -1962,11 +1936,11 @@
 }
 
 int TabStrip::GetActiveTabWidth() const {
-  return tab_container_->GetLayoutHelper()->active_tab_width();
+  return tab_container_->GetActiveTabWidth();
 }
 
 int TabStrip::GetInactiveTabWidth() const {
-  return tab_container_->GetLayoutHelper()->inactive_tab_width();
+  return tab_container_->GetInactiveTabWidth();
 }
 
 const Tab* TabStrip::GetLastVisibleTab() const {
@@ -2185,11 +2159,6 @@
   parent_->ShowContextMenuForTab(tab, point, source_type);
 }
 
-const gfx::Rect& TabStrip::ideal_bounds(tab_groups::TabGroupId group) const {
-  return tab_container_->GetLayoutHelper()->group_header_ideal_bounds().at(
-      group);
-}
-
 void TabStrip::OnMouseEntered(const ui::MouseEvent& event) {
   mouse_entered_tabstrip_time_ = base::TimeTicks::Now();
 }
@@ -2231,8 +2200,11 @@
 }
 
 void TabStrip::OnViewFocused(views::View* observed_view) {
-  auto index =
-      tab_container_->GetTabsViewModel()->GetIndexOfView(observed_view);
+  TabSlotView* slot_view = views::AsViewClass<TabSlotView>(observed_view);
+  if (!slot_view)
+    return;
+
+  absl::optional<int> index = GetModelIndexOf(slot_view);
   if (index.has_value())
     controller_->OnKeyboardFocusedTabChanged(index);
 }
@@ -2275,7 +2247,7 @@
 ADD_PROPERTY_METADATA(int, BackgroundOffset)
 ADD_READONLY_PROPERTY_METADATA(int, TabCount)
 ADD_READONLY_PROPERTY_METADATA(int, ModelCount)
-ADD_READONLY_PROPERTY_METADATA(int, PinnedTabCount)
+ADD_READONLY_PROPERTY_METADATA(int, ModelPinnedTabCount)
 ADD_READONLY_PROPERTY_METADATA(absl::optional<int>, FocusedTabIndex)
 ADD_READONLY_PROPERTY_METADATA(int, StrokeThickness)
 ADD_READONLY_PROPERTY_METADATA(SkColor,
diff --git a/chrome/browser/ui/views/tabs/tab_strip.h b/chrome/browser/ui/views/tabs/tab_strip.h
index baf088f0..7a9effd 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.h
+++ b/chrome/browser/ui/views/tabs/tab_strip.h
@@ -206,13 +206,13 @@
   // Cover method for TabStripController::GetCount.
   int GetModelCount() const;
 
+  // Returns the number of pinned tabs.
+  int GetModelPinnedTabCount() const;
+
   TabStripController* controller() const { return controller_.get(); }
 
   TabDragContext* GetDragContext();
 
-  // Returns the number of pinned tabs.
-  int GetPinnedTabCount() const;
-
   // Returns true if Tabs in this TabStrip are currently changing size or
   // position.
   bool IsAnimating() const;
@@ -307,7 +307,6 @@
   std::u16string GetAccessibleTabName(const Tab* tab) const override;
   absl::optional<int> GetCustomBackgroundId(
       BrowserFrameActiveState active_state) const override;
-  gfx::Rect GetTabAnimationTargetBounds(const Tab* tab) override;
   float GetHoverOpacityForTab(float range_parameter) const override;
   float GetHoverOpacityForRadialHighlight() const override;
   std::u16string GetGroupTitle(
@@ -409,14 +408,6 @@
   // |offset| and moves it if possible.
   void ShiftGroupRelative(const tab_groups::TabGroupId& group, int offset);
 
-  // Retrieves the ideal bounds for the Tab at the specified index.
-  const gfx::Rect& ideal_bounds(int tab_data_index) const {
-    return tab_container_->GetTabsViewModel()->ideal_bounds(tab_data_index);
-  }
-
-  // Retrieves the ideal bounds for the Tab Group Header at the specified group.
-  const gfx::Rect& ideal_bounds(tab_groups::TabGroupId group) const;
-
   // views::View:
   void OnMouseEntered(const ui::MouseEvent& event) override;
   void OnMouseExited(const ui::MouseEvent& event) override;
diff --git a/chrome/browser/ui/views/tabs/tab_strip_unittest.cc b/chrome/browser/ui/views/tabs/tab_strip_unittest.cc
index b812a68..dc4c3af 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_unittest.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip_unittest.cc
@@ -172,10 +172,6 @@
     tab_strip_parent_->Layout();
   }
 
-  views::BoundsAnimator* bounds_animator() {
-    return &tab_strip_->tab_container_->GetBoundsAnimator();
-  }
-
   int GetActiveTabWidth() { return tab_strip_->GetActiveTabWidth(); }
   int GetInactiveTabWidth() { return tab_strip_->GetInactiveTabWidth(); }
 
diff --git a/chrome/browser/ui/views/tabs/tab_style_views.cc b/chrome/browser/ui/views/tabs/tab_style_views.cc
index 385ed3e7..0499d76e 100644
--- a/chrome/browser/ui/views/tabs/tab_style_views.cc
+++ b/chrome/browser/ui/views/tabs/tab_style_views.cc
@@ -94,12 +94,6 @@
   float GetHoverInterpolatedSeparatorOpacity(bool for_layout,
                                              const Tab* other_tab) const;
 
-  // Helper that returns an interpolated opacity if the tab is
-  // mid-bounds-animation. Used only for the first and last tabs, since those
-  // are the primary cases where separator opacity is likely to change during
-  // a bounds animation.
-  float GetBoundsInterpolatedSeparatorOpacity() const;
-
   // Returns whether we shoould extend the hit test region for Fitts' Law.
   bool ShouldExtendHitTest() const;
 
@@ -668,10 +662,6 @@
     // sufficient contrast against the empty gap, so this contingency isn't
     // needed. Therefore, the separator is hidden only for tabs with visible
     // backgrounds.
-    // TODO(crbug.com/876599): This value should be interpolated because the
-    // separator may be going from shown (the default) to hidden (when animating
-    // past an empty gap like this). This should behave similarly to
-    // GetBoundsInterpolatedSeparatorOpacity(), but not just for the end slots.
     if (adjacent_tab->IsSelected())
       return 0.0f;
   }
@@ -686,11 +676,11 @@
   }
 
   // If the tab does not have a visible background and is in the first slot,
-  // make sure the opacity is interpolated correctly when it animates into
-  // position, since the separator is likely going from shown (the default) to
-  // hidden (in the first slot). See GetBoundsInterpolatedSeparatorOpacity().
+  // do not show the separator. This once was interpolated based on the tab's
+  // progress through animating into this slot, but that was removed because the
+  // visual impact was minimal and
   if (!adjacent_tab && leading)
-    return GetBoundsInterpolatedSeparatorOpacity();
+    return 0.0f;
 
   return GetHoverInterpolatedSeparatorOpacity(for_layout, adjacent_tab);
 }
@@ -713,22 +703,6 @@
   return 1.0f - std::max(hover_value, adjacent_hover_value(other_tab));
 }
 
-float GM2TabStyle::GetBoundsInterpolatedSeparatorOpacity() const {
-  // When the bounds of a tab are animating, fade the separator based on how
-  // close to the target bounds this tab is. This function is only called
-  // when the target bounds are an end slot. That means this function will fade
-  // the separators in or out as a tab animtes into the end slot, but it will
-  // not be called if the tab is animating out of the end slot. In that case,
-  // the separator will snap to full opacity immediately, which is visually
-  // consistent with other bounds animations.
-  const gfx::Rect target_bounds =
-      tab_->controller()->GetTabAnimationTargetBounds(tab_);
-  const int tab_width = std::max(tab_->width(), target_bounds.width());
-  return static_cast<float>(
-             std::min(std::abs(tab_->x() - target_bounds.x()), tab_width)) /
-         tab_width;
-}
-
 bool GM2TabStyle::ShouldExtendHitTest() const {
   const views::Widget* widget = tab_->GetWidget();
   return widget->IsMaximized() || widget->IsFullscreen();
diff --git a/chrome/browser/ui/webui/BUILD.gn b/chrome/browser/ui/webui/BUILD.gn
index 683e979d..d8d8de83 100644
--- a/chrome/browser/ui/webui/BUILD.gn
+++ b/chrome/browser/ui/webui/BUILD.gn
@@ -55,6 +55,7 @@
       "//ash/webui/file_manager:file_manager_untrusted_ui",
       "//ash/webui/help_app_ui",
       "//ash/webui/os_feedback_ui",
+      "//ash/webui/shortcut_customization_ui",
       "//chrome/browser/ash",
     ]
     if (!is_official_build) {
diff --git a/chrome/browser/ui/webui/chrome_web_ui_configs_chromeos.cc b/chrome/browser/ui/webui/chrome_web_ui_configs_chromeos.cc
index b3b591f..f3155a4 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_configs_chromeos.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_configs_chromeos.cc
@@ -8,6 +8,7 @@
 #include "content/public/browser/webui_config_map.h"
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "ash/webui/shortcut_customization_ui/shortcut_customization_app_ui.h"
 #include "chrome/browser/ui/webui/chromeos/notification_tester/notification_tester_ui.h"
 #if !defined(OFFICIAL_BUILD)
 #include "ash/webui/facial_ml_app_ui/facial_ml_app_ui.h"
@@ -20,6 +21,7 @@
 void RegisterAshChromeWebUIConfigs() {
   // Add `WebUIConfig`s for Ash ChromeOS to the list here.
   auto& map = content::WebUIConfigMap::GetInstance();
+  map.AddWebUIConfig(std::make_unique<ash::ShortcutCustomizationAppUIConfig>());
   map.AddWebUIConfig(std::make_unique<chromeos::NotificationTesterUIConfig>());
 #if !defined(OFFICIAL_BUILD)
   map.AddWebUIConfig(std::make_unique<ash::FacialMLAppUIConfig>());
diff --git a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
index 7f93fed8..4db427035 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
@@ -209,8 +209,6 @@
 #include "ash/webui/scanning/url_constants.h"
 #include "ash/webui/shimless_rma/shimless_rma.h"
 #include "ash/webui/shimless_rma/url_constants.h"
-#include "ash/webui/shortcut_customization_ui/shortcut_customization_app_ui.h"
-#include "ash/webui/shortcut_customization_ui/url_constants.h"
 #include "ash/webui/system_extensions_internals_ui/system_extensions_internals_ui.h"
 #include "ash/webui/system_extensions_internals_ui/url_constants.h"
 #include "base/system/sys_info.h"
@@ -1028,10 +1026,6 @@
   }
   if (url.host_piece() == ash::kChromeUIMediaAppHost)
     return &NewComponentUI<ash::MediaAppUI, ChromeMediaAppUIDelegate>;
-  if (features::IsShortcutCustomizationAppEnabled()) {
-    if (url.host_piece() == ash::kChromeUIShortcutCustomizationAppHost)
-      return &NewWebUI<ash::ShortcutCustomizationAppUI>;
-  }
   if (ash::features::IsFirmwareUpdaterAppEnabled()) {
     if (url.host_piece() == ash::kChromeUIFirmwareUpdateAppHost)
       return &NewWebUI<ash::FirmwareUpdateAppUI>;
diff --git a/chrome/browser/ui/webui/chromeos/in_session_password_change/lock_screen_reauth_dialogs.cc b/chrome/browser/ui/webui/chromeos/in_session_password_change/lock_screen_reauth_dialogs.cc
index fde8484f..b697c60 100644
--- a/chrome/browser/ui/webui/chromeos/in_session_password_change/lock_screen_reauth_dialogs.cc
+++ b/chrome/browser/ui/webui/chromeos/in_session_password_change/lock_screen_reauth_dialogs.cc
@@ -19,6 +19,7 @@
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/webui/chromeos/in_session_password_change/base_lock_dialog.h"
@@ -110,6 +111,24 @@
   return CalculateOobeDialogSizeForPrimaryDisplay();
 }
 
+void LockScreenStartReauthDialog::RequestMediaAccessPermission(
+    content::WebContents* web_contents,
+    const content::MediaStreamRequest& request,
+    content::MediaResponseCallback callback) {
+  // This is required for accessing the camera for SAML logins.
+  MediaCaptureDevicesDispatcher::GetInstance()->ProcessMediaAccessRequest(
+      web_contents, request, std::move(callback), nullptr /* extension */);
+}
+
+bool LockScreenStartReauthDialog::CheckMediaAccessPermission(
+    content::RenderFrameHost* render_frame_host,
+    const GURL& security_origin,
+    blink::mojom::MediaStreamType type) {
+  // This is required for accessing the camera for SAML logins.
+  return MediaCaptureDevicesDispatcher::GetInstance()
+      ->CheckMediaAccessPermission(render_frame_host, security_origin, type);
+}
+
 void LockScreenStartReauthDialog::Show() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   if (g_dialog) {
@@ -161,6 +180,13 @@
   return ret.width();
 }
 
+content::WebContents* LockScreenStartReauthDialog::GetWebContents() {
+  auto* web_ui = webui();
+  if (!web_ui)
+    return nullptr;
+  return web_ui->GetWebContents();
+}
+
 void LockScreenStartReauthDialog::DeleteLockScreenNetworkDialog() {
   if (!lock_screen_network_dialog_)
     return;
diff --git a/chrome/browser/ui/webui/chromeos/in_session_password_change/lock_screen_reauth_dialogs.h b/chrome/browser/ui/webui/chromeos/in_session_password_change/lock_screen_reauth_dialogs.h
index 9a8b0dd..d6e0d72 100644
--- a/chrome/browser/ui/webui/chromeos/in_session_password_change/lock_screen_reauth_dialogs.h
+++ b/chrome/browser/ui/webui/chromeos/in_session_password_change/lock_screen_reauth_dialogs.h
@@ -35,10 +35,20 @@
   LockScreenStartReauthDialog(LockScreenStartReauthDialog const&) = delete;
   ~LockScreenStartReauthDialog() override;
 
+  // content::WebDialogDelegate
+  void RequestMediaAccessPermission(
+      content::WebContents* web_contents,
+      const content::MediaStreamRequest& request,
+      content::MediaResponseCallback callback) override;
+  bool CheckMediaAccessPermission(content::RenderFrameHost* render_frame_host,
+                                  const GURL& security_origin,
+                                  blink::mojom::MediaStreamType type) override;
+
   void Show();
   void Dismiss();
   bool IsRunning();
   int GetDialogWidth();
+  content::WebContents* GetWebContents();
 
   void DismissLockScreenNetworkDialog();
   void DismissLockScreenCaptivePortalDialog();
diff --git a/chrome/browser/web_applications/web_app_install_utils.cc b/chrome/browser/web_applications/web_app_install_utils.cc
index 402dfa1..066a9ff 100644
--- a/chrome/browser/web_applications/web_app_install_utils.cc
+++ b/chrome/browser/web_applications/web_app_install_utils.cc
@@ -639,7 +639,7 @@
   return urls;
 }
 
-base::flat_set<GURL> RemoveDuplicates(const std::vector<GURL>& from_urls) {
+base::flat_set<GURL> RemoveDuplicates(std::vector<GURL> from_urls) {
   return base::flat_set<GURL>{from_urls};
 }
 
@@ -659,7 +659,7 @@
 
   RemoveInvalidUrls(std::ref(icon_urls));
 
-  return RemoveDuplicates(icon_urls);
+  return RemoveDuplicates(std::move(icon_urls));
 }
 
 void PopulateOtherIcons(WebAppInstallInfo* web_app_info,
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 326dbf47..ae5a3ee4 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1660067998-d456c0c64daec27616a3a3491db0e29da7596b93.profdata
+chrome-linux-main-1660111194-a2b8e0cbf26d1488c0e26d9c85572b713074f36e.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index ed7d5ac2..fa14328 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1660067998-752bed295d7eb2a9aa79bdd97f2eed52f0d5dcdd.profdata
+chrome-mac-arm-main-1660111194-ac9093402317d5f777aad19b3f93f538de4d2f3e.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 28431d4b..2ed6596 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1660046387-7c16af46336a6a1585ab4e4e5314fabde162f260.profdata
+chrome-mac-main-1660111194-76c5298cbd7526455c48ad3e6b0489fac667fec5.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index b0bc0a0..56707d9 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1660057155-ee57f3d67dea7e308389127d554c2feddf31a266.profdata
+chrome-win32-main-1660111194-fb87dd62de881d79520e85254af5218303b865fa.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index aa907439..f299d78 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1660057155-6e68224f2ea3eb2b7348e58fb8ab7d66520b9d7e.profdata
+chrome-win64-main-1660111194-805e2cb9acd79076d09b76e83fde174352ab823e.profdata
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 86855bf1..06419f3 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -538,6 +538,9 @@
 const base::Feature kHappinessTrackingPersonalizationWallpaper{
     "HappinessTrackingPersonalizationWallpaper",
     base::FEATURE_DISABLED_BY_DEFAULT};
+// Enables the Happiness Tracking System for Media App PDF survey.
+const base::Feature kHappinessTrackingMediaAppPdf{
+    "HappinessTrackingMediaAppPdf", base::FEATURE_DISABLED_BY_DEFAULT};
 #endif
 
 // Hides the origin text from showing up briefly in WebApp windows.
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index 01f228e..7462850 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -378,6 +378,9 @@
 COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kHappinessTrackingPersonalizationWallpaper;
 
+COMPONENT_EXPORT(CHROME_FEATURES)
+extern const base::Feature kHappinessTrackingMediaAppPdf;
+
 #endif
 
 COMPONENT_EXPORT(CHROME_FEATURES)
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index eb28697..167ade6 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -870,6 +870,15 @@
 const char kHatsPersonalizationWallpaperSurveyIsSelected[] =
     "hats_personalization_wallpaper_is_selected";
 
+// An int64 pref. This is the timestamp, microseconds after epoch, that
+// indicates the end of the most recent Media App PDF survey cycle.
+const char kHatsMediaAppPdfCycleEndTs[] =
+    "hats_media_app_pdf_cycle_end_timestamp";
+
+// A boolean pref. Indicates if the device is selected for the Media App PDF
+// survey.
+const char kHatsMediaAppPdfIsSelected[] = "hats_media_app_pdf_is_selected";
+
 // A boolean pref. Indicates if we've already shown a notification to inform the
 // current user about the quick unlock feature.
 const char kPinUnlockFeatureNotificationShown[] =
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index 4d8afaaa..fbfd94e3 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -302,6 +302,8 @@
 extern const char kHatsPersonalizationScreensaverSurveyIsSelected[];
 extern const char kHatsPersonalizationWallpaperSurveyCycleEndTs[];
 extern const char kHatsPersonalizationWallpaperSurveyIsSelected[];
+extern const char kHatsMediaAppPdfCycleEndTs[];
+extern const char kHatsMediaAppPdfIsSelected[];
 extern const char kEolStatus[];
 extern const char kEndOfLifeDate[];
 extern const char kEolNotificationDismissed[];
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 90df675a..1d3ecf9 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -7462,6 +7462,7 @@
       "../browser/lacros/account_manager/signin_helper_lacros_unittest.cc",
       "../browser/lacros/cert/client_cert_store_lacros_unittest.cc",
       "../browser/lacros/force_installed_tracker_lacros_unittest.cc",
+      "../browser/lacros/fullscreen_controller_client_lacros_unittest.cc",
       "../browser/lacros/lacros_memory_pressure_evaluator_unittest.cc",
       "../browser/lacros/lacros_url_handling_unittest.cc",
       "../browser/lacros/metrics_reporting_observer_unittest.cc",
@@ -7490,6 +7491,7 @@
       "//chromeos/lacros:test_support",
       "//chromeos/startup",
       "//chromeos/ui/base",
+      "//chromeos/ui/wm",
       "//components/account_manager_core",
       "//components/account_manager_core:test_support",
     ]
diff --git a/chrome/test/base/test_chrome_web_ui_controller_factory.cc b/chrome/test/base/test_chrome_web_ui_controller_factory.cc
index c6c6391..1247c84 100644
--- a/chrome/test/base/test_chrome_web_ui_controller_factory.cc
+++ b/chrome/test/base/test_chrome_web_ui_controller_factory.cc
@@ -64,11 +64,9 @@
       provider ? provider->NewWebUI(web_ui, webui_url)
                : ChromeWebUIControllerFactory::CreateWebUIControllerForURL(
                      web_ui, webui_url);
+
   // Add an empty callback since managed-footnote always sends this message.
   web_ui->RegisterMessageCallback("observeManagedUI", base::DoNothing());
-  content::URLDataSource::Add(profile,
-                              std::make_unique<TestDataSource>("webui"));
-
   content::WebUIDataSource* source = webui::CreateWebUITestDataSource();
   if (provider)
     provider->DataSourceOverrides(source);
diff --git a/chrome/test/base/web_ui_browser_test.cc b/chrome/test/base/web_ui_browser_test.cc
index a94ffef..0c75e446 100644
--- a/chrome/test/base/web_ui_browser_test.cc
+++ b/chrome/test/base/web_ui_browser_test.cc
@@ -30,6 +30,7 @@
 #include "chrome/browser/ui/browser_navigator.h"
 #include "chrome/browser/ui/browser_navigator_params.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/webui/test_data_source.h"
 #include "chrome/browser/ui/webui/web_ui_test_handler.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/common/url_constants.h"
@@ -519,6 +520,17 @@
 
   logging::SetLogMessageHandler(&LogHandler);
 
+  // Register URLDataSource that serves files used in tests at chrome://test/
+  // e.g. `chrome://test/mocha.js`.
+  //
+  // For tests that run on the login screen, there is no Browser during
+  // SetUpOnMainThread() so skip adding TestDataSource. These tests don't need
+  // TestDataSource anyway.
+  if (browser()) {
+    content::URLDataSource::Add(browser()->profile(),
+                                std::make_unique<TestDataSource>("webui"));
+  }
+
   test_factory_ = std::make_unique<TestChromeWebUIControllerFactory>();
   factory_registration_ =
       std::make_unique<content::ScopedWebUIControllerFactoryRegistration>(
diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json
index 15defc2..3d9d415 100644
--- a/chrome/test/data/policy/policy_test_cases.json
+++ b/chrome/test/data/policy/policy_test_cases.json
@@ -8004,13 +8004,14 @@
   },
   "KeepFullscreenWithoutNotificationUrlAllowList": {
     "os": [
-      "chromeos_ash"
+      "chromeos_ash",
+      "chromeos_lacros"
     ],
     "policy_pref_mapping_tests": [
       {
         "policies": {},
         "prefs": {
-          "ash.keep_fullscreen_without_notification_url_allow_list": {
+          "keep_fullscreen_without_notification_url_allow_list": {
             "default_value": []
           }
         }
@@ -8022,7 +8023,7 @@
           ]
         },
         "prefs": {
-          "ash.keep_fullscreen_without_notification_url_allow_list": {
+          "keep_fullscreen_without_notification_url_allow_list": {
             "value": [
               "*"
             ]
diff --git a/chrome/test/data/webui/settings/payments_section_test.ts b/chrome/test/data/webui/settings/payments_section_test.ts
index 832fe59..035c1387 100644
--- a/chrome/test/data/webui/settings/payments_section_test.ts
+++ b/chrome/test/data/webui/settings/payments_section_test.ts
@@ -416,7 +416,7 @@
           assertTrue(creditCardDialog.$.saveButton.disabled);
 
           // Add a name.
-          creditCardDialog.set('creditCard.name', 'Jane Doe');
+          creditCardDialog.set('name_', 'Jane Doe');
           flush();
 
           assertEquals('hidden', getComputedStyle(expiredError).visibility);
@@ -432,6 +432,29 @@
         });
   });
 
+  test('verifyNotEditedEntryAfterCancel', async function() {
+    const creditCard = createCreditCardEntry();
+    let creditCardDialog = createCreditCardDialog(creditCard);
+
+    await whenAttributeIs(creditCardDialog.$.dialog, 'open', '');
+
+    // Edit a entry.
+    creditCardDialog.set('name_', 'EditedName');
+    creditCardDialog.set('nickname_', 'NickName');
+    creditCardDialog.set('cardNumber_', '0000000000001234');
+    flush();
+
+    creditCardDialog.$.cancelButton.click();
+    await eventToPromise('close', creditCardDialog);
+
+    creditCardDialog = createCreditCardDialog(creditCard);
+    await whenAttributeIs(creditCardDialog.$.dialog, 'open', '');
+
+    assertEquals(creditCardDialog.get('name_'), creditCard.name);
+    assertEquals(creditCardDialog.get('cardNumber_'), creditCard.cardNumber);
+    assertEquals(creditCardDialog.get('nickname_'), creditCard.nickname);
+  });
+
   test('verifyCancelCreditCardEdit', function(done) {
     const creditCard = createEmptyCreditCardEntry();
     const creditCardDialog = createCreditCardDialog(creditCard);
diff --git a/chromeos/crosapi/mojom/BUILD.gn b/chromeos/crosapi/mojom/BUILD.gn
index 64d9719..e01a20f 100644
--- a/chromeos/crosapi/mojom/BUILD.gn
+++ b/chromeos/crosapi/mojom/BUILD.gn
@@ -44,6 +44,7 @@
     "file_manager.mojom",
     "file_system_provider.mojom",
     "force_installed_tracker.mojom",
+    "fullscreen_controller.mojom",
     "geolocation.mojom",
     "holding_space_service.mojom",
     "identity_manager.mojom",
diff --git a/chromeos/crosapi/mojom/crosapi.mojom b/chromeos/crosapi/mojom/crosapi.mojom
index 9afeed4..1c153d0 100644
--- a/chromeos/crosapi/mojom/crosapi.mojom
+++ b/chromeos/crosapi/mojom/crosapi.mojom
@@ -39,6 +39,7 @@
 import "chromeos/crosapi/mojom/file_manager.mojom";
 import "chromeos/crosapi/mojom/file_system_provider.mojom";
 import "chromeos/crosapi/mojom/force_installed_tracker.mojom";
+import "chromeos/crosapi/mojom/fullscreen_controller.mojom";
 import "chromeos/crosapi/mojom/geolocation.mojom";
 import "chromeos/crosapi/mojom/holding_space_service.mojom";
 import "chromeos/crosapi/mojom/identity_manager.mojom";
@@ -124,8 +125,8 @@
 // please note the milestone when you added it, to help us reason about
 // compatibility between the client applications and older ash-chrome binaries.
 //
-// Next version: 94
-// Next method id: 98
+// Next version: 95
+// Next method id: 99
 [Stable, Uuid="8b79c34f-2bf8-4499-979a-b17cac522c1e",
  RenamedFrom="crosapi.mojom.AshChromeService"]
 interface Crosapi {
@@ -327,6 +328,11 @@
   BindForceInstalledTracker@57(
     pending_receiver<ForceInstalledTracker> receiver);
 
+  // Binds the full screen controller which determines whether to keep or exit
+  // full screen mode.
+  [MinVersion=94] BindFullscreenController@98(
+      pending_receiver<FullscreenController> receiver);
+
   // Binds the GeolocationService interface for getting network access point
   // information.
   // Added in M95.
diff --git a/chromeos/crosapi/mojom/fullscreen_controller.mojom b/chromeos/crosapi/mojom/fullscreen_controller.mojom
new file mode 100644
index 0000000..9b1be82
--- /dev/null
+++ b/chromeos/crosapi/mojom/fullscreen_controller.mojom
@@ -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.
+
+module crosapi.mojom;
+
+// A client implemented by lacros-chrome.
+[Stable]
+interface FullscreenControllerClient {
+  // Returns whether full screen mode should be exited on session lock/unlock.
+  ShouldExitFullscreenBeforeLock@0() => (bool should_exit_fullscreen);
+};
+
+// This interface lets ash query lacros whether it should exit full screen mode.
+// Implemented by ash-chrome.
+[Stable, Uuid="49d56ccf-d93f-4fea-a9cd-ce84bc8ea4f5"]
+interface FullscreenController {
+  // Registers the client that lives in lacros-chrome.
+  AddClient@0(pending_remote<FullscreenControllerClient> client);
+};
diff --git a/chromeos/lacros/lacros_service.cc b/chromeos/lacros/lacros_service.cc
index dd29a30..f8b1c5a6 100644
--- a/chromeos/lacros/lacros_service.cc
+++ b/chromeos/lacros/lacros_service.cc
@@ -48,6 +48,7 @@
 #include "chromeos/crosapi/mojom/file_manager.mojom.h"
 #include "chromeos/crosapi/mojom/file_system_provider.mojom.h"
 #include "chromeos/crosapi/mojom/force_installed_tracker.mojom.h"
+#include "chromeos/crosapi/mojom/fullscreen_controller.mojom.h"
 #include "chromeos/crosapi/mojom/geolocation.mojom.h"
 #include "chromeos/crosapi/mojom/holding_space_service.mojom.h"
 #include "chromeos/crosapi/mojom/identity_manager.mojom.h"
@@ -333,6 +334,9 @@
       &crosapi::mojom::Crosapi::BindForceInstalledTracker,
       Crosapi::MethodMinVersions::kBindForceInstalledTrackerMinVersion>();
   ConstructRemote<
+      crosapi::mojom::FullscreenController, &Crosapi::BindFullscreenController,
+      Crosapi::MethodMinVersions::kBindFullscreenControllerMinVersion>();
+  ConstructRemote<
       crosapi::mojom::GeolocationService,
       &crosapi::mojom::Crosapi::BindGeolocationService,
       Crosapi::MethodMinVersions::kBindGeolocationServiceMinVersion>();
diff --git a/chromeos/ui/wm/BUILD.gn b/chromeos/ui/wm/BUILD.gn
index 61fb2cc..a46f6ea 100644
--- a/chromeos/ui/wm/BUILD.gn
+++ b/chromeos/ui/wm/BUILD.gn
@@ -16,14 +16,21 @@
     "desks/desks_helper.h",
     "features.cc",
     "features.h",
+    "fullscreen/keep_fullscreen_for_url_checker.cc",
+    "fullscreen/keep_fullscreen_for_url_checker.h",
+    "fullscreen/pref_names.cc",
+    "fullscreen/pref_names.h",
   ]
 
   deps = [
     "//base",
     "//build:chromeos_buildflags",
     "//chromeos/ui/base",
+    "//components/prefs",
+    "//components/url_matcher",
     "//ui/aura",
     "//ui/base",
+    "//url",
   ]
 
   if (is_chromeos_ash) {
diff --git a/chromeos/ui/wm/DEPS b/chromeos/ui/wm/DEPS
index f86f4ee..8435167 100644
--- a/chromeos/ui/wm/DEPS
+++ b/chromeos/ui/wm/DEPS
@@ -2,5 +2,7 @@
   "+ui/aura",
   "+ui/base",
   "+ui/platform_window",
+  "+components/prefs",
+  "+components/url_matcher",
   "+ui/views",
 ]
diff --git a/chromeos/ui/wm/fullscreen/keep_fullscreen_for_url_checker.cc b/chromeos/ui/wm/fullscreen/keep_fullscreen_for_url_checker.cc
new file mode 100644
index 0000000..7f6582fa
--- /dev/null
+++ b/chromeos/ui/wm/fullscreen/keep_fullscreen_for_url_checker.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 "chromeos/ui/wm/fullscreen/keep_fullscreen_for_url_checker.h"
+
+#include "chromeos/ui/wm/fullscreen/pref_names.h"
+#include "components/prefs/pref_change_registrar.h"
+#include "components/prefs/pref_service.h"
+#include "components/url_matcher/url_matcher.h"
+#include "components/url_matcher/url_util.h"
+#include "url/gurl.h"
+
+namespace chromeos {
+
+KeepFullscreenForUrlChecker::KeepFullscreenForUrlChecker(
+    PrefService* pref_service)
+    : pref_service_(pref_service) {
+  pref_observer_.Init(pref_service);
+  pref_observer_.Add(
+      prefs::kKeepFullscreenWithoutNotificationUrlAllowList,
+      base::BindRepeating(&KeepFullscreenForUrlChecker::OnPrefChanged,
+                          base::Unretained(this)));
+
+  // Initialize with the current pref values.
+  OnPrefChanged();
+}
+
+KeepFullscreenForUrlChecker::~KeepFullscreenForUrlChecker() = default;
+
+bool KeepFullscreenForUrlChecker::
+    IsKeepFullscreenWithoutNotificationPolicySet() {
+  return url_matcher_ != nullptr;
+}
+
+bool KeepFullscreenForUrlChecker::ShouldExitFullscreenForUrl(GURL window_url) {
+  return url_matcher_ ? url_matcher_->MatchURL(window_url).empty() : true;
+}
+
+void KeepFullscreenForUrlChecker::OnPrefChanged() {
+  const auto& url_allow_list = pref_service_->GetValueList(
+      prefs::kKeepFullscreenWithoutNotificationUrlAllowList);
+  if (url_allow_list.size() == 0) {
+    url_matcher_ = nullptr;
+    return;
+  }
+
+  url_matcher_ = std::make_unique<url_matcher::URLMatcher>();
+  url_matcher::util::AddAllowFilters(url_matcher_.get(), url_allow_list);
+}
+
+}  // namespace chromeos
diff --git a/chromeos/ui/wm/fullscreen/keep_fullscreen_for_url_checker.h b/chromeos/ui/wm/fullscreen/keep_fullscreen_for_url_checker.h
new file mode 100644
index 0000000..19f779a
--- /dev/null
+++ b/chromeos/ui/wm/fullscreen/keep_fullscreen_for_url_checker.h
@@ -0,0 +1,57 @@
+// 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_UI_WM_FULLSCREEN_KEEP_FULLSCREEN_FOR_URL_CHECKER_H_
+#define CHROMEOS_UI_WM_FULLSCREEN_KEEP_FULLSCREEN_FOR_URL_CHECKER_H_
+
+#include <memory>
+
+#include "base/memory/raw_ptr.h"
+#include "components/prefs/pref_change_registrar.h"
+#include "components/prefs/pref_service.h"
+#include "components/url_matcher/url_matcher.h"
+
+class GURL;
+
+namespace chromeos {
+
+// Initialized with the given PrefService, the KeepFullscreenForUrlChecker is
+// used to check if it is allowed by user pref to keep fullscreen after unlock.
+class KeepFullscreenForUrlChecker {
+ public:
+  explicit KeepFullscreenForUrlChecker(PrefService* pref_service);
+
+  KeepFullscreenForUrlChecker(const KeepFullscreenForUrlChecker&) = delete;
+  KeepFullscreenForUrlChecker& operator=(const KeepFullscreenForUrlChecker&) =
+      delete;
+
+  ~KeepFullscreenForUrlChecker();
+
+  // Returns true if the KeepFullscreenWithoutNotification value is set in the
+  // corresponding PrefService, otherwise returns false.
+  bool IsKeepFullscreenWithoutNotificationPolicySet();
+
+  // Returns true if it is not allowed by user pref to keep full screen for the
+  // given window URL. Returns false if it is allowed to keep full screen, that
+  // is, if |window_url| matches a pattern in the
+  // KeepFullscreenWithoutNotification policy of the corresponding PrefService.
+  bool ShouldExitFullscreenForUrl(GURL window_url);
+
+ private:
+  // Updates the filters of the URLMatcher when the
+  // KeepFullscreenWithoutNotificationUrlAllowList pref changed.
+  void OnPrefChanged();
+
+  PrefChangeRegistrar pref_observer_;
+
+  raw_ptr<PrefService> pref_service_;
+
+  std::unique_ptr<url_matcher::URLMatcher> url_matcher_;
+
+  base::WeakPtrFactory<KeepFullscreenForUrlChecker> weak_ptr_factory_{this};
+};
+
+}  // namespace chromeos
+
+#endif  // CHROMEOS_UI_WM_FULLSCREEN_KEEP_FULLSCREEN_FOR_URL_CHECKER_H_
diff --git a/chromeos/ui/wm/fullscreen/pref_names.cc b/chromeos/ui/wm/fullscreen/pref_names.cc
new file mode 100644
index 0000000..a8d4da8
--- /dev/null
+++ b/chromeos/ui/wm/fullscreen/pref_names.cc
@@ -0,0 +1,15 @@
+// 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/ui/wm/fullscreen/pref_names.h"
+
+namespace chromeos::prefs {
+
+// A list of URLs that are allowed to continue full screen mode after session
+// unlock without a notification. To prevent fake login screens, the device
+// normally exits full screen mode before locking a session.
+const char kKeepFullscreenWithoutNotificationUrlAllowList[] =
+    "keep_fullscreen_without_notification_url_allow_list";
+
+}  // namespace chromeos::prefs
diff --git a/chromeos/ui/wm/fullscreen/pref_names.h b/chromeos/ui/wm/fullscreen/pref_names.h
new file mode 100644
index 0000000..a8883f46
--- /dev/null
+++ b/chromeos/ui/wm/fullscreen/pref_names.h
@@ -0,0 +1,14 @@
+// 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_UI_WM_FULLSCREEN_PREF_NAMES_H_
+#define CHROMEOS_UI_WM_FULLSCREEN_PREF_NAMES_H_
+
+namespace chromeos::prefs {
+
+extern const char kKeepFullscreenWithoutNotificationUrlAllowList[];
+
+}  // namespace chromeos::prefs
+
+#endif  // CHROMEOS_UI_WM_FULLSCREEN_PREF_NAMES_H_
diff --git a/components/autofill_assistant/browser/BUILD.gn b/components/autofill_assistant/browser/BUILD.gn
index 17d667e..fdaf7cc 100644
--- a/components/autofill_assistant/browser/BUILD.gn
+++ b/components/autofill_assistant/browser/BUILD.gn
@@ -405,6 +405,8 @@
     "actions/wait_for_dom_test_base.h",
     "fake_common_dependencies.cc",
     "fake_common_dependencies.h",
+    "fake_platform_dependencies.cc",
+    "fake_platform_dependencies.h",
     "fake_script_executor_delegate.cc",
     "fake_script_executor_delegate.h",
     "fake_script_executor_ui_delegate.cc",
@@ -526,6 +528,7 @@
     "field_formatter_unittest.cc",
     "full_card_requester_unittest.cc",
     "generic_ui_replace_placeholders_unittest.cc",
+    "headless/headless_script_controller_impl_unittest.cc",
     "js_flow_util_unittest.cc",
     "mock_client_context.cc",
     "mock_client_context.h",
diff --git a/components/autofill_assistant/browser/android/client_android.cc b/components/autofill_assistant/browser/android/client_android.cc
index 108d1fd..c3cbdc9 100644
--- a/components/autofill_assistant/browser/android/client_android.cc
+++ b/components/autofill_assistant/browser/android/client_android.cc
@@ -708,7 +708,8 @@
       GetWebContents(), /* client= */ this,
       base::DefaultTickClock::GetInstance(),
       RuntimeManager::GetForWebContents(GetWebContents())->GetWeakPtr(),
-      std::move(service), ukm::UkmRecorder::Get(), annotate_dom_model_service_);
+      std::move(service), /* web_controller= */ nullptr,
+      ukm::UkmRecorder::Get(), annotate_dom_model_service_);
   ui_controller_ = std::make_unique<UiController>(
       /* client= */ this, controller_.get(), std::move(tts_controller));
   ui_controller_->StartListening();
diff --git a/components/autofill_assistant/browser/android/starter_delegate_android.cc b/components/autofill_assistant/browser/android/starter_delegate_android.cc
index 89d647d..a1c01541 100644
--- a/components/autofill_assistant/browser/android/starter_delegate_android.cc
+++ b/components/autofill_assistant/browser/android/starter_delegate_android.cc
@@ -15,6 +15,7 @@
 #include "components/autofill_assistant/browser/android/trigger_script_bridge_android.h"
 #include "components/autofill_assistant/browser/android/ui_controller_android_utils.h"
 #include "components/autofill_assistant/browser/assistant_field_trial_util.h"
+#include "components/autofill_assistant/browser/headless/client_headless.h"
 #include "components/autofill_assistant/browser/headless/headless_script_controller_impl.h"
 #include "components/autofill_assistant/browser/public/password_change/website_login_manager_impl.h"
 #include "components/autofill_assistant/browser/public/runtime_manager_impl.h"
@@ -320,10 +321,17 @@
       jinitial_url, GetIsCustomTab());
 
   if (trigger_context->GetScriptParameters().GetRunHeadless()) {
+    auto client = std::make_unique<ClientHeadless>(
+        &GetWebContents(), starter_->GetCommonDependencies(),
+        /* action_extension_delegate= */ nullptr, GetWebsiteLoginManager(),
+        base::DefaultTickClock::GetInstance(),
+        RuntimeManager::GetForWebContents(&GetWebContents())->GetWeakPtr(),
+        ukm::UkmRecorder::Get(),
+        starter_->GetCommonDependencies()->GetOrCreateAnnotateDomModelService(
+            GetWebContents().GetBrowserContext()));
     headless_script_controller_ =
         std::make_unique<HeadlessScriptControllerImpl>(
-            &GetWebContents(), /*action_extension_delegate=*/nullptr,
-            GetWebsiteLoginManager());
+            &GetWebContents(), starter_.get(), std::move(client));
 
     headless_script_controller_->StartScript(
         // Note: this ignores device-only parameters.
diff --git a/components/autofill_assistant/browser/autofill_assistant_factory.cc b/components/autofill_assistant/browser/autofill_assistant_factory.cc
index c3161c8..277de1a 100644
--- a/components/autofill_assistant/browser/autofill_assistant_factory.cc
+++ b/components/autofill_assistant/browser/autofill_assistant_factory.cc
@@ -8,6 +8,7 @@
 
 #include "components/autofill_assistant/browser/autofill_assistant_impl.h"
 #include "components/autofill_assistant/browser/common_dependencies.h"
+#include "content/public/browser/browser_context.h"
 
 namespace autofill_assistant {
 
diff --git a/components/autofill_assistant/browser/autofill_assistant_impl.cc b/components/autofill_assistant/browser/autofill_assistant_impl.cc
index 9c9ba8a..def9fd2 100644
--- a/components/autofill_assistant/browser/autofill_assistant_impl.cc
+++ b/components/autofill_assistant/browser/autofill_assistant_impl.cc
@@ -7,8 +7,10 @@
 #include <memory>
 #include <vector>
 
+#include "base/time/default_tick_clock.h"
 #include "components/autofill_assistant/browser/common_dependencies.h"
 #include "components/autofill_assistant/browser/desktop/starter_delegate_desktop.h"
+#include "components/autofill_assistant/browser/headless/client_headless.h"
 #include "components/autofill_assistant/browser/headless/headless_script_controller_impl.h"
 #include "components/autofill_assistant/browser/protocol_utils.h"
 #include "components/autofill_assistant/browser/public/password_change/website_login_manager.h"
@@ -19,6 +21,7 @@
 #include "components/autofill_assistant/browser/service/service_request_sender.h"
 #include "components/autofill_assistant/browser/service/service_request_sender_impl.h"
 #include "components/autofill_assistant/browser/service/simple_url_loader_factory.h"
+#include "components/autofill_assistant/browser/starter.h"
 #include "components/version_info/version_info.h"
 #include "content/public/browser/browser_context.h"
 #include "net/http/http_status_code.h"
@@ -140,8 +143,20 @@
     content::WebContents* web_contents,
     ExternalActionDelegate* action_extension_delegate,
     WebsiteLoginManager* website_login_manager) {
-  return std::make_unique<HeadlessScriptControllerImpl>(
-      web_contents, action_extension_delegate, website_login_manager);
+  auto* starter = Starter::FromWebContents(web_contents);
+  if (!starter) {
+    return nullptr;
+  }
+
+  auto client = std::make_unique<ClientHeadless>(
+      web_contents, starter->GetCommonDependencies(), action_extension_delegate,
+      website_login_manager, base::DefaultTickClock::GetInstance(),
+      RuntimeManager::GetForWebContents(web_contents)->GetWeakPtr(),
+      ukm::UkmRecorder::Get(),
+      starter->GetCommonDependencies()->GetOrCreateAnnotateDomModelService(
+          web_contents->GetBrowserContext()));
+  return std::make_unique<HeadlessScriptControllerImpl>(web_contents, starter,
+                                                        std::move(client));
 }
 
 }  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/controller.cc b/components/autofill_assistant/browser/controller.cc
index ac11864..720ba283 100644
--- a/components/autofill_assistant/browser/controller.cc
+++ b/components/autofill_assistant/browser/controller.cc
@@ -75,6 +75,7 @@
                        const base::TickClock* tick_clock,
                        base::WeakPtr<RuntimeManager> runtime_manager,
                        std::unique_ptr<Service> service,
+                       std::unique_ptr<WebController> web_controller,
                        ukm::UkmRecorder* ukm_recorder,
                        AnnotateDomModelService* annotate_dom_model_service)
     : content::WebContentsObserver(web_contents),
@@ -84,6 +85,7 @@
       service_(service ? std::move(service)
                        : ServiceImpl::Create(web_contents->GetBrowserContext(),
                                              client_)),
+      web_controller_(std::move(web_controller)),
       navigating_to_new_document_(web_contents->IsWaitingForResponse()),
       ukm_recorder_(ukm_recorder),
       annotate_dom_model_service_(annotate_dom_model_service) {}
diff --git a/components/autofill_assistant/browser/controller.h b/components/autofill_assistant/browser/controller.h
index 7c84e93..dab68a12 100644
--- a/components/autofill_assistant/browser/controller.h
+++ b/components/autofill_assistant/browser/controller.h
@@ -63,13 +63,14 @@
  public:
   // |web_contents|, |client|, |tick_clock| and |script_executor_ui_delegate|
   // must remain valid for the lifetime of the instance. Controller will take
-  // ownership of |service| if specified, otherwise will create and own the
-  // default service.
+  // ownership of |service| and |web_controller| if specified, otherwise will
+  // create and own a default instance.
   Controller(content::WebContents* web_contents,
              Client* client,
              const base::TickClock* tick_clock,
              base::WeakPtr<RuntimeManager> runtime_manager,
              std::unique_ptr<Service> service,
+             std::unique_ptr<WebController> web_controller,
              ukm::UkmRecorder* ukm_recorder,
              AnnotateDomModelService* annotate_dom_model_service);
 
@@ -313,15 +314,15 @@
   const raw_ptr<const base::TickClock> tick_clock_;
   base::WeakPtr<RuntimeManager> runtime_manager_;
 
-  // Lazily instantiate in GetWebController().
-  std::unique_ptr<WebController> web_controller_;
-
   // An instance to suppress keyboard. If this is not nullptr, the keyboard
   // is suppressed.
   std::unique_ptr<SuppressKeyboardRAII> suppress_keyboard_raii_;
 
-  // Lazily instantiate in GetService().
   std::unique_ptr<Service> service_;
+
+  // Lazily instantiate in GetWebController().
+  std::unique_ptr<WebController> web_controller_;
+
   std::unique_ptr<TriggerContext> trigger_context_;
 
   AutofillAssistantState state_ = AutofillAssistantState::INACTIVE;
diff --git a/components/autofill_assistant/browser/controller_unittest.cc b/components/autofill_assistant/browser/controller_unittest.cc
index 13763b5b..7b72f80 100644
--- a/components/autofill_assistant/browser/controller_unittest.cc
+++ b/components/autofill_assistant/browser/controller_unittest.cc
@@ -113,11 +113,10 @@
     mock_runtime_manager_ = std::make_unique<MockRuntimeManager>();
     controller_ = std::make_unique<Controller>(
         web_contents(), &mock_client_, task_environment()->GetMockTickClock(),
-        mock_runtime_manager_->GetWeakPtr(), std::move(service), &ukm_recorder_,
+        mock_runtime_manager_->GetWeakPtr(), std::move(service),
+        std::move(web_controller), &ukm_recorder_,
         &mock_annotate_dom_model_service_);
 
-    SetWebControllerForTest(controller_.get(), std::move(web_controller));
-
     ON_CALL(mock_client_, AttachUI()).WillByDefault(Invoke([this]() {
       controller_->SetUiShown(true);
     }));
@@ -2304,11 +2303,13 @@
 
 TEST_F(ControllerTest, FlowFinishedMetricControllerDestroyedMidFlow) {
   auto service = std::make_unique<NiceMock<MockService>>();
+  auto web_controller = std::make_unique<NiceMock<MockWebController>>();
   auto* service_ptr = service.get();
 
   auto controller = std::make_unique<Controller>(
       web_contents(), &mock_client_, task_environment()->GetMockTickClock(),
-      mock_runtime_manager_->GetWeakPtr(), std::move(service), &ukm_recorder_,
+      mock_runtime_manager_->GetWeakPtr(), std::move(service),
+      std::move(web_controller), &ukm_recorder_,
       /* annotate_dom_model_service= */ nullptr);
   SetWebControllerForTest(controller.get(),
                           std::make_unique<NiceMock<MockWebController>>());
diff --git a/components/autofill_assistant/browser/fake_platform_dependencies.cc b/components/autofill_assistant/browser/fake_platform_dependencies.cc
new file mode 100644
index 0000000..c637ac5
--- /dev/null
+++ b/components/autofill_assistant/browser/fake_platform_dependencies.cc
@@ -0,0 +1,17 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill_assistant/browser/fake_platform_dependencies.h"
+
+namespace autofill_assistant {
+
+FakePlatformDependencies::FakePlatformDependencies() = default;
+FakePlatformDependencies::~FakePlatformDependencies() = default;
+
+bool FakePlatformDependencies::IsCustomTab(
+    const content::WebContents& web_contents) const {
+  return is_custom_tab_;
+}
+
+}  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/fake_platform_dependencies.h b/components/autofill_assistant/browser/fake_platform_dependencies.h
new file mode 100644
index 0000000..7366269
--- /dev/null
+++ b/components/autofill_assistant/browser/fake_platform_dependencies.h
@@ -0,0 +1,26 @@
+// 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_ASSISTANT_BROWSER_FAKE_PLATFORM_DEPENDENCIES_H_
+#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_FAKE_PLATFORM_DEPENDENCIES_H_
+
+#include "components/autofill_assistant/browser/platform_dependencies.h"
+
+namespace autofill_assistant {
+
+class FakePlatformDependencies : public PlatformDependencies {
+ public:
+  FakePlatformDependencies();
+  ~FakePlatformDependencies() override;
+
+  // From PlatformDependencies:
+  bool IsCustomTab(const content::WebContents& web_contents) const override;
+
+  // Intentionally public to allow tests direct access.
+  bool is_custom_tab_ = false;
+};
+
+}  // namespace autofill_assistant
+
+#endif  // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_FAKE_PLATFORM_DEPENDENCIES_H_
diff --git a/components/autofill_assistant/browser/fake_starter_platform_delegate.cc b/components/autofill_assistant/browser/fake_starter_platform_delegate.cc
index 51952b0e..4205034 100644
--- a/components/autofill_assistant/browser/fake_starter_platform_delegate.cc
+++ b/components/autofill_assistant/browser/fake_starter_platform_delegate.cc
@@ -142,7 +142,7 @@
 
 const PlatformDependencies*
 FakeStarterPlatformDelegate::GetPlatformDependencies() const {
-  return nullptr;
+  return &fake_platform_dependencies_;
 }
 
 base::WeakPtr<StarterPlatformDelegate>
diff --git a/components/autofill_assistant/browser/fake_starter_platform_delegate.h b/components/autofill_assistant/browser/fake_starter_platform_delegate.h
index da7ea21..e4fd61f 100644
--- a/components/autofill_assistant/browser/fake_starter_platform_delegate.h
+++ b/components/autofill_assistant/browser/fake_starter_platform_delegate.h
@@ -9,6 +9,7 @@
 #include "base/callback.h"
 #include "base/memory/raw_ptr.h"
 #include "components/autofill_assistant/browser/fake_common_dependencies.h"
+#include "components/autofill_assistant/browser/fake_platform_dependencies.h"
 #include "components/autofill_assistant/browser/starter_platform_delegate.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
@@ -63,6 +64,7 @@
 
   // Intentionally public to give tests direct access.
   FakeCommonDependencies fake_common_dependencies_;
+  FakePlatformDependencies fake_platform_dependencies_;
   std::unique_ptr<TriggerScriptCoordinator::UiDelegate>
       trigger_script_ui_delegate_;
   std::unique_ptr<ServiceRequestSender> trigger_script_request_sender_for_test_;
diff --git a/components/autofill_assistant/browser/headless/client_headless.cc b/components/autofill_assistant/browser/headless/client_headless.cc
index 3cc3cb1..9bf22eb 100644
--- a/components/autofill_assistant/browser/headless/client_headless.cc
+++ b/components/autofill_assistant/browser/headless/client_headless.cc
@@ -12,7 +12,6 @@
 #include "base/containers/flat_set.h"
 #include "base/json/json_writer.h"
 #include "base/metrics/field_trial_params.h"
-#include "base/time/default_tick_clock.h"
 #include "components/autofill_assistant/browser/autofill_assistant_tts_controller.h"
 #include "components/autofill_assistant/browser/controller.h"
 #include "components/autofill_assistant/browser/display_strings_util.h"
@@ -45,10 +44,18 @@
     content::WebContents* web_contents,
     const CommonDependencies* common_dependencies,
     ExternalActionDelegate* action_extension_delegate,
-    WebsiteLoginManager* website_login_manager)
+    WebsiteLoginManager* website_login_manager,
+    const base::TickClock* tick_clock,
+    base::WeakPtr<RuntimeManager> runtime_manager,
+    ukm::UkmRecorder* ukm_recorder,
+    AnnotateDomModelService* annotate_dom_model_service)
     : web_contents_(web_contents),
       common_dependencies_(common_dependencies),
-      website_login_manager_(website_login_manager) {
+      website_login_manager_(website_login_manager),
+      tick_clock_(tick_clock),
+      runtime_manager_(runtime_manager),
+      ukm_recorder_(ukm_recorder),
+      annotate_dom_model_service_(annotate_dom_model_service) {
   headless_ui_controller_ =
       std::make_unique<HeadlessUiController>(action_extension_delegate);
 }
@@ -58,6 +65,8 @@
 void ClientHeadless::Start(
     const GURL& url,
     std::unique_ptr<TriggerContext> trigger_context,
+    std::unique_ptr<Service> service,
+    std::unique_ptr<WebController> web_controller,
     base::OnceCallback<void(Metrics::DropOutReason reason)>
         script_ended_callback) {
   // Ignore the call if a script is already running.
@@ -66,12 +75,9 @@
   }
   script_ended_callback_ = std::move(script_ended_callback);
   controller_ = std::make_unique<Controller>(
-      web_contents_, /* client= */ this, base::DefaultTickClock::GetInstance(),
-      RuntimeManager::GetForWebContents(web_contents_)->GetWeakPtr(),
-      /* service= */ nullptr, ukm::UkmRecorder::Get(),
-      /* annotate_dom_model_service= */
-      common_dependencies_->GetOrCreateAnnotateDomModelService(
-          GetWebContents()->GetBrowserContext()));
+      web_contents_, /* client= */ this, tick_clock_, runtime_manager_,
+      std::move(service), std::move(web_controller), ukm_recorder_,
+      annotate_dom_model_service_);
   controller_->AddObserver(headless_ui_controller_.get());
   controller_->Start(url, std::move(trigger_context));
 }
diff --git a/components/autofill_assistant/browser/headless/client_headless.h b/components/autofill_assistant/browser/headless/client_headless.h
index b1b0c6f..e9c59a7 100644
--- a/components/autofill_assistant/browser/headless/client_headless.h
+++ b/components/autofill_assistant/browser/headless/client_headless.h
@@ -35,7 +35,11 @@
   explicit ClientHeadless(content::WebContents* web_contents,
                           const CommonDependencies* common_dependencies,
                           ExternalActionDelegate* action_extension_delegate,
-                          WebsiteLoginManager* website_login_manager);
+                          WebsiteLoginManager* website_login_manager,
+                          const base::TickClock* tick_clock,
+                          base::WeakPtr<RuntimeManager> runtime_manager,
+                          ukm::UkmRecorder* ukm_recorder,
+                          AnnotateDomModelService* annotate_dom_model_service);
   ClientHeadless(const ClientHeadless&) = delete;
   ClientHeadless& operator=(const ClientHeadless&) = delete;
 
@@ -44,6 +48,8 @@
   bool IsRunning() const;
   void Start(const GURL& url,
              std::unique_ptr<TriggerContext> trigger_context,
+             std::unique_ptr<Service> service,
+             std::unique_ptr<WebController> web_controller,
              base::OnceCallback<void(Metrics::DropOutReason reason)>
                  script_ended_callback);
 
@@ -91,15 +97,18 @@
                                   signin::AccessTokenInfo access_token_info);
   void NotifyScriptEnded(Metrics::DropOutReason reason);
 
-  raw_ptr<content::WebContents> web_contents_;
+  const raw_ptr<content::WebContents> web_contents_;
   std::unique_ptr<Controller> controller_;
   const raw_ptr<const CommonDependencies> common_dependencies_;
-  raw_ptr<WebsiteLoginManager> website_login_manager_;
+  const raw_ptr<WebsiteLoginManager> website_login_manager_;
   std::unique_ptr<HeadlessUiController> headless_ui_controller_;
-  raw_ptr<signin::IdentityManager> identity_manager_ = nullptr;
   std::unique_ptr<signin::AccessTokenFetcher> access_token_fetcher_;
   base::OnceCallback<void(bool, const std::string&)>
       fetch_access_token_callback_;
+  const raw_ptr<const base::TickClock> tick_clock_;
+  base::WeakPtr<RuntimeManager> runtime_manager_;
+  const raw_ptr<ukm::UkmRecorder> ukm_recorder_;
+  const raw_ptr<AnnotateDomModelService> annotate_dom_model_service_;
 
   // Only set while a script is running.
   base::OnceCallback<void(Metrics::DropOutReason reason)>
diff --git a/components/autofill_assistant/browser/headless/headless_script_controller_impl.cc b/components/autofill_assistant/browser/headless/headless_script_controller_impl.cc
index 5187218..ca71d13 100644
--- a/components/autofill_assistant/browser/headless/headless_script_controller_impl.cc
+++ b/components/autofill_assistant/browser/headless/headless_script_controller_impl.cc
@@ -7,24 +7,19 @@
 #include "base/callback_helpers.h"
 #include "base/time/default_tick_clock.h"
 #include "components/autofill_assistant/browser/desktop/starter_delegate_desktop.h"
-#include "components/autofill_assistant/browser/public/password_change/website_login_manager.h"
+#include "components/autofill_assistant/browser/headless/client_headless.h"
 #include "components/autofill_assistant/browser/starter.h"
 
 namespace autofill_assistant {
 
 HeadlessScriptControllerImpl::HeadlessScriptControllerImpl(
     content::WebContents* web_contents,
-    ExternalActionDelegate* action_extension_delegate,
-    WebsiteLoginManager* website_login_manager)
-    : web_contents_(web_contents) {
-  DCHECK(web_contents_);
-
-  auto* starter = Starter::FromWebContents(web_contents_);
-  if (starter) {
-    client_ = std::make_unique<ClientHeadless>(
-        web_contents, starter->GetCommonDependencies(),
-        action_extension_delegate, website_login_manager);
-  }
+    Starter* starter,
+    std::unique_ptr<ClientHeadless> client)
+    : web_contents_(web_contents),
+      starter_(starter),
+      client_(std::move(client)) {
+  DCHECK(web_contents_ && starter_ && client_);
 }
 
 HeadlessScriptControllerImpl::~HeadlessScriptControllerImpl() = default;
@@ -41,19 +36,24 @@
     base::OnceCallback<void(ScriptResult)> script_ended_callback,
     bool use_autofill_assistant_onboarding,
     base::OnceCallback<void()> onboarding_successful_callback) {
+  StartScript(script_parameters, std::move(script_ended_callback),
+              use_autofill_assistant_onboarding,
+              std::move(onboarding_successful_callback),
+              /* service= */ nullptr, /* web_controller= */ nullptr);
+}
+void HeadlessScriptControllerImpl::StartScript(
+    const base::flat_map<std::string, std::string>& script_parameters,
+    base::OnceCallback<void(ScriptResult)> script_ended_callback,
+    bool use_autofill_assistant_onboarding,
+    base::OnceCallback<void()> onboarding_successful_callback,
+    std::unique_ptr<Service> service,
+    std::unique_ptr<WebController> web_controller) {
   // This HeadlessScriptController is currently executing a script, so we return
   // an error.
   if (script_ended_callback_) {
     std::move(script_ended_callback).Run({false});
     return;
   }
-  auto* starter = Starter::FromWebContents(web_contents_);
-  // The starter has not yet been initialized or was not initialized at the
-  // time the constructor was called.
-  if (!starter || !client_) {
-    std::move(script_ended_callback).Run({false});
-    return;
-  }
 
   script_ended_callback_ = std::move(script_ended_callback);
   onboarding_successful_callback_ = std::move(onboarding_successful_callback);
@@ -61,7 +61,7 @@
   auto trigger_context = std::make_unique<TriggerContext>(
       std::move(parameters),
       /* experiment_ids = */ "",
-      starter->GetPlatformDependencies()->IsCustomTab(*web_contents_),
+      starter_->GetPlatformDependencies()->IsCustomTab(*web_contents_),
       /*onboarding_shown = */ false,
       /*is_direct_action = */ false,
       /* initial_url = */ "",
@@ -69,13 +69,16 @@
       /* is_externally_triggered = */ true,
       /* skip_autofill_assistant_onboarding = */
       !use_autofill_assistant_onboarding);
-  starter->CanStart(
+  starter_->CanStart(
       std::move(trigger_context),
       base::BindOnce(&HeadlessScriptControllerImpl::OnReadyToStart,
-                     weak_ptr_factory_.GetWeakPtr()));
+                     weak_ptr_factory_.GetWeakPtr(), std::move(service),
+                     std::move(web_controller)));
 }
 
 void HeadlessScriptControllerImpl::OnReadyToStart(
+    std::unique_ptr<Service> service,
+    std::unique_ptr<WebController> web_controller,
     bool can_start,
     absl::optional<GURL> url,
     std::unique_ptr<TriggerContext> trigger_context) {
@@ -89,7 +92,8 @@
   // TODO(b/201964911): At this point we should be sure no other Controller
   // exists on this tab. Add logic to the starter to check that's the case.
   client_->Start(
-      *url, std::move(trigger_context),
+      *url, std::move(trigger_context), std::move(service),
+      std::move(web_controller),
       base::BindOnce(&HeadlessScriptControllerImpl::NotifyScriptEnded,
                      weak_ptr_factory_.GetWeakPtr()));
 }
diff --git a/components/autofill_assistant/browser/headless/headless_script_controller_impl.h b/components/autofill_assistant/browser/headless/headless_script_controller_impl.h
index 55b8ffb2..7a15391c 100644
--- a/components/autofill_assistant/browser/headless/headless_script_controller_impl.h
+++ b/components/autofill_assistant/browser/headless/headless_script_controller_impl.h
@@ -11,24 +11,24 @@
 
 #include "base/callback_helpers.h"
 #include "base/memory/raw_ptr.h"
-#include "components/autofill_assistant/browser/autofill_assistant_impl.h"
-#include "components/autofill_assistant/browser/controller.h"
-#include "components/autofill_assistant/browser/controller_observer.h"
-#include "components/autofill_assistant/browser/execution_delegate.h"
-#include "components/autofill_assistant/browser/headless/client_headless.h"
 #include "components/autofill_assistant/browser/metrics.h"
-#include "components/autofill_assistant/browser/script_executor_ui_delegate.h"
+#include "components/autofill_assistant/browser/public/headless_script_controller.h"
 
 namespace autofill_assistant {
 
-class WebsiteLoginManager;
+class ClientHeadless;
+class Service;
+class Starter;
+class WebController;
+class TriggerContext;
+class HeadlessScriptControllerImplTest;
 
 class HeadlessScriptControllerImpl : public HeadlessScriptController {
  public:
-  HeadlessScriptControllerImpl(
-      content::WebContents* web_contents,
-      ExternalActionDelegate* action_extension_delegate,
-      WebsiteLoginManager* website_login_manager);
+  // |starter| must outlive this instance.
+  HeadlessScriptControllerImpl(content::WebContents* web_contents,
+                               Starter* starter,
+                               std::unique_ptr<ClientHeadless> client);
 
   HeadlessScriptControllerImpl(const HeadlessScriptControllerImpl&) = delete;
   HeadlessScriptControllerImpl& operator=(const HeadlessScriptControllerImpl&) =
@@ -47,7 +47,19 @@
       base::OnceCallback<void()> onboarding_successful_callback) override;
 
  private:
-  void OnReadyToStart(bool can_start,
+  friend HeadlessScriptControllerImplTest;
+
+  void StartScript(
+      const base::flat_map<std::string, std::string>& script_parameters,
+      base::OnceCallback<void(ScriptResult)> script_ended_callback,
+      bool use_autofill_assistant_onboarding,
+      base::OnceCallback<void()> onboarding_successful_callback,
+      std::unique_ptr<Service> service,
+      std::unique_ptr<WebController> web_controller);
+
+  void OnReadyToStart(std::unique_ptr<Service> service,
+                      std::unique_ptr<WebController> web_controller,
+                      bool can_start,
                       absl::optional<GURL> url,
                       std::unique_ptr<TriggerContext> trigger_context);
 
@@ -57,6 +69,7 @@
   void NotifyScriptEnded(Metrics::DropOutReason reason);
 
   raw_ptr<content::WebContents> web_contents_;
+  raw_ptr<Starter> starter_;
   std::unique_ptr<ClientHeadless> client_;
 
   base::OnceCallback<void(ScriptResult)> script_ended_callback_;
diff --git a/components/autofill_assistant/browser/headless/headless_script_controller_impl_unittest.cc b/components/autofill_assistant/browser/headless/headless_script_controller_impl_unittest.cc
new file mode 100644
index 0000000..d7c862b7
--- /dev/null
+++ b/components/autofill_assistant/browser/headless/headless_script_controller_impl_unittest.cc
@@ -0,0 +1,241 @@
+// 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_assistant/browser/headless/headless_script_controller_impl.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/callback_helpers.h"
+#include "base/containers/flat_map.h"
+#include "base/guid.h"
+#include "base/memory/raw_ptr.h"
+#include "base/run_loop.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/bind.h"
+#include "base/test/gmock_callback_support.h"
+#include "base/test/gtest_util.h"
+#include "base/test/mock_callback.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
+#include "base/time/time.h"
+#include "components/autofill/core/browser/autofill_test_utils.h"
+#include "components/autofill/core/browser/field_types.h"
+#include "components/autofill_assistant/browser/cud_condition.pb.h"
+#include "components/autofill_assistant/browser/device_context.h"
+#include "components/autofill_assistant/browser/fake_script_executor_ui_delegate.h"
+#include "components/autofill_assistant/browser/fake_starter_platform_delegate.h"
+#include "components/autofill_assistant/browser/features.h"
+#include "components/autofill_assistant/browser/headless/client_headless.h"
+#include "components/autofill_assistant/browser/mock_autofill_assistant_tts_controller.h"
+#include "components/autofill_assistant/browser/mock_client.h"
+#include "components/autofill_assistant/browser/mock_controller_observer.h"
+#include "components/autofill_assistant/browser/mock_personal_data_manager.h"
+#include "components/autofill_assistant/browser/public/mock_runtime_manager.h"
+#include "components/autofill_assistant/browser/service/mock_service.h"
+#include "components/autofill_assistant/browser/service/service.h"
+#include "components/autofill_assistant/browser/starter.h"
+#include "components/autofill_assistant/browser/switches.h"
+#include "components/autofill_assistant/browser/test_util.h"
+#include "components/autofill_assistant/browser/trigger_context.h"
+#include "components/autofill_assistant/browser/ukm_test_util.h"
+#include "components/autofill_assistant/browser/web/mock_web_controller.h"
+#include "components/password_manager/core/browser/mock_password_change_success_tracker.h"
+#include "components/strings/grit/components_strings.h"
+#include "components/ukm/content/source_url_recorder.h"
+#include "components/ukm/test_ukm_recorder.h"
+#include "content/public/test/navigation_simulator.h"
+#include "content/public/test/prerender_test_util.h"
+#include "content/public/test/test_browser_context.h"
+#include "content/public/test/test_renderer_host.h"
+#include "content/public/test/web_contents_tester.h"
+#include "net/http/http_status_code.h"
+#include "services/metrics/public/cpp/metrics_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "third_party/blink/public/common/features.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace autofill_assistant {
+
+using ::base::test::RunOnceCallback;
+using ::testing::_;
+using ::testing::AllOf;
+using ::testing::AnyNumber;
+using ::testing::DoAll;
+using ::testing::ElementsAre;
+using ::testing::ElementsAreArray;
+using ::testing::Eq;
+using ::testing::Field;
+using ::testing::Gt;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::IsEmpty;
+using ::testing::NiceMock;
+using ::testing::Not;
+using ::testing::NotNull;
+using ::testing::Property;
+using ::testing::Return;
+using ::testing::SaveArg;
+using ::testing::Sequence;
+using ::testing::SizeIs;
+using ::testing::StrEq;
+using ::testing::UnorderedElementsAre;
+using ::testing::WithArgs;
+
+const char kExampleDeeplink[] = "https://www.example.com";
+
+class HeadlessScriptControllerImplTest : public testing::Test {
+ public:
+  void SetUp() override {
+    web_contents_ = content::WebContentsTester::CreateTestWebContents(
+        &browser_context_, nullptr);
+    mock_runtime_manager_ = std::make_unique<MockRuntimeManager>();
+
+    starter_ = std::make_unique<Starter>(
+        web_contents(), fake_platform_delegate_.GetWeakPtr(), &ukm_recorder_,
+        mock_runtime_manager_->GetWeakPtr(),
+        task_environment_.GetMockTickClock());
+
+    mock_service_to_inject_ = std::make_unique<NiceMock<MockService>>();
+    mock_service_ = mock_service_to_inject_.get();
+    // Fetching scripts succeeds for all URLs, but return nothing.
+    ON_CALL(*mock_service_, GetScriptsForUrl)
+        .WillByDefault(RunOnceCallback<2>(
+            net::HTTP_OK, "", ServiceRequestSender::ResponseInfo{}));
+
+    // Scripts run, but have no actions.
+    ON_CALL(*mock_service_, GetActions)
+        .WillByDefault(RunOnceCallback<5>(
+            net::HTTP_OK, "", ServiceRequestSender::ResponseInfo{}));
+    ON_CALL(*mock_service_, GetNextActions)
+        .WillByDefault(RunOnceCallback<6>(
+            net::HTTP_OK, "", ServiceRequestSender::ResponseInfo{}));
+
+    mock_web_controller_to_inject_ =
+        std::make_unique<NiceMock<MockWebController>>();
+    mock_web_controller_ = mock_web_controller_to_inject_.get();
+    ON_CALL(*mock_web_controller_, FindElement)
+        .WillByDefault(RunOnceCallback<2>(ClientStatus(), nullptr));
+
+    auto client = std::make_unique<ClientHeadless>(
+        web_contents_.get(), starter_->GetCommonDependencies(), nullptr,
+        nullptr, task_environment_.GetMockTickClock(),
+        mock_runtime_manager_->GetWeakPtr(), &ukm_recorder_, nullptr);
+    headless_script_controller_ =
+        std::make_unique<HeadlessScriptControllerImpl>(
+            web_contents_.get(), starter_.get(), std::move(client));
+  }
+
+  content::WebContents* web_contents() { return web_contents_.get(); }
+
+  // Note that calling this method moves |service_| and |web_controller_| so
+  // it should not be called more than once per test.
+  void Start(const base::flat_map<std::string, std::string>& params,
+             bool expect_success) {
+    // Since the callback is often called in a PostTask, we use this to make
+    // sure the test does not fininsh before the callback is called.
+    base::RunLoop run_loop;
+
+    EXPECT_CALL(mock_script_ended_callback_, Run)
+        .WillOnce([&run_loop, &expect_success](
+                      HeadlessScriptController::ScriptResult result) {
+          EXPECT_EQ(result.success, expect_success);
+          run_loop.Quit();
+        });
+    headless_script_controller_->StartScript(
+        params, mock_script_ended_callback_.Get(),
+        /* use_autofill_assistant_onboarding = */ false, base::DoNothing(),
+        std::move(mock_service_to_inject_),
+        std::move(mock_web_controller_to_inject_));
+    run_loop.Run();
+  }
+
+  void SetupScripts(SupportsScriptResponseProto scripts) {
+    EXPECT_CALL(*mock_service_, GetScriptsForUrl)
+        .WillOnce(RunOnceCallback<2>(net::HTTP_OK, scripts.SerializeAsString(),
+                                     ServiceRequestSender::ResponseInfo{}));
+  }
+
+  void SetupActionsForScript(const std::string& path,
+                             ActionsResponseProto actions_response) {
+    EXPECT_CALL(*mock_service_, GetActions(StrEq(path), _, _, _, _, _))
+        .WillOnce(RunOnceCallback<5>(net::HTTP_OK,
+                                     actions_response.SerializeAsString(),
+                                     ServiceRequestSender::ResponseInfo{}));
+  }
+
+  static SupportedScriptProto* AddRunnableScript(
+      SupportsScriptResponseProto* response,
+      const std::string& name_and_path,
+      bool direct_action = true) {
+    SupportedScriptProto* script = response->add_scripts();
+    script->set_path(name_and_path);
+    if (direct_action) {
+      script->mutable_presentation()->mutable_direct_action()->add_names(
+          name_and_path);
+    }
+    return script;
+  }
+
+ protected:
+  content::BrowserTaskEnvironment task_environment_{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+  content::RenderViewHostTestEnabler rvh_test_enabler_;
+  content::TestBrowserContext browser_context_;
+  std::unique_ptr<content::WebContents> web_contents_;
+  base::MockCallback<
+      base::OnceCallback<void(HeadlessScriptController::ScriptResult)>>
+      mock_script_ended_callback_;
+  FakeStarterPlatformDelegate fake_platform_delegate_;
+  std::unique_ptr<MockRuntimeManager> mock_runtime_manager_;
+  std::unique_ptr<Starter> starter_;
+  std::unique_ptr<HeadlessScriptControllerImpl> headless_script_controller_;
+  ukm::TestAutoSetUkmRecorder ukm_recorder_;
+  raw_ptr<MockService> mock_service_;
+  raw_ptr<MockWebController> mock_web_controller_;
+
+ private:
+  // These will be moved when the |Start| method is called, so expectations
+  // should be written using |mock_service| and |mock_web_controller_| instead.
+  std::unique_ptr<MockService> mock_service_to_inject_;
+  std::unique_ptr<MockWebController> mock_web_controller_to_inject_;
+};
+
+TEST_F(HeadlessScriptControllerImplTest,
+       StartFailsWithoutMandatoryScriptParameter) {
+  // The startup will fail because we are missing the initial URL.
+  base::flat_map<std::string, std::string> params = {
+      {"ENABLED", "true"}, {"START_IMMEDIATELY", "true"}};
+  Start(params, /* expect_success= */ false);
+}
+
+TEST_F(HeadlessScriptControllerImplTest, StartFailsIfNoScriptsAvailable) {
+  EXPECT_CALL(*mock_service_, GetScriptsForUrl)
+      .WillOnce(RunOnceCallback<2>(net::HTTP_OK, "",
+                                   ServiceRequestSender::ResponseInfo{}));
+  base::flat_map<std::string, std::string> params = {
+      {"ENABLED", "true"},
+      {"START_IMMEDIATELY", "true"},
+      {"ORIGINAL_DEEPLINK", kExampleDeeplink}};
+  Start(params, /* expect_success= */ false);
+}
+
+TEST_F(HeadlessScriptControllerImplTest, SuccessfulRun) {
+  SupportsScriptResponseProto script_response;
+  auto* script = AddRunnableScript(&script_response, "script");
+  script->mutable_presentation()->set_autostart(true);
+  SetupScripts(script_response);
+
+  ActionsResponseProto script_actions;
+  script_actions.add_actions()->mutable_stop();
+  SetupActionsForScript("script", script_actions);
+
+  base::flat_map<std::string, std::string> params = {
+      {"ENABLED", "true"},
+      {"START_IMMEDIATELY", "true"},
+      {"ORIGINAL_DEEPLINK", kExampleDeeplink}};
+  Start(params, /* expect_success= */ true);
+}
+
+}  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/public/BUILD.gn b/components/autofill_assistant/browser/public/BUILD.gn
index 3477ce98..790170a 100644
--- a/components/autofill_assistant/browser/public/BUILD.gn
+++ b/components/autofill_assistant/browser/public/BUILD.gn
@@ -28,9 +28,13 @@
 
   deps = [
     ":proto",
-    "//base",
     "//components/autofill_assistant/browser/public/fast_checkout/proto:proto",
     "//components/autofill_assistant/browser/public/password_change/proto:proto",
+    "//url",
+  ]
+
+  public_deps = [
+    "//base",
     "//components/version_info:channel",
     "//content/public/browser",
   ]
diff --git a/components/autofill_assistant/browser/public/autofill_assistant.cc b/components/autofill_assistant/browser/public/autofill_assistant.cc
index 0d3b10b..388f625 100644
--- a/components/autofill_assistant/browser/public/autofill_assistant.cc
+++ b/components/autofill_assistant/browser/public/autofill_assistant.cc
@@ -10,6 +10,7 @@
 #include "base/containers/span.h"
 #include "base/hash/legacy_hash.h"
 #include "base/strings/string_util.h"
+#include "url/gurl.h"
 #include "url/origin.h"
 
 namespace autofill_assistant {
diff --git a/components/autofill_assistant/browser/public/autofill_assistant_factory.h b/components/autofill_assistant/browser/public/autofill_assistant_factory.h
index 34a8afaf..090c7cc 100644
--- a/components/autofill_assistant/browser/public/autofill_assistant_factory.h
+++ b/components/autofill_assistant/browser/public/autofill_assistant_factory.h
@@ -9,7 +9,10 @@
 
 #include "components/autofill_assistant/browser/public/autofill_assistant.h"
 #include "components/version_info/channel.h"
-#include "content/public/browser/browser_context.h"
+
+namespace content {
+class BrowserContext;
+}  // namespace content
 
 namespace autofill_assistant {
 
diff --git a/components/autofill_assistant/browser/public/headless_script_controller.h b/components/autofill_assistant/browser/public/headless_script_controller.h
index e8cf067..6a110dfb 100644
--- a/components/autofill_assistant/browser/public/headless_script_controller.h
+++ b/components/autofill_assistant/browser/public/headless_script_controller.h
@@ -5,11 +5,11 @@
 #ifndef COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_PUBLIC_HEADLESS_SCRIPT_CONTROLLER_H_
 #define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_PUBLIC_HEADLESS_SCRIPT_CONTROLLER_H_
 
+#include "base/containers/flat_map.h"
 #include "base/memory/weak_ptr.h"
 #include "components/autofill_assistant/browser/public/external_action.pb.h"
 #include "components/autofill_assistant/browser/public/runtime_observer.h"
 #include "components/autofill_assistant/browser/public/ui_state.h"
-#include "content/public/browser/web_contents.h"
 
 namespace autofill_assistant {
 
diff --git a/components/autofill_assistant/browser/public/runtime_manager.cc b/components/autofill_assistant/browser/public/runtime_manager.cc
index 75431db..3f60376 100644
--- a/components/autofill_assistant/browser/public/runtime_manager.cc
+++ b/components/autofill_assistant/browser/public/runtime_manager.cc
@@ -3,7 +3,9 @@
 // found in the LICENSE file.
 
 #include "components/autofill_assistant/browser/public/runtime_manager.h"
+
 #include "components/autofill_assistant/browser/public/runtime_manager_impl.h"
+#include "content/public/browser/web_contents.h"
 
 namespace autofill_assistant {
 
diff --git a/components/autofill_assistant/browser/public/runtime_manager.h b/components/autofill_assistant/browser/public/runtime_manager.h
index acacfbd..9fbfce8 100644
--- a/components/autofill_assistant/browser/public/runtime_manager.h
+++ b/components/autofill_assistant/browser/public/runtime_manager.h
@@ -8,7 +8,10 @@
 #include "base/memory/weak_ptr.h"
 #include "components/autofill_assistant/browser/public/runtime_observer.h"
 #include "components/autofill_assistant/browser/public/ui_state.h"
-#include "content/public/browser/web_contents.h"
+
+namespace content {
+class WebContents;
+}  // namespace content
 
 namespace autofill_assistant {
 // Notifies subscribed observers when the UI state changes.
diff --git a/components/autofill_assistant/browser/public/runtime_manager_impl.h b/components/autofill_assistant/browser/public/runtime_manager_impl.h
index 52a009d..ee50393 100644
--- a/components/autofill_assistant/browser/public/runtime_manager_impl.h
+++ b/components/autofill_assistant/browser/public/runtime_manager_impl.h
@@ -13,7 +13,9 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_user_data.h"
 
-// TODO: Move implementation to internal/.
+// TODO: Move implementation to internal/. When that is done, the public
+// dependency on content for the build target autofill_assistant/browser:public
+// can be removed.
 namespace autofill_assistant {
 class RuntimeManagerImpl
     : public RuntimeManager,
diff --git a/components/browser_ui/media/android/BUILD.gn b/components/browser_ui/media/android/BUILD.gn
index 3ea9e3d..747d30d 100644
--- a/components/browser_ui/media/android/BUILD.gn
+++ b/components/browser_ui/media/android/BUILD.gn
@@ -40,59 +40,14 @@
   sources = [
     "java/res/drawable-hdpi/audio_playing.png",
     "java/res/drawable-hdpi/audio_playing_square.png",
-    "java/res/drawable-hdpi/ic_call_end_white_36dp.png",
-    "java/res/drawable-hdpi/ic_fast_forward_white_36dp.png",
-    "java/res/drawable-hdpi/ic_fast_rewind_white_36dp.png",
-    "java/res/drawable-hdpi/ic_mic_off_white_36dp.png",
-    "java/res/drawable-hdpi/ic_mic_white_36dp.png",
-    "java/res/drawable-hdpi/ic_skip_next_white_36dp.png",
-    "java/res/drawable-hdpi/ic_skip_previous_white_36dp.png",
-    "java/res/drawable-hdpi/ic_videocam_off_white_36dp.png",
-    "java/res/drawable-hdpi/ic_videocam_white_36dp.png",
     "java/res/drawable-mdpi/audio_playing.png",
     "java/res/drawable-mdpi/audio_playing_square.png",
-    "java/res/drawable-mdpi/ic_call_end_white_36dp.png",
-    "java/res/drawable-mdpi/ic_fast_forward_white_36dp.png",
-    "java/res/drawable-mdpi/ic_fast_rewind_white_36dp.png",
-    "java/res/drawable-mdpi/ic_mic_off_white_36dp.png",
-    "java/res/drawable-mdpi/ic_mic_white_36dp.png",
-    "java/res/drawable-mdpi/ic_skip_next_white_36dp.png",
-    "java/res/drawable-mdpi/ic_skip_previous_white_36dp.png",
-    "java/res/drawable-mdpi/ic_videocam_off_white_36dp.png",
-    "java/res/drawable-mdpi/ic_videocam_white_36dp.png",
     "java/res/drawable-xhdpi/audio_playing.png",
     "java/res/drawable-xhdpi/audio_playing_square.png",
-    "java/res/drawable-xhdpi/ic_call_end_white_36dp.png",
-    "java/res/drawable-xhdpi/ic_fast_forward_white_36dp.png",
-    "java/res/drawable-xhdpi/ic_fast_rewind_white_36dp.png",
-    "java/res/drawable-xhdpi/ic_mic_off_white_36dp.png",
-    "java/res/drawable-xhdpi/ic_mic_white_36dp.png",
-    "java/res/drawable-xhdpi/ic_skip_next_white_36dp.png",
-    "java/res/drawable-xhdpi/ic_skip_previous_white_36dp.png",
-    "java/res/drawable-xhdpi/ic_videocam_off_white_36dp.png",
-    "java/res/drawable-xhdpi/ic_videocam_white_36dp.png",
     "java/res/drawable-xxhdpi/audio_playing.png",
     "java/res/drawable-xxhdpi/audio_playing_square.png",
-    "java/res/drawable-xxhdpi/ic_call_end_white_36dp.png",
-    "java/res/drawable-xxhdpi/ic_fast_forward_white_36dp.png",
-    "java/res/drawable-xxhdpi/ic_fast_rewind_white_36dp.png",
-    "java/res/drawable-xxhdpi/ic_mic_off_white_36dp.png",
-    "java/res/drawable-xxhdpi/ic_mic_white_36dp.png",
-    "java/res/drawable-xxhdpi/ic_skip_next_white_36dp.png",
-    "java/res/drawable-xxhdpi/ic_skip_previous_white_36dp.png",
-    "java/res/drawable-xxhdpi/ic_videocam_off_white_36dp.png",
-    "java/res/drawable-xxhdpi/ic_videocam_white_36dp.png",
     "java/res/drawable-xxxhdpi/audio_playing.png",
     "java/res/drawable-xxxhdpi/audio_playing_square.png",
-    "java/res/drawable-xxxhdpi/ic_call_end_white_36dp.png",
-    "java/res/drawable-xxxhdpi/ic_fast_forward_white_36dp.png",
-    "java/res/drawable-xxxhdpi/ic_fast_rewind_white_36dp.png",
-    "java/res/drawable-xxxhdpi/ic_mic_off_white_36dp.png",
-    "java/res/drawable-xxxhdpi/ic_mic_white_36dp.png",
-    "java/res/drawable-xxxhdpi/ic_skip_next_white_36dp.png",
-    "java/res/drawable-xxxhdpi/ic_skip_previous_white_36dp.png",
-    "java/res/drawable-xxxhdpi/ic_videocam_off_white_36dp.png",
-    "java/res/drawable-xxxhdpi/ic_videocam_white_36dp.png",
   ]
   deps = [
     "//components/browser_ui/strings/android:browser_ui_strings_grd",
diff --git a/components/browser_ui/media/android/java/res/drawable-hdpi/ic_call_end_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-hdpi/ic_call_end_white_36dp.png
deleted file mode 100644
index 90773818..0000000
--- a/components/browser_ui/media/android/java/res/drawable-hdpi/ic_call_end_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-hdpi/ic_fast_forward_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-hdpi/ic_fast_forward_white_36dp.png
deleted file mode 100644
index 6a7db4b..0000000
--- a/components/browser_ui/media/android/java/res/drawable-hdpi/ic_fast_forward_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-hdpi/ic_fast_rewind_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-hdpi/ic_fast_rewind_white_36dp.png
deleted file mode 100644
index 656d022..0000000
--- a/components/browser_ui/media/android/java/res/drawable-hdpi/ic_fast_rewind_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-hdpi/ic_mic_off_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-hdpi/ic_mic_off_white_36dp.png
deleted file mode 100644
index c0e773b..0000000
--- a/components/browser_ui/media/android/java/res/drawable-hdpi/ic_mic_off_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-hdpi/ic_mic_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-hdpi/ic_mic_white_36dp.png
deleted file mode 100644
index 2b377a74..0000000
--- a/components/browser_ui/media/android/java/res/drawable-hdpi/ic_mic_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-hdpi/ic_skip_next_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-hdpi/ic_skip_next_white_36dp.png
deleted file mode 100644
index cf68df8..0000000
--- a/components/browser_ui/media/android/java/res/drawable-hdpi/ic_skip_next_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-hdpi/ic_skip_previous_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-hdpi/ic_skip_previous_white_36dp.png
deleted file mode 100644
index da1c1c95..0000000
--- a/components/browser_ui/media/android/java/res/drawable-hdpi/ic_skip_previous_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-hdpi/ic_videocam_off_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-hdpi/ic_videocam_off_white_36dp.png
deleted file mode 100644
index fafc3a3..0000000
--- a/components/browser_ui/media/android/java/res/drawable-hdpi/ic_videocam_off_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-hdpi/ic_videocam_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-hdpi/ic_videocam_white_36dp.png
deleted file mode 100644
index 5c99f19..0000000
--- a/components/browser_ui/media/android/java/res/drawable-hdpi/ic_videocam_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-mdpi/ic_call_end_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-mdpi/ic_call_end_white_36dp.png
deleted file mode 100644
index 8fb6ffd..0000000
--- a/components/browser_ui/media/android/java/res/drawable-mdpi/ic_call_end_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-mdpi/ic_fast_forward_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-mdpi/ic_fast_forward_white_36dp.png
deleted file mode 100644
index f890f113..0000000
--- a/components/browser_ui/media/android/java/res/drawable-mdpi/ic_fast_forward_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-mdpi/ic_fast_rewind_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-mdpi/ic_fast_rewind_white_36dp.png
deleted file mode 100644
index 9d02d43..0000000
--- a/components/browser_ui/media/android/java/res/drawable-mdpi/ic_fast_rewind_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-mdpi/ic_mic_off_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-mdpi/ic_mic_off_white_36dp.png
deleted file mode 100644
index 153d979f..0000000
--- a/components/browser_ui/media/android/java/res/drawable-mdpi/ic_mic_off_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-mdpi/ic_mic_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-mdpi/ic_mic_white_36dp.png
deleted file mode 100644
index d3d9dc2b..0000000
--- a/components/browser_ui/media/android/java/res/drawable-mdpi/ic_mic_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-mdpi/ic_skip_next_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-mdpi/ic_skip_next_white_36dp.png
deleted file mode 100644
index 9032328..0000000
--- a/components/browser_ui/media/android/java/res/drawable-mdpi/ic_skip_next_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-mdpi/ic_skip_previous_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-mdpi/ic_skip_previous_white_36dp.png
deleted file mode 100644
index 23faeeb..0000000
--- a/components/browser_ui/media/android/java/res/drawable-mdpi/ic_skip_previous_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-mdpi/ic_videocam_off_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-mdpi/ic_videocam_off_white_36dp.png
deleted file mode 100644
index b09d4dd33..0000000
--- a/components/browser_ui/media/android/java/res/drawable-mdpi/ic_videocam_off_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-mdpi/ic_videocam_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-mdpi/ic_videocam_white_36dp.png
deleted file mode 100644
index f4e905c55..0000000
--- a/components/browser_ui/media/android/java/res/drawable-mdpi/ic_videocam_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_call_end_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_call_end_white_36dp.png
deleted file mode 100644
index ff84f1f..0000000
--- a/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_call_end_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_fast_forward_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_fast_forward_white_36dp.png
deleted file mode 100644
index f7d810f..0000000
--- a/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_fast_forward_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_fast_rewind_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_fast_rewind_white_36dp.png
deleted file mode 100644
index 12ff39a..0000000
--- a/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_fast_rewind_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_mic_off_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_mic_off_white_36dp.png
deleted file mode 100644
index 89ec023..0000000
--- a/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_mic_off_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_mic_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_mic_white_36dp.png
deleted file mode 100644
index d79f5bb..0000000
--- a/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_mic_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_skip_next_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_skip_next_white_36dp.png
deleted file mode 100644
index 972192d..0000000
--- a/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_skip_next_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_skip_previous_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_skip_previous_white_36dp.png
deleted file mode 100644
index 1181ec9..0000000
--- a/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_skip_previous_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_videocam_off_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_videocam_off_white_36dp.png
deleted file mode 100644
index b305b70..0000000
--- a/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_videocam_off_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_videocam_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_videocam_white_36dp.png
deleted file mode 100644
index 646115b..0000000
--- a/components/browser_ui/media/android/java/res/drawable-xhdpi/ic_videocam_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_call_end_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_call_end_white_36dp.png
deleted file mode 100644
index 3213989..0000000
--- a/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_call_end_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_fast_forward_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_fast_forward_white_36dp.png
deleted file mode 100644
index b41b3de4..0000000
--- a/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_fast_forward_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_fast_rewind_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_fast_rewind_white_36dp.png
deleted file mode 100644
index 253833b..0000000
--- a/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_fast_rewind_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_mic_off_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_mic_off_white_36dp.png
deleted file mode 100644
index 03cb6a6..0000000
--- a/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_mic_off_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_mic_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_mic_white_36dp.png
deleted file mode 100644
index fc3b9246..0000000
--- a/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_mic_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_skip_next_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_skip_next_white_36dp.png
deleted file mode 100644
index 4652215..0000000
--- a/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_skip_next_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_skip_previous_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_skip_previous_white_36dp.png
deleted file mode 100644
index c8db47f..0000000
--- a/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_skip_previous_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_videocam_off_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_videocam_off_white_36dp.png
deleted file mode 100644
index 54378c0..0000000
--- a/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_videocam_off_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_videocam_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_videocam_white_36dp.png
deleted file mode 100644
index 60f37bc..0000000
--- a/components/browser_ui/media/android/java/res/drawable-xxhdpi/ic_videocam_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_call_end_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_call_end_white_36dp.png
deleted file mode 100644
index ad9f949f..0000000
--- a/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_call_end_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_fast_forward_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_fast_forward_white_36dp.png
deleted file mode 100644
index b111f7d..0000000
--- a/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_fast_forward_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_fast_rewind_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_fast_rewind_white_36dp.png
deleted file mode 100644
index e1baaa3..0000000
--- a/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_fast_rewind_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_mic_off_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_mic_off_white_36dp.png
deleted file mode 100644
index 533c60e..0000000
--- a/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_mic_off_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_mic_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_mic_white_36dp.png
deleted file mode 100644
index 5ec10394..0000000
--- a/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_mic_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_skip_next_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_skip_next_white_36dp.png
deleted file mode 100644
index 00b29dd..0000000
--- a/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_skip_next_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_skip_previous_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_skip_previous_white_36dp.png
deleted file mode 100644
index 9e52d500..0000000
--- a/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_skip_previous_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_videocam_off_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_videocam_off_white_36dp.png
deleted file mode 100644
index 59bc5fe065..0000000
--- a/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_videocam_off_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_videocam_white_36dp.png b/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_videocam_white_36dp.png
deleted file mode 100644
index 3372697..0000000
--- a/components/browser_ui/media/android/java/res/drawable-xxxhdpi/ic_videocam_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaNotificationController.java b/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaNotificationController.java
index adf3ba6d..87161f3 100644
--- a/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaNotificationController.java
+++ b/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaNotificationController.java
@@ -331,28 +331,28 @@
         mActionToButtonInfo = new SparseArray<>();
 
         mActionToButtonInfo.put(MediaSessionAction.PLAY,
-                new MediaButtonInfo(R.drawable.ic_play_arrow_white_36dp,
+                new MediaButtonInfo(R.drawable.ic_play_arrow_white_24dp,
                         R.string.accessibility_play, ACTION_PLAY, MEDIA_ACTION_PLAY));
         mActionToButtonInfo.put(MediaSessionAction.PAUSE,
-                new MediaButtonInfo(R.drawable.ic_pause_white_36dp, R.string.accessibility_pause,
+                new MediaButtonInfo(R.drawable.ic_pause_white_24dp, R.string.accessibility_pause,
                         ACTION_PAUSE, MEDIA_ACTION_PAUSE));
         mActionToButtonInfo.put(MediaSessionAction.STOP,
-                new MediaButtonInfo(R.drawable.ic_stop_white_36dp, R.string.accessibility_stop,
+                new MediaButtonInfo(R.drawable.ic_stop_white_24dp, R.string.accessibility_stop,
                         ACTION_STOP, MEDIA_ACTION_STOP));
         mActionToButtonInfo.put(MediaSessionAction.PREVIOUS_TRACK,
-                new MediaButtonInfo(R.drawable.ic_skip_previous_white_36dp,
+                new MediaButtonInfo(R.drawable.ic_skip_previous_white_24dp,
                         R.string.accessibility_previous_track, ACTION_PREVIOUS_TRACK,
                         MEDIA_ACTION_PREVIOUS_TRACK));
         mActionToButtonInfo.put(MediaSessionAction.NEXT_TRACK,
-                new MediaButtonInfo(R.drawable.ic_skip_next_white_36dp,
+                new MediaButtonInfo(R.drawable.ic_skip_next_white_24dp,
                         R.string.accessibility_next_track, ACTION_NEXT_TRACK,
                         MEDIA_ACTION_NEXT_TRACK));
         mActionToButtonInfo.put(MediaSessionAction.SEEK_FORWARD,
-                new MediaButtonInfo(R.drawable.ic_fast_forward_white_36dp,
+                new MediaButtonInfo(R.drawable.ic_fast_forward_white_24dp,
                         R.string.accessibility_seek_forward, ACTION_SEEK_FORWARD,
                         MEDIA_ACTION_SEEK_FORWARD));
         mActionToButtonInfo.put(MediaSessionAction.SEEK_BACKWARD,
-                new MediaButtonInfo(R.drawable.ic_fast_rewind_white_36dp,
+                new MediaButtonInfo(R.drawable.ic_fast_rewind_white_24dp,
                         R.string.accessibility_seek_backward, ACTION_SEEK_BACKWARD,
                         MEDIA_ACTION_SEEK_BACKWARD));
 
diff --git a/components/browser_ui/photo_picker/android/java/res/layout/video_player.xml b/components/browser_ui/photo_picker/android/java/res/layout/video_player.xml
index a8db9fe..49ac0531 100644
--- a/components/browser_ui/photo_picker/android/java/res/layout/video_player.xml
+++ b/components/browser_ui/photo_picker/android/java/res/layout/video_player.xml
@@ -66,7 +66,7 @@
                     android:layout_height="24dp"
                     android:layout_marginEnd="8dp"
                     android:contentDescription="@string/accessibility_pause_video"
-                    app:srcCompat="@drawable/ic_fast_rewind_white_36dp" />
+                    app:srcCompat="@drawable/ic_fast_rewind_white_24dp" />
                 <TextView
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
@@ -78,7 +78,7 @@
                     android:layout_height="24dp"
                     android:layout_marginStart="8dp"
                     android:contentDescription="@string/accessibility_pause_video"
-                    app:srcCompat="@drawable/ic_fast_forward_white_36dp" />
+                    app:srcCompat="@drawable/ic_fast_forward_white_24dp" />
             </LinearLayout>
 
             <ImageView
diff --git a/components/browser_ui/styles/android/BUILD.gn b/components/browser_ui/styles/android/BUILD.gn
index b40575d7..3b7d43c 100644
--- a/components/browser_ui/styles/android/BUILD.gn
+++ b/components/browser_ui/styles/android/BUILD.gn
@@ -58,14 +58,11 @@
     "java/res/drawable-hdpi/ic_delete_white_24dp.png",
     "java/res/drawable-hdpi/ic_edit_24dp.png",
     "java/res/drawable-hdpi/ic_logo_googleg_24dp.png",
-    "java/res/drawable-hdpi/ic_pause_white_24dp.png",
     "java/res/drawable-hdpi/ic_pause_white_36dp.png",
-    "java/res/drawable-hdpi/ic_play_arrow_white_24dp.png",
     "java/res/drawable-hdpi/ic_play_arrow_white_36dp.png",
     "java/res/drawable-hdpi/ic_refresh_white_24dp.png",
     "java/res/drawable-hdpi/ic_refresh_white_36dp.png",
     "java/res/drawable-hdpi/ic_share_white_24dp.png",
-    "java/res/drawable-hdpi/ic_stop_white_36dp.png",
     "java/res/drawable-hdpi/ic_storage.png",
     "java/res/drawable-hdpi/modern_toolbar_shadow.png",
     "java/res/drawable-hdpi/navigation_bubble_shadow.9.png",
@@ -84,14 +81,11 @@
     "java/res/drawable-mdpi/ic_delete_white_24dp.png",
     "java/res/drawable-mdpi/ic_edit_24dp.png",
     "java/res/drawable-mdpi/ic_logo_googleg_24dp.png",
-    "java/res/drawable-mdpi/ic_pause_white_24dp.png",
     "java/res/drawable-mdpi/ic_pause_white_36dp.png",
-    "java/res/drawable-mdpi/ic_play_arrow_white_24dp.png",
     "java/res/drawable-mdpi/ic_play_arrow_white_36dp.png",
     "java/res/drawable-mdpi/ic_refresh_white_24dp.png",
     "java/res/drawable-mdpi/ic_refresh_white_36dp.png",
     "java/res/drawable-mdpi/ic_share_white_24dp.png",
-    "java/res/drawable-mdpi/ic_stop_white_36dp.png",
     "java/res/drawable-mdpi/ic_storage.png",
     "java/res/drawable-mdpi/modern_toolbar_shadow.png",
     "java/res/drawable-mdpi/navigation_bubble_shadow.9.png",
@@ -105,14 +99,11 @@
     "java/res/drawable-xhdpi/ic_delete_white_24dp.png",
     "java/res/drawable-xhdpi/ic_edit_24dp.png",
     "java/res/drawable-xhdpi/ic_logo_googleg_24dp.png",
-    "java/res/drawable-xhdpi/ic_pause_white_24dp.png",
     "java/res/drawable-xhdpi/ic_pause_white_36dp.png",
-    "java/res/drawable-xhdpi/ic_play_arrow_white_24dp.png",
     "java/res/drawable-xhdpi/ic_play_arrow_white_36dp.png",
     "java/res/drawable-xhdpi/ic_refresh_white_24dp.png",
     "java/res/drawable-xhdpi/ic_refresh_white_36dp.png",
     "java/res/drawable-xhdpi/ic_share_white_24dp.png",
-    "java/res/drawable-xhdpi/ic_stop_white_36dp.png",
     "java/res/drawable-xhdpi/ic_storage.png",
     "java/res/drawable-xhdpi/modern_toolbar_shadow.png",
     "java/res/drawable-xhdpi/navigation_bubble_shadow.9.png",
@@ -126,14 +117,11 @@
     "java/res/drawable-xxhdpi/ic_delete_white_24dp.png",
     "java/res/drawable-xxhdpi/ic_edit_24dp.png",
     "java/res/drawable-xxhdpi/ic_logo_googleg_24dp.png",
-    "java/res/drawable-xxhdpi/ic_pause_white_24dp.png",
     "java/res/drawable-xxhdpi/ic_pause_white_36dp.png",
-    "java/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png",
     "java/res/drawable-xxhdpi/ic_play_arrow_white_36dp.png",
     "java/res/drawable-xxhdpi/ic_refresh_white_24dp.png",
     "java/res/drawable-xxhdpi/ic_refresh_white_36dp.png",
     "java/res/drawable-xxhdpi/ic_share_white_24dp.png",
-    "java/res/drawable-xxhdpi/ic_stop_white_36dp.png",
     "java/res/drawable-xxhdpi/ic_storage.png",
     "java/res/drawable-xxhdpi/modern_toolbar_shadow.png",
     "java/res/drawable-xxhdpi/navigation_bubble_shadow.9.png",
@@ -147,14 +135,11 @@
     "java/res/drawable-xxxhdpi/ic_delete_white_24dp.png",
     "java/res/drawable-xxxhdpi/ic_edit_24dp.png",
     "java/res/drawable-xxxhdpi/ic_logo_googleg_24dp.png",
-    "java/res/drawable-xxxhdpi/ic_pause_white_24dp.png",
     "java/res/drawable-xxxhdpi/ic_pause_white_36dp.png",
-    "java/res/drawable-xxxhdpi/ic_play_arrow_white_24dp.png",
     "java/res/drawable-xxxhdpi/ic_play_arrow_white_36dp.png",
     "java/res/drawable-xxxhdpi/ic_refresh_white_24dp.png",
     "java/res/drawable-xxxhdpi/ic_refresh_white_36dp.png",
     "java/res/drawable-xxxhdpi/ic_share_white_24dp.png",
-    "java/res/drawable-xxxhdpi/ic_stop_white_36dp.png",
     "java/res/drawable-xxxhdpi/ic_storage.png",
     "java/res/drawable-xxxhdpi/navigation_bubble_shadow.9.png",
     "java/res/drawable-xxxhdpi/plus.png",
@@ -171,6 +156,7 @@
     "java/res/drawable/ic_bar_chart_24dp.xml",
     "java/res/drawable/ic_brightness_medium_24dp.xml",
     "java/res/drawable/ic_business.xml",
+    "java/res/drawable/ic_call_end_white_24dp.xml",
     "java/res/drawable/ic_collections_grey.xml",
     "java/res/drawable/ic_data_viz_grey.xml",
     "java/res/drawable/ic_desktop_windows.xml",
@@ -180,6 +166,8 @@
     "java/res/drawable/ic_drive_file_24dp.xml",
     "java/res/drawable/ic_drive_image_24dp.xml",
     "java/res/drawable/ic_eye_crossed.xml",
+    "java/res/drawable/ic_fast_forward_white_24dp.xml",
+    "java/res/drawable/ic_fast_rewind_white_24dp.xml",
     "java/res/drawable/ic_file_download_24dp.xml",
     "java/res/drawable/ic_file_download_36dp.xml",
     "java/res/drawable/ic_flash_on_24dp.xml",
@@ -192,21 +180,29 @@
     "java/res/drawable/ic_lightbulb_24dp.xml",
     "java/res/drawable/ic_link_24dp.xml",
     "java/res/drawable/ic_logo_assistant_24dp.xml",
+    "java/res/drawable/ic_mic_off_white_24dp.xml",
+    "java/res/drawable/ic_mic_white_24dp.xml",
     "java/res/drawable/ic_music_note_24dp.xml",
     "java/res/drawable/ic_offline_pin_24dp_on_dark_bg.xml",
     "java/res/drawable/ic_offline_pin_24dp_on_light_bg.xml",
     "java/res/drawable/ic_outline_sms_24dp.xml",
+    "java/res/drawable/ic_pause_white_24dp.xml",
     "java/res/drawable/ic_photo_camera_black.xml",
+    "java/res/drawable/ic_play_arrow_white_24dp.xml",
     "java/res/drawable/ic_play_circle_filled_24dp_on_dark_bg.xml",
     "java/res/drawable/ic_play_circle_filled_24dp_on_light_bg.xml",
     "java/res/drawable/ic_remove.xml",
     "java/res/drawable/ic_replay_white_24dp.xml",
     "java/res/drawable/ic_security_grey.xml",
     "java/res/drawable/ic_settings_black.xml",
+    "java/res/drawable/ic_skip_next_white_24dp.xml",
+    "java/res/drawable/ic_skip_previous_white_24dp.xml",
     "java/res/drawable/ic_spam_reduction_24dp.xml",
+    "java/res/drawable/ic_stop_white_24dp.xml",
     "java/res/drawable/ic_tune_24dp.xml",
     "java/res/drawable/ic_update_grey.xml",
     "java/res/drawable/ic_videocam_24dp.xml",
+    "java/res/drawable/ic_videocam_off_white_24dp.xml",
     "java/res/drawable/ic_visibility_black.xml",
     "java/res/drawable/ic_visibility_off_black.xml",
     "java/res/drawable/ic_volume_off_white_24dp.xml",
diff --git a/components/browser_ui/styles/android/java/res/drawable-hdpi/ic_pause_white_24dp.png b/components/browser_ui/styles/android/java/res/drawable-hdpi/ic_pause_white_24dp.png
deleted file mode 100644
index 1701f34..0000000
--- a/components/browser_ui/styles/android/java/res/drawable-hdpi/ic_pause_white_24dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/styles/android/java/res/drawable-hdpi/ic_play_arrow_white_24dp.png b/components/browser_ui/styles/android/java/res/drawable-hdpi/ic_play_arrow_white_24dp.png
deleted file mode 100644
index 35f2e7f..0000000
--- a/components/browser_ui/styles/android/java/res/drawable-hdpi/ic_play_arrow_white_24dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/styles/android/java/res/drawable-hdpi/ic_stop_white_36dp.png b/components/browser_ui/styles/android/java/res/drawable-hdpi/ic_stop_white_36dp.png
deleted file mode 100644
index 1fcf519..0000000
--- a/components/browser_ui/styles/android/java/res/drawable-hdpi/ic_stop_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/styles/android/java/res/drawable-mdpi/ic_pause_white_24dp.png b/components/browser_ui/styles/android/java/res/drawable-mdpi/ic_pause_white_24dp.png
deleted file mode 100644
index e1169e5..0000000
--- a/components/browser_ui/styles/android/java/res/drawable-mdpi/ic_pause_white_24dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/styles/android/java/res/drawable-mdpi/ic_play_arrow_white_24dp.png b/components/browser_ui/styles/android/java/res/drawable-mdpi/ic_play_arrow_white_24dp.png
deleted file mode 100644
index cc060f1a..0000000
--- a/components/browser_ui/styles/android/java/res/drawable-mdpi/ic_play_arrow_white_24dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/styles/android/java/res/drawable-mdpi/ic_stop_white_36dp.png b/components/browser_ui/styles/android/java/res/drawable-mdpi/ic_stop_white_36dp.png
deleted file mode 100644
index dfff26c..0000000
--- a/components/browser_ui/styles/android/java/res/drawable-mdpi/ic_stop_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/styles/android/java/res/drawable-xhdpi/ic_pause_white_24dp.png b/components/browser_ui/styles/android/java/res/drawable-xhdpi/ic_pause_white_24dp.png
deleted file mode 100644
index f49aed7..0000000
--- a/components/browser_ui/styles/android/java/res/drawable-xhdpi/ic_pause_white_24dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/styles/android/java/res/drawable-xhdpi/ic_play_arrow_white_24dp.png b/components/browser_ui/styles/android/java/res/drawable-xhdpi/ic_play_arrow_white_24dp.png
deleted file mode 100644
index a3c80e7..0000000
--- a/components/browser_ui/styles/android/java/res/drawable-xhdpi/ic_play_arrow_white_24dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/styles/android/java/res/drawable-xhdpi/ic_stop_white_36dp.png b/components/browser_ui/styles/android/java/res/drawable-xhdpi/ic_stop_white_36dp.png
deleted file mode 100644
index 801d341..0000000
--- a/components/browser_ui/styles/android/java/res/drawable-xhdpi/ic_stop_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/styles/android/java/res/drawable-xxhdpi/ic_pause_white_24dp.png b/components/browser_ui/styles/android/java/res/drawable-xxhdpi/ic_pause_white_24dp.png
deleted file mode 100644
index 7192ad4..0000000
--- a/components/browser_ui/styles/android/java/res/drawable-xxhdpi/ic_pause_white_24dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/styles/android/java/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png b/components/browser_ui/styles/android/java/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png
deleted file mode 100644
index 547ef30..0000000
--- a/components/browser_ui/styles/android/java/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/styles/android/java/res/drawable-xxhdpi/ic_stop_white_36dp.png b/components/browser_ui/styles/android/java/res/drawable-xxhdpi/ic_stop_white_36dp.png
deleted file mode 100644
index adef631..0000000
--- a/components/browser_ui/styles/android/java/res/drawable-xxhdpi/ic_stop_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/styles/android/java/res/drawable-xxxhdpi/ic_pause_white_24dp.png b/components/browser_ui/styles/android/java/res/drawable-xxxhdpi/ic_pause_white_24dp.png
deleted file mode 100644
index 660ac658..0000000
--- a/components/browser_ui/styles/android/java/res/drawable-xxxhdpi/ic_pause_white_24dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/styles/android/java/res/drawable-xxxhdpi/ic_play_arrow_white_24dp.png b/components/browser_ui/styles/android/java/res/drawable-xxxhdpi/ic_play_arrow_white_24dp.png
deleted file mode 100644
index be5c062..0000000
--- a/components/browser_ui/styles/android/java/res/drawable-xxxhdpi/ic_play_arrow_white_24dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/styles/android/java/res/drawable-xxxhdpi/ic_stop_white_36dp.png b/components/browser_ui/styles/android/java/res/drawable-xxxhdpi/ic_stop_white_36dp.png
deleted file mode 100644
index 035ca181..0000000
--- a/components/browser_ui/styles/android/java/res/drawable-xxxhdpi/ic_stop_white_36dp.png
+++ /dev/null
Binary files differ
diff --git a/components/browser_ui/styles/android/java/res/drawable/ic_call_end_white_24dp.xml b/components/browser_ui/styles/android/java/res/drawable/ic_call_end_white_24dp.xml
new file mode 100644
index 0000000..da6f9ca
--- /dev/null
+++ b/components/browser_ui/styles/android/java/res/drawable/ic_call_end_white_24dp.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="@macro/default_icon_color">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M12,9c-1.6,0 -3.15,0.25 -4.6,0.72v3.1c0,0.39 -0.23,0.74 -0.56,0.9 -0.98,0.49 -1.87,1.12 -2.66,1.85 -0.18,0.18 -0.43,0.28 -0.7,0.28 -0.28,0 -0.53,-0.11 -0.71,-0.29L0.29,13.08c-0.18,-0.17 -0.29,-0.42 -0.29,-0.7 0,-0.28 0.11,-0.53 0.29,-0.71C3.34,8.78 7.46,7 12,7s8.66,1.78 11.71,4.67c0.18,0.18 0.29,0.43 0.29,0.71 0,0.28 -0.11,0.53 -0.29,0.71l-2.48,2.48c-0.18,0.18 -0.43,0.29 -0.71,0.29 -0.27,0 -0.52,-0.11 -0.7,-0.28 -0.79,-0.74 -1.69,-1.36 -2.67,-1.85 -0.33,-0.16 -0.56,-0.5 -0.56,-0.9v-3.1C15.15,9.25 13.6,9 12,9z"/>
+</vector>
diff --git a/components/browser_ui/styles/android/java/res/drawable/ic_fast_forward_white_24dp.xml b/components/browser_ui/styles/android/java/res/drawable/ic_fast_forward_white_24dp.xml
new file mode 100644
index 0000000..d7ca2e9
--- /dev/null
+++ b/components/browser_ui/styles/android/java/res/drawable/ic_fast_forward_white_24dp.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="@macro/default_icon_color">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M4,18l8.5,-6L4,6v12zM13,6v12l8.5,-6L13,6z"/>
+</vector>
diff --git a/components/browser_ui/styles/android/java/res/drawable/ic_fast_rewind_white_24dp.xml b/components/browser_ui/styles/android/java/res/drawable/ic_fast_rewind_white_24dp.xml
new file mode 100644
index 0000000..215c990d
--- /dev/null
+++ b/components/browser_ui/styles/android/java/res/drawable/ic_fast_rewind_white_24dp.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="@macro/default_icon_color">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M11,18L11,6l-8.5,6 8.5,6zM11.5,12l8.5,6L20,6l-8.5,6z"/>
+</vector>
diff --git a/components/browser_ui/styles/android/java/res/drawable/ic_mic_off_white_24dp.xml b/components/browser_ui/styles/android/java/res/drawable/ic_mic_off_white_24dp.xml
new file mode 100644
index 0000000..7abb054
--- /dev/null
+++ b/components/browser_ui/styles/android/java/res/drawable/ic_mic_off_white_24dp.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="@macro/default_icon_color">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M19,11h-1.7c0,0.74 -0.16,1.43 -0.43,2.05l1.23,1.23c0.56,-0.98 0.9,-2.09 0.9,-3.28zM14.98,11.17c0,-0.06 0.02,-0.11 0.02,-0.17L15,5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v0.18l5.98,5.99zM4.27,3L3,4.27l6.01,6.01L9.01,11c0,1.66 1.33,3 2.99,3 0.22,0 0.44,-0.03 0.65,-0.08l1.66,1.66c-0.71,0.33 -1.5,0.52 -2.31,0.52 -2.76,0 -5.3,-2.1 -5.3,-5.1L5,11c0,3.41 2.72,6.23 6,6.72L11,21h2v-3.28c0.91,-0.13 1.77,-0.45 2.54,-0.9L19.73,21 21,19.73 4.27,3z"/>
+</vector>
diff --git a/components/browser_ui/styles/android/java/res/drawable/ic_mic_white_24dp.xml b/components/browser_ui/styles/android/java/res/drawable/ic_mic_white_24dp.xml
new file mode 100644
index 0000000..1f107d7
--- /dev/null
+++ b/components/browser_ui/styles/android/java/res/drawable/ic_mic_white_24dp.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="@macro/default_icon_color">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M12,14c1.66,0 2.99,-1.34 2.99,-3L15,5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v6c0,1.66 1.34,3 3,3zM17.3,11c0,3 -2.54,5.1 -5.3,5.1S6.7,14 6.7,11L5,11c0,3.41 2.72,6.23 6,6.72L11,21h2v-3.28c3.28,-0.48 6,-3.3 6,-6.72h-1.7z"/>
+</vector>
diff --git a/components/browser_ui/styles/android/java/res/drawable/ic_pause_white_24dp.xml b/components/browser_ui/styles/android/java/res/drawable/ic_pause_white_24dp.xml
new file mode 100644
index 0000000..1120faa
--- /dev/null
+++ b/components/browser_ui/styles/android/java/res/drawable/ic_pause_white_24dp.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="@macro/default_icon_color">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z"/>
+</vector>
diff --git a/components/browser_ui/styles/android/java/res/drawable/ic_play_arrow_white_24dp.xml b/components/browser_ui/styles/android/java/res/drawable/ic_play_arrow_white_24dp.xml
new file mode 100644
index 0000000..5ea1636
--- /dev/null
+++ b/components/browser_ui/styles/android/java/res/drawable/ic_play_arrow_white_24dp.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="@macro/default_icon_color">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M8,5v14l11,-7z"/>
+</vector>
diff --git a/components/browser_ui/styles/android/java/res/drawable/ic_skip_next_white_24dp.xml b/components/browser_ui/styles/android/java/res/drawable/ic_skip_next_white_24dp.xml
new file mode 100644
index 0000000..b4b33e5
--- /dev/null
+++ b/components/browser_ui/styles/android/java/res/drawable/ic_skip_next_white_24dp.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="@macro/default_icon_color">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M6,18l8.5,-6L6,6v12zM16,6v12h2V6h-2z"/>
+</vector>
diff --git a/components/browser_ui/styles/android/java/res/drawable/ic_skip_previous_white_24dp.xml b/components/browser_ui/styles/android/java/res/drawable/ic_skip_previous_white_24dp.xml
new file mode 100644
index 0000000..36dcff5
--- /dev/null
+++ b/components/browser_ui/styles/android/java/res/drawable/ic_skip_previous_white_24dp.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="@macro/default_icon_color">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M6,6h2v12L6,18zM9.5,12l8.5,6L18,6z"/>
+</vector>
diff --git a/components/browser_ui/styles/android/java/res/drawable/ic_stop_white_24dp.xml b/components/browser_ui/styles/android/java/res/drawable/ic_stop_white_24dp.xml
new file mode 100644
index 0000000..a0d5bff
--- /dev/null
+++ b/components/browser_ui/styles/android/java/res/drawable/ic_stop_white_24dp.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="@macro/default_icon_color">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M6,6h12v12H6z"/>
+</vector>
diff --git a/components/browser_ui/styles/android/java/res/drawable/ic_videocam_off_white_24dp.xml b/components/browser_ui/styles/android/java/res/drawable/ic_videocam_off_white_24dp.xml
new file mode 100644
index 0000000..c337358
--- /dev/null
+++ b/components/browser_ui/styles/android/java/res/drawable/ic_videocam_off_white_24dp.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="@macro/default_icon_color">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M21,6.5l-4,4V7c0,-0.55 -0.45,-1 -1,-1H9.82L21,17.18V6.5zM3.27,2L2,3.27 4.73,6H4c-0.55,0 -1,0.45 -1,1v10c0,0.55 0.45,1 1,1h12c0.21,0 0.39,-0.08 0.54,-0.18L19.73,21 21,19.73 3.27,2z"/>
+</vector>
diff --git a/components/endpoint_fetcher/BUILD.gn b/components/endpoint_fetcher/BUILD.gn
index f26fc85d..01bc668 100644
--- a/components/endpoint_fetcher/BUILD.gn
+++ b/components/endpoint_fetcher/BUILD.gn
@@ -15,7 +15,7 @@
     "//components/signin/public/identity_manager",
     "//components/version_info:channel",
     "//google_apis",
-    "//net/traffic_annotation",
+    "//net",
     "//services/data_decoder/public/cpp",
     "//services/network/public/cpp",
   ]
diff --git a/components/endpoint_fetcher/endpoint_fetcher.cc b/components/endpoint_fetcher/endpoint_fetcher.cc
index 24a751a64..908bb90 100644
--- a/components/endpoint_fetcher/endpoint_fetcher.cc
+++ b/components/endpoint_fetcher/endpoint_fetcher.cc
@@ -11,9 +11,11 @@
 #include "components/version_info/channel.h"
 #include "google_apis/gaia/gaia_urls.h"
 #include "google_apis/google_api_keys.h"
+#include "net/http/http_status_code.h"
 #include "services/network/public/cpp/resource_request.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "services/network/public/cpp/simple_url_loader.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
 
 namespace {
 const char kContentTypeKey[] = "Content-Type";
@@ -152,6 +154,8 @@
     auto response = std::make_unique<EndpointResponse>();
     VLOG(1) << __func__ << " No primary accounts found";
     response->response = "No primary accounts found";
+    response->error_type =
+        absl::make_optional<FetchErrorType>(FetchErrorType::kAuthError);
     // TODO(crbug.com/993393) Add more detailed error messaging
     std::move(endpoint_fetcher_callback).Run(std::move(response));
     return;
@@ -177,6 +181,8 @@
   if (error.state() != GoogleServiceAuthError::NONE) {
     auto response = std::make_unique<EndpointResponse>();
     response->response = "There was an authentication error";
+    response->error_type =
+        absl::make_optional<FetchErrorType>(FetchErrorType::kAuthError);
     // TODO(crbug.com/993393) Add more detailed error messaging
     std::move(endpoint_fetcher_callback).Run(std::move(response));
     return;
@@ -246,26 +252,37 @@
 void EndpointFetcher::OnResponseFetched(
     EndpointFetcherCallback endpoint_fetcher_callback,
     std::unique_ptr<std::string> response_body) {
+  int http_status_code = -1;
+  if (simple_url_loader_->ResponseInfo() &&
+      simple_url_loader_->ResponseInfo()->headers) {
+    http_status_code =
+        simple_url_loader_->ResponseInfo()->headers->response_code();
+  }
   int net_error_code = simple_url_loader_->NetError();
   // The EndpointFetcher and its members will be destroyed after
   // any of the below callbacks. Do not access The EndpointFetcher
   // or its members after the callbacks.
   simple_url_loader_.reset();
+
+  auto response = std::make_unique<EndpointResponse>();
+  response->http_status_code = http_status_code;
+  if (net_error_code != net::OK) {
+    response->error_type =
+        absl::make_optional<FetchErrorType>(FetchErrorType::kNetError);
+  }
+
   if (response_body) {
     if (sanitize_response_) {
       data_decoder::JsonSanitizer::Sanitize(
           std::move(*response_body),
           base::BindOnce(&EndpointFetcher::OnSanitizationResult,
-                         weak_ptr_factory_.GetWeakPtr(),
+                         weak_ptr_factory_.GetWeakPtr(), std::move(response),
                          std::move(endpoint_fetcher_callback)));
     } else {
-      auto response = std::make_unique<EndpointResponse>();
       response->response = *response_body;
       std::move(endpoint_fetcher_callback).Run(std::move(response));
     }
   } else {
-    auto response = std::make_unique<EndpointResponse>();
-    // TODO(crbug.com/993393) Add more detailed error messaging
     std::string net_error = net::ErrorToString(net_error_code);
     VLOG(1) << __func__ << " with response error: " << net_error;
     response->response = "There was a response error";
@@ -274,16 +291,21 @@
 }
 
 void EndpointFetcher::OnSanitizationResult(
+    std::unique_ptr<EndpointResponse> response,
     EndpointFetcherCallback endpoint_fetcher_callback,
     data_decoder::JsonSanitizer::Result result) {
-  auto response = std::make_unique<EndpointResponse>();
-  if (result.value.has_value())
+  if (result.value.has_value()) {
     response->response = result.value.value();
-  else if (result.error.has_value())
+  } else if (result.error.has_value()) {
+    response->error_type =
+        absl::make_optional<FetchErrorType>(FetchErrorType::kResultParseError);
     response->response =
         "There was a sanitization error: " + result.error.value();
-  else
+  } else {
+    response->error_type =
+        absl::make_optional<FetchErrorType>(FetchErrorType::kResultParseError);
     response->response = "There was an unknown sanitization error";
+  }
   // The EndpointFetcher and its members will be destroyed after
   // any the below callback. Do not access The EndpointFetcher
   // or its members after the callback.
diff --git a/components/endpoint_fetcher/endpoint_fetcher.h b/components/endpoint_fetcher/endpoint_fetcher.h
index 3843c8e..e3e7f5f 100644
--- a/components/endpoint_fetcher/endpoint_fetcher.h
+++ b/components/endpoint_fetcher/endpoint_fetcher.h
@@ -15,6 +15,7 @@
 #include "components/signin/public/identity_manager/scope_set.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 #include "services/data_decoder/public/cpp/json_sanitizer.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace network {
 struct ResourceRequest;
@@ -29,9 +30,16 @@
 class GoogleServiceAuthError;
 class GURL;
 
+enum class FetchErrorType {
+  kAuthError = 0,
+  kNetError = 1,
+  kResultParseError = 2,
+};
+
 struct EndpointResponse {
   std::string response;
-  // TODO(crbug.com/993393) Add more detailed error messaging
+  int http_status_code{-1};
+  absl::optional<FetchErrorType> error_type;
 };
 
 using EndpointFetcherCallback =
@@ -133,7 +141,8 @@
                           signin::AccessTokenInfo access_token_info);
   void OnResponseFetched(EndpointFetcherCallback callback,
                          std::unique_ptr<std::string> response_body);
-  void OnSanitizationResult(EndpointFetcherCallback endpoint_fetcher_callback,
+  void OnSanitizationResult(std::unique_ptr<EndpointResponse> response,
+                            EndpointFetcherCallback endpoint_fetcher_callback,
                             data_decoder::JsonSanitizer::Result result);
 
   enum AuthType { CHROME_API_KEY, OAUTH, NO_AUTH };
diff --git a/components/endpoint_fetcher/endpoint_fetcher_unittest.cc b/components/endpoint_fetcher/endpoint_fetcher_unittest.cc
index 45823261..de741fe 100644
--- a/components/endpoint_fetcher/endpoint_fetcher_unittest.cc
+++ b/components/endpoint_fetcher/endpoint_fetcher_unittest.cc
@@ -117,9 +117,11 @@
 TEST_F(EndpointFetcherTest, FetchResponse) {
   SignIn();
   SetMockResponse(GURL(kEndpoint), kExpectedResponse, net::HTTP_OK, net::OK);
-  EXPECT_CALL(
-      endpoint_fetcher_callback(),
-      Run(Pointee(Field(&EndpointResponse::response, kExpectedResponse))));
+  EXPECT_CALL(endpoint_fetcher_callback(),
+              Run(Pointee(AllOf(
+                  Field(&EndpointResponse::response, kExpectedResponse),
+                  Field(&EndpointResponse::http_status_code, net::HTTP_OK),
+                  Field(&EndpointResponse::error_type, absl::nullopt)))));
   endpoint_fetcher()->Fetch(endpoint_fetcher_callback().Get());
   base::RunLoop().RunUntilIdle();
 }
@@ -127,10 +129,13 @@
 TEST_F(EndpointFetcherTest, FetchMalformedResponse) {
   SignIn();
   SetMockResponse(GURL(kEndpoint), kMalformedResponse, net::HTTP_OK, net::OK);
-  EXPECT_CALL(
-      endpoint_fetcher_callback(),
-      Run(Pointee(Field(&EndpointResponse::response,
-                        testing::StartsWith(kExpectedSanitizationError)))));
+  EXPECT_CALL(endpoint_fetcher_callback(),
+              Run(Pointee(AllOf(
+                  Field(&EndpointResponse::response,
+                        testing::StartsWith(kExpectedSanitizationError)),
+                  Field(&EndpointResponse::http_status_code, net::HTTP_OK),
+                  Field(&EndpointResponse::error_type,
+                        FetchErrorType::kResultParseError)))));
   endpoint_fetcher()->Fetch(endpoint_fetcher_callback().Get());
   base::RunLoop().RunUntilIdle();
 }
@@ -141,7 +146,22 @@
                   net::ERR_FAILED);
   EXPECT_CALL(
       endpoint_fetcher_callback(),
-      Run(Pointee(Field(&EndpointResponse::response, kExpectedResponseError))));
+      Run(Pointee(AllOf(
+          Field(&EndpointResponse::response, kExpectedResponseError),
+          Field(&EndpointResponse::http_status_code, -1),
+          Field(&EndpointResponse::error_type, FetchErrorType::kNetError)))));
+  endpoint_fetcher()->Fetch(endpoint_fetcher_callback().Get());
+  base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(EndpointFetcherTest, FetchRedirectionResponse) {
+  SignIn();
+  SetMockResponse(GURL(kEndpoint), kExpectedResponse, net::HTTP_FOUND, net::OK);
+  EXPECT_CALL(endpoint_fetcher_callback(),
+              Run(Pointee(AllOf(
+                  Field(&EndpointResponse::response, kExpectedResponse),
+                  Field(&EndpointResponse::http_status_code, net::HTTP_FOUND),
+                  Field(&EndpointResponse::error_type, absl::nullopt)))));
   endpoint_fetcher()->Fetch(endpoint_fetcher_callback().Get());
   base::RunLoop().RunUntilIdle();
 }
@@ -151,7 +171,10 @@
   identity_test_env().SetAutomaticIssueOfAccessTokens(false);
   EXPECT_CALL(
       endpoint_fetcher_callback(),
-      Run(Pointee(Field(&EndpointResponse::response, kExpectedAuthError))));
+      Run(Pointee(AllOf(
+          Field(&EndpointResponse::response, kExpectedAuthError),
+          Field(&EndpointResponse::http_status_code, -1),
+          Field(&EndpointResponse::error_type, FetchErrorType::kAuthError)))));
   endpoint_fetcher()->Fetch(endpoint_fetcher_callback().Get());
   identity_test_env().WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
       GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE));
@@ -159,9 +182,12 @@
 }
 
 TEST_F(EndpointFetcherTest, FetchOAuthNoPrimaryAccount) {
-  EXPECT_CALL(endpoint_fetcher_callback(),
-              Run(Pointee(Field(&EndpointResponse::response,
-                                kExpectedPrimaryAccountError))));
+  EXPECT_CALL(
+      endpoint_fetcher_callback(),
+      Run(Pointee(AllOf(
+          Field(&EndpointResponse::response, kExpectedPrimaryAccountError),
+          Field(&EndpointResponse::http_status_code, -1),
+          Field(&EndpointResponse::error_type, FetchErrorType::kAuthError)))));
   endpoint_fetcher()->Fetch(endpoint_fetcher_callback().Get());
   base::RunLoop().RunUntilIdle();
 }
diff --git a/components/history_clusters/core/history_clusters_service_task_get_most_recent_clusters.cc b/components/history_clusters/core/history_clusters_service_task_get_most_recent_clusters.cc
index 97b764e..2bb592b 100644
--- a/components/history_clusters/core/history_clusters_service_task_get_most_recent_clusters.cc
+++ b/components/history_clusters/core/history_clusters_service_task_get_most_recent_clusters.cc
@@ -54,9 +54,15 @@
     // If visits can't be clustered, either because `backend_` is null, or all
     // unclustered visits have already been clustered and returned, then return
     // persisted clusters.
-    weak_history_clusters_service_->NotifyDebugMessage(
-        "HistoryClustersService::QueryClusters Error: ClusteringBackend is "
-        "nullptr. Returning empty cluster vector.");
+    if (!backend_) {
+      weak_history_clusters_service_->NotifyDebugMessage(
+          "HistoryClustersServiceTaskGetMostRecentClusters::Start() Error: "
+          "ClusteringBackend is nullptr. Returning most recent clusters.");
+    } else {
+      weak_history_clusters_service_->NotifyDebugMessage(
+          "HistoryClustersServiceTaskGetMostRecentClusters::Start() exhausted "
+          "unclustered visits. Returning most recent clusters.");
+    }
     ReturnMostRecentPersistedClusters(continuation_params_.continuation_time);
 
   } else {
diff --git a/components/sync/engine/loopback_server/loopback_server.cc b/components/sync/engine/loopback_server/loopback_server.cc
index dc0a3fb3..af8f045 100644
--- a/components/sync/engine/loopback_server/loopback_server.cc
+++ b/components/sync/engine/loopback_server/loopback_server.cc
@@ -10,6 +10,7 @@
 #include <utility>
 
 #include "base/containers/cxx20_erase.h"
+#include "base/feature_list.h"
 #include "base/files/file_util.h"
 #include "base/format_macros.h"
 #include "base/guid.h"
@@ -63,6 +64,14 @@
 static const char kSyncedBookmarksFolderServerTag[] = "synced_bookmarks";
 static const char kSyncedBookmarksFolderName[] = "Synced Bookmarks";
 
+// Returns entity's version without increasing it by one for tombstones. The
+// version is updated and set in SaveEntity() and there is no need to increment
+// it again in CommitResponse. Otherwise, it would be possible that the next
+// commit request would return the same version.
+const base::Feature kSyncReturnRealVersionOnCommitInLoopbackServer{
+    "SyncReturnRealVersionOnCommitInLoopbackServer",
+    base::FEATURE_ENABLED_BY_DEFAULT};
+
 int GetServerMigrationVersion(
     const std::map<ModelType, int>& server_migration_versions,
     ModelType type) {
@@ -581,7 +590,9 @@
                                         : sync_pb::CommitResponse::SUCCESS);
   entry_response->set_id_string(entity.GetId());
 
-  if (entity.IsDeleted()) {
+  if (entity.IsDeleted() &&
+      !base::FeatureList::IsEnabled(
+          kSyncReturnRealVersionOnCommitInLoopbackServer)) {
     entry_response->set_version(entity.GetVersion() + 1);
   } else {
     entry_response->set_version(entity.GetVersion());
diff --git a/components/viz/service/display/overlay_candidate_factory.cc b/components/viz/service/display/overlay_candidate_factory.cc
index e76e6f19..7ed78b0 100644
--- a/components/viz/service/display/overlay_candidate_factory.cc
+++ b/components/viz/service/display/overlay_candidate_factory.cc
@@ -466,7 +466,10 @@
     // |size_in_pixels| as 'set_resource_size_in_pixels' is not called as these
     // quads are not intended to become overlays.
     if (!quad->resource_size_in_pixels().IsEmpty())
-      candidate.priority_hint = gfx::OverlayPriorityHint::kRegular;
+      candidate.priority_hint =
+          candidate.requires_overlay
+              ? gfx::OverlayPriorityHint::kHardwareProtection
+              : gfx::OverlayPriorityHint::kRegular;
 
 #if BUILDFLAG(IS_ANDROID)
     if (quad->is_stream_video) {
diff --git a/components/webrtc/android/java/src/org/chromium/components/webrtc/MediaCaptureNotificationUtil.java b/components/webrtc/android/java/src/org/chromium/components/webrtc/MediaCaptureNotificationUtil.java
index 826c28a08..cd3419e 100644
--- a/components/webrtc/android/java/src/org/chromium/components/webrtc/MediaCaptureNotificationUtil.java
+++ b/components/webrtc/android/java/src/org/chromium/components/webrtc/MediaCaptureNotificationUtil.java
@@ -54,7 +54,7 @@
         if (stopIntent != null) {
             builder.setPriorityBeforeO(NotificationCompat.PRIORITY_HIGH);
             builder.setVibrate(new long[0]);
-            builder.addAction(R.drawable.ic_stop_white_36dp,
+            builder.addAction(R.drawable.ic_stop_white_24dp,
                     appContext.getString(R.string.accessibility_stop), stopIntent);
         } else {
             assert mediaType != MediaType.SCREEN_CAPTURE : "SCREEN_CAPTURE requires a stop action";
diff --git a/content/browser/accessibility/browser_accessibility_cocoa.mm b/content/browser/accessibility/browser_accessibility_cocoa.mm
index 09af9f4..c606df43 100644
--- a/content/browser/accessibility/browser_accessibility_cocoa.mm
+++ b/content/browser/accessibility/browser_accessibility_cocoa.mm
@@ -1222,10 +1222,10 @@
   } else if (_owner->IsTextField() &&
              _owner->HasState(ax::mojom::State::kMultiline)) {
     cocoa_role = NSAccessibilityTextAreaRole;
-  } else if (role == ax::mojom::Role::kImage && _owner->GetChildCount()) {
+  } else if (ui::IsImage(_owner->GetRole()) && _owner->GetChildCount()) {
     // An image map is an image with children, and exposed on Mac as a group.
     cocoa_role = NSAccessibilityGroupRole;
-  } else if (role == ax::mojom::Role::kImage &&
+  } else if (ui::IsImage(_owner->GetRole()) &&
              _owner->HasExplicitlyEmptyName()) {
     cocoa_role = NSAccessibilityUnknownRole;
   } else if (_owner->IsRootWebAreaForPresentationalIframe()) {
diff --git a/content/browser/accessibility/browser_accessibility_manager.cc b/content/browser/accessibility/browser_accessibility_manager.cc
index 1726c31c..f8cac33 100644
--- a/content/browser/accessibility/browser_accessibility_manager.cc
+++ b/content/browser/accessibility/browser_accessibility_manager.cc
@@ -30,6 +30,12 @@
 #include "ui/accessibility/ax_tree_serializer.h"
 #include "ui/base/buildflags.h"
 
+#if defined(AX_FAIL_FAST_BUILD)
+#include "base/command_line.h"
+#include "content/public/browser/ax_inspect_factory.h"
+#include "content/public/common/content_switches.h"
+#endif
+
 namespace content {
 
 namespace {
@@ -729,6 +735,30 @@
 
   // Allow derived classes to do event post-processing.
   FinalizeAccessibilityEvents();
+
+#if defined(AX_FAIL_FAST_BUILD)
+  // When running a debugging/sanitizer build with
+  // --force-renderer-accessibility, exercise the properties for every node, to
+  // ensure no crashes or assertions are triggered. This helpfully runs for all
+  // web tests on builder linux-blink-web-tests-force-accessibility-rel, as well
+  // as for some clusterfuzz runs.
+  static int g_max_ax_tree_exercise_iterations = 3;  // Avoid timeouts.
+  static int count = 0;
+  if (GetRoot()->GetChildCount() > 0 &&
+      !GetRoot()->GetBoolAttribute(ax::mojom::BoolAttribute::kBusy) &&
+      ++count <= g_max_ax_tree_exercise_iterations) {
+    base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+    if (command_line->HasSwitch(::switches::kForceRendererAccessibility)) {
+      std::unique_ptr<ui::AXTreeFormatter> formatter(
+          AXInspectFactory::CreatePlatformFormatter());
+      formatter->SetPropertyFilters({{"*", ui::AXPropertyFilter::ALLOW}});
+      std::string formatted_tree = formatter->Format(GetRoot());
+      VLOG(1) << "\n\n******** Formatted tree ********\n\n"
+              << formatted_tree << "\n*********************************\n\n";
+    }
+  }
+#endif
+
   return true;
 }
 
diff --git a/content/browser/file_system_access/file_system_access_manager_impl.cc b/content/browser/file_system_access/file_system_access_manager_impl.cc
index 3b60d30..159b9ef4 100644
--- a/content/browser/file_system_access/file_system_access_manager_impl.cc
+++ b/content/browser/file_system_access/file_system_access_manager_impl.cc
@@ -513,6 +513,7 @@
 
 void FileSystemAccessManagerImpl::Shutdown() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  operation_runner_.Reset();
   permission_context_ = nullptr;
 }
 
diff --git a/content/browser/preloading/prerender/prerender_browsertest.cc b/content/browser/preloading/prerender/prerender_browsertest.cc
index 46fd15c..a0c561fa 100644
--- a/content/browser/preloading/prerender/prerender_browsertest.cc
+++ b/content/browser/preloading/prerender/prerender_browsertest.cc
@@ -585,6 +585,135 @@
   EXPECT_TRUE(prerender_observer.was_activated());
 }
 
+// Tests that clicking a link annotated with "target=_blank" cannot activate a
+// prerender.
+IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, ActivateOnLinkClick_TargetBlank) {
+  const GURL kInitialUrl = GetUrl("/simple_links.html");
+  const GURL kPrerenderingUrl = GetUrl("/title2.html");
+
+  // Navigate to an initial page which has a link to `kPrerenderingUrl`.
+  ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
+
+  // Start prerendering `kPrerenderingUrl`.
+  int prerender_host_id = AddPrerender(kPrerenderingUrl);
+  test::PrerenderHostObserver prerender_observer(*web_contents(),
+                                                 prerender_host_id);
+
+  // Click the link annotated with "target=_blank". This should not activate the
+  // prerendered page.
+  TestNavigationObserver nav_observer(kPrerenderingUrl);
+  nav_observer.StartWatchingNewWebContents();
+  const std::string kLinkClickScript = R"(
+      clickSameSiteNewWindowLink();
+  )";
+  EXPECT_TRUE(ExecJs(web_contents(), kLinkClickScript));
+  nav_observer.WaitForNavigationFinished();
+  EXPECT_EQ(nav_observer.last_navigation_url(), kPrerenderingUrl);
+  EXPECT_FALSE(prerender_observer.was_activated());
+
+  // The navigation occurred in a new WebContents, so the original WebContents
+  // should still be showing the initial trigger page.
+  EXPECT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
+  // Also, the prerendered page should still be alive.
+  EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl));
+
+  // Navigate to `kPrerenderingUrl` on the original WebContents. This should
+  // activate the prerendered page.
+  NavigatePrimaryPage(kPrerenderingUrl);
+  EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
+  EXPECT_TRUE(prerender_observer.was_activated());
+  ExpectFinalStatusForSpeculationRule(PrerenderHost::FinalStatus::kActivated);
+}
+
+// Tests that clicking a link annotated with "target=_blank rel=noopener" cannot
+// activate a prerender.
+IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
+                       ActivateOnLinkClick_TargetBlankWithNoopener) {
+  const GURL kInitialUrl = GetUrl("/simple_links.html");
+  const GURL kPrerenderingUrl = GetUrl("/title2.html");
+
+  // Navigate to an initial page which has a link to `kPrerenderingUrl`.
+  ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
+
+  // Start prerendering `kPrerenderingUrl`.
+  int prerender_host_id = AddPrerender(kPrerenderingUrl);
+  test::PrerenderHostObserver prerender_observer(*web_contents(),
+                                                 prerender_host_id);
+
+  // Click the link annotated with "target=_blank rel=noopener". This should not
+  // activate the prerendered page.
+  TestNavigationObserver nav_observer(kPrerenderingUrl);
+  nav_observer.StartWatchingNewWebContents();
+  const std::string kLinkClickScript = R"(
+      clickSameSiteNewWindowWithNoopenerLink();
+  )";
+  EXPECT_TRUE(ExecJs(web_contents(), kLinkClickScript));
+  nav_observer.WaitForNavigationFinished();
+  EXPECT_EQ(nav_observer.last_navigation_url(), kPrerenderingUrl);
+  EXPECT_FALSE(prerender_observer.was_activated());
+
+  // The navigation occurred in a new WebContents, so the original WebContents
+  // should still be showing the initial trigger page.
+  EXPECT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
+  // Also, the prerendered page should still be alive.
+  EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl));
+
+  // Navigate to `kPrerenderingUrl` on the original WebContents. This should
+  // activate the prerendered page.
+  NavigatePrimaryPage(kPrerenderingUrl);
+  EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
+  EXPECT_TRUE(prerender_observer.was_activated());
+  ExpectFinalStatusForSpeculationRule(PrerenderHost::FinalStatus::kActivated);
+}
+
+// Tests that clicking a link annotated with "target=_blank rel=opener" cannot
+// activate a prerender.
+IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
+                       ActivateOnLinkClick_TargetBlankWithOpener) {
+  const GURL kInitialUrl = GetUrl("/simple_links.html");
+  const GURL kPrerenderingUrl = GetUrl("/title2.html");
+
+  // Navigate to an initial page which has a link to `kPrerenderingUrl`.
+  ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
+
+  // Start prerendering `kPrerenderingUrl`.
+  int prerender_host_id = AddPrerender(kPrerenderingUrl);
+  test::PrerenderHostObserver prerender_observer(*web_contents(),
+                                                 prerender_host_id);
+
+  // Click the link annotated with "target=_blank rel=opener". This should not
+  // activate the prerendered page.
+  TestNavigationObserver nav_observer(kPrerenderingUrl);
+  nav_observer.StartWatchingNewWebContents();
+  const std::string kLinkClickScript = R"(
+      clickSameSiteNewWindowWithOpenerLink();
+  )";
+  EXPECT_TRUE(ExecJs(web_contents(), kLinkClickScript));
+  nav_observer.WaitForNavigationFinished();
+  EXPECT_EQ(nav_observer.last_navigation_url(), kPrerenderingUrl);
+  EXPECT_FALSE(prerender_observer.was_activated());
+
+  // The navigation occurred in a new WebContents, so the original WebContents
+  // should still be showing the initial trigger page.
+  EXPECT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl);
+  // Also, the prerendered page should still be alive.
+  EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl));
+
+  // Navigate to `kPrerenderingUrl` on the original WebContents. The page opened
+  // with "rel=opener" should prevent it from activating the prerendered page.
+  NavigatePrimaryPage(kPrerenderingUrl);
+  EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl);
+  EXPECT_FALSE(prerender_observer.was_activated());
+
+  // The prerendered page should be destroyed as the trigger page navigated
+  // away.
+  prerender_observer.WaitForDestroyed();
+  EXPECT_EQ(GetHostForUrl(kPrerenderingUrl),
+            RenderFrameHost::kNoFrameTreeNodeId);
+  ExpectFinalStatusForSpeculationRule(
+      PrerenderHost::FinalStatus::kTriggerDestroyed);
+}
+
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, ResponseHeaders) {
   const GURL kInitialUrl = GetUrl("/empty.html");
   const GURL kPrerenderingUrl = GetUrl("/set-header?X-Foo: bar");
@@ -611,8 +740,6 @@
 // Tests that prerendering is cancelled if a network request for the
 // navigation results in an empty response with 404 status.
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderCancelledOnEmptyBody404) {
-  base::HistogramTester histogram_tester;
-
   const GURL kInitialUrl = GetUrl("/empty.html");
   // Specify a URL for which we don't have a corresponding file in the data dir.
   const GURL kPrerenderingUrl = GetUrl("/404");
@@ -637,8 +764,6 @@
 // navigation results in an non-empty response with 404 status.
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
                        PrerenderCancelledOnNonEmptyBody404) {
-  base::HistogramTester histogram_tester;
-
   const GURL kInitialUrl = GetUrl("/empty.html");
   const GURL kPrerenderingUrl = GetUrl("/page404.html");
 
@@ -659,8 +784,6 @@
 // Tests that prerendering is cancelled if a network request for the
 // navigation results in an non-empty response with 500 status.
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderCancelledOn500Page) {
-  base::HistogramTester histogram_tester;
-
   const GURL kInitialUrl = GetUrl("/empty.html");
   const GURL kPrerenderingUrl = GetUrl("/page500.html");
 
@@ -679,8 +802,6 @@
 }
 
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderCancelledOn204Page) {
-  base::HistogramTester histogram_tester;
-
   // Navigate to an initial page.
   const GURL kInitialUrl = GetUrl("/title1.html");
   ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
@@ -702,8 +823,6 @@
 }
 
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderCancelledOn205Page) {
-  base::HistogramTester histogram_tester;
-
   // Navigate to an initial page.
   const GURL kInitialUrl = GetUrl("/title1.html");
   ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
@@ -725,8 +844,6 @@
 }
 
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderAllowedOn204Iframe) {
-  base::HistogramTester histogram_tester;
-
   // Navigate to an initial page.
   const GURL kInitialUrl = GetUrl("/title1.html");
   ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
@@ -752,8 +869,6 @@
 }
 
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, CancelOnAuthRequested) {
-  base::HistogramTester histogram_tester;
-
   // Navigate to an initial page.
   const GURL kInitialUrl = GetUrl("/title1.html");
   ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
@@ -798,8 +913,6 @@
 }
 
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, CancelOnAuthRequestedSubframe) {
-  base::HistogramTester histogram_tester;
-
   // Navigate to an initial page.
   const GURL kInitialUrl = GetUrl("/title1.html");
   ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
@@ -829,8 +942,6 @@
 }
 
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, CancelOnAuthRequestedSubResource) {
-  base::HistogramTester histogram_tester;
-
   // Navigate to an initial page.
   const GURL kInitialUrl = GetUrl("/empty.html");
   ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
@@ -1222,8 +1333,6 @@
 }
 
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, CrossOriginRedirection) {
-  base::HistogramTester histogram_tester;
-
   // Navigate to an initial page.
   const GURL kInitialUrl = GetUrl("/empty.html");
   ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
@@ -1699,7 +1808,6 @@
 // Test main frame navigation in prerendering page cancels the prerendering.
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
                        MainFrameNavigationCancelsPrerendering) {
-  base::HistogramTester histogram_tester;
   const GURL kInitialUrl = GetUrl("/empty.html");
   const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
   const GURL kHungUrl = GetUrl("/hung");
@@ -1724,7 +1832,6 @@
 
 // Regression test for https://crbug.com/1198051
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, MainFrameFragmentNavigation) {
-  base::HistogramTester histogram_tester;
   const GURL kInitialUrl = GetUrl("/empty.html");
   const GURL kPrerenderingUrl =
       GetUrl("/navigation_controller/hash_anchor_with_iframe.html");
@@ -2097,8 +2204,6 @@
 // Tests that prerendering will be cancelled if a prerendering page wants to set
 // a WebContents-level preferred size.
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, CancelOnPreferredSizeChanged) {
-  base::HistogramTester histogram_tester;
-
   const GURL kInitialUrl = GetUrl("/empty.html");
   const GURL kPrerenderingUrl = GetUrl("/title1.html");
   ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
@@ -2659,8 +2764,6 @@
 // prernedering should be canceled.
 IN_PROC_BROWSER_TEST_P(SSLPrerenderBrowserTest,
                        CertificateValidation_Navigation) {
-  base::HistogramTester histogram_tester;
-
   // Navigate to an initial page.
   const GURL kInitialUrl = GetUrl("/empty.html");
   ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
@@ -2686,8 +2789,6 @@
 // prernedering should be canceled.
 IN_PROC_BROWSER_TEST_P(SSLPrerenderBrowserTest,
                        CertificateValidation_Subresource) {
-  base::HistogramTester histogram_tester;
-
   // Navigate to an initial page.
   const GURL kInitialUrl = GetUrl("/empty.html");
   ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
@@ -2725,8 +2826,6 @@
 // resource request is intercepted and sent by a service worker.
 IN_PROC_BROWSER_TEST_P(SSLPrerenderBrowserTest,
                        CertificateValidation_SWMainResource) {
-  base::HistogramTester histogram_tester;
-
   // Register a service worker that intercepts resource requests.
   const GURL kInitialUrl = GetUrl("/workers/service_worker_setup.html");
   ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
@@ -2765,8 +2864,6 @@
   if (GetParam() == SSLPrerenderTestErrorBlockType::kCertError)
     return;
 
-  base::HistogramTester histogram_tester;
-
   // Load an initial page and register a service worker that intercepts
   // resources requests.
   const GURL kInitialUrl = GetUrl("/workers/service_worker_setup.html");
@@ -2947,8 +3044,6 @@
 
 // Tests that prerendering is gated behind CSP:prefetch-src
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, CSPPrefetchSrc) {
-  base::HistogramTester histogram_tester;
-
   GURL initial_url = GetUrl("/empty.html");
   ASSERT_TRUE(NavigateToURL(shell(), initial_url));
   const std::string kCSPScript = R"(
@@ -3007,8 +3102,6 @@
 
 // Tests that prerendering is gated behind CSP:default-src.
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, CSPDefaultSrc) {
-  base::HistogramTester histogram_tester;
-
   GURL initial_url = GetUrl("/empty.html");
   ASSERT_TRUE(NavigateToURL(shell(), initial_url));
   std::string kCSPScript = R"(
@@ -3306,7 +3399,6 @@
 // Tests that we will get the exception from the prerendering if the
 // prerendering page attempts to use notification.
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, NotificationConstructorAndroid) {
-  base::HistogramTester histogram_tester;
   const GURL kInitialUrl = GetUrl("/empty.html");
   const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
 
@@ -3330,7 +3422,6 @@
 // TODO(crbug.com/1215073): Make a WPT when we have a stable way to wait
 // cancellation runs.
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, DownloadByScript) {
-  base::HistogramTester histogram_tester;
   const GURL kInitialUrl = GetUrl("/empty.html");
   const GURL kPrerenderingUrl = GetUrl("/empty.html?prerendering");
 
@@ -3357,7 +3448,6 @@
 }
 
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, DownloadInMainFrame) {
-  base::HistogramTester histogram_tester;
   const GURL kInitialUrl = GetUrl("/empty.html");
 
   // Navigate to an initial page.
@@ -3374,7 +3464,6 @@
 }
 
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, DownloadInSubframe) {
-  base::HistogramTester histogram_tester;
   const GURL kInitialUrl = GetUrl("/empty.html");
   const GURL kPrerenderingUrl = GetUrl("/empty.html?prerendering");
 
@@ -3405,7 +3494,6 @@
 // here, because browser cannot defer this request as the renderer's main thread
 // blocks while it waits for the response.
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, RequestAudioOutputDevice) {
-  base::HistogramTester histogram_tester;
   const GURL kInitialUrl = GetUrl("/empty.html");
   const GURL kPrerenderingUrl = GetUrl("/title1.html");
 
@@ -3433,7 +3521,6 @@
 // Tests that an activated page is allowed to request output devices.
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
                        RequestAudioOutputDeviceAfterActivation) {
-  base::HistogramTester histogram_tester;
   const GURL kInitialUrl = GetUrl("/empty.html");
   const GURL kPrerenderingUrl = GetUrl("/title1.html");
 
@@ -3561,7 +3648,6 @@
 
 // Tests that prerendering doesn't run for low-end devices.
 IN_PROC_BROWSER_TEST_F(PrerenderLowMemoryBrowserTest, NoPrerender) {
-  base::HistogramTester histogram_tester;
   const GURL kInitialUrl = GetUrl("/empty.html");
   const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
 
@@ -3601,7 +3687,6 @@
 
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
                        IsInactiveAndDisallowActivationCancelsPrerendering) {
-  base::HistogramTester histogram_tester;
   const GURL kInitialUrl = GetUrl("/empty.html");
   const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
 
@@ -3799,7 +3884,6 @@
 // Ensure that WebContentsObserver::DidFailLoad is not invoked and cancels
 // prerendering when invoked inside prerender frame tree.
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, DidFailLoadCancelsPrerendering) {
-  base::HistogramTester histogram_tester;
   const GURL kInitialUrl = GetUrl("/empty.html");
   const GURL kPrerenderingUrl = GetUrl("/page_with_iframe.html");
 
@@ -3898,7 +3982,6 @@
 // prerender navigation when activation has already started.
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
                        MainFrameNavigationDuringActivation) {
-  base::HistogramTester histogram_tester;
   const GURL kInitialUrl = GetUrl("/empty.html");
   const GURL kPrerenderingUrl = GetUrl("/empty.html?1");
   const GURL kPrerenderingUrl2 = GetUrl("/empty.html?2");
@@ -4562,7 +4645,6 @@
 
 // Test if the host is abandoned when the renderer page crashes.
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, AbandonIfRendererProcessCrashes) {
-  base::HistogramTester histogram_tester;
   const GURL kInitialUrl = GetUrl("/empty.html");
   const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
 
@@ -4604,7 +4686,6 @@
 
 // Test if the host is abandoned when the renderer page is killed.
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, AbandonIfRendererProcessIsKilled) {
-  base::HistogramTester histogram_tester;
   const GURL kInitialUrl = GetUrl("/empty.html");
   const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
 
@@ -4928,7 +5009,6 @@
 // forward and goes into the BFCache.
 IN_PROC_BROWSER_TEST_P(PrerenderWithBackForwardCacheBrowserTest,
                        CancelOnAfterTriggerIsStoredInBackForwardCache_Forward) {
-  base::HistogramTester histogram_tester;
   const GURL kInitialUrl = GetUrl("/empty.html");
   const GURL kNextUrl = GetUrl("/empty.html?next");
   const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
@@ -4976,7 +5056,6 @@
 // and goes into the BFCache.
 IN_PROC_BROWSER_TEST_P(PrerenderWithBackForwardCacheBrowserTest,
                        CancelOnAfterTriggerIsStoredInBackForwardCache_Back) {
-  base::HistogramTester histogram_tester;
   const GURL kInitialUrl = GetUrl("/empty.html");
   const GURL kNextUrl = GetUrl("/empty.html?next");
   const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
@@ -5365,7 +5444,6 @@
 
 // Tests that cross-origin urls cannot be prerendered.
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, SkipCrossOriginPrerender) {
-  base::HistogramTester histogram_tester;
   const GURL kInitialUrl = GetUrl("/empty.html");
   const GURL kPrerenderingUrl = GetCrossOriginUrl("/empty.html?crossorigin");
 
@@ -5812,7 +5890,6 @@
 // Tests that prerendering is cancelled when a mixed content subframe is
 // detected.
 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, MixedContent) {
-  base::HistogramTester histogram_tester;
   const GURL kInitialUrl = GetUrl("/empty.html");
   const GURL kPrerenderingUrl = GetUrl("/empty.html?prerendering");
 
diff --git a/content/browser/renderer_host/media/media_stream_manager_unittest.cc b/content/browser/renderer_host/media/media_stream_manager_unittest.cc
index 21d937cd..386639ab 100644
--- a/content/browser/renderer_host/media/media_stream_manager_unittest.cc
+++ b/content/browser/renderer_host/media/media_stream_manager_unittest.cc
@@ -1157,17 +1157,16 @@
 
 class MediaStreamManagerTestForTransfers : public MediaStreamManagerTest {
  public:
-  void CustomSetUp(bool create_original_device = true) {
+  void SetUp() override {
     scoped_feature_list_.InitAndEnableFeature(
         features::kMediaStreamTrackTransfer);
     media_stream_manager_->UseFakeUIFactoryForTests(base::BindRepeating([]() {
       return std::make_unique<FakeMediaStreamUIProxy>(
           /*tests_use_fake_render_frame_hosts=*/true);
     }));
+  }
 
-    if (!create_original_device) {
-      return;
-    }
+  void RequestDeviceCaptureTypeAudioDevice() {
     // Generate stream on first renderer.
     original_device_ = CreateOrSearchAudioDeviceStream(
         blink::mojom::StreamSelectionStrategy::FORCE_NEW_STREAM, absl::nullopt);
@@ -1178,6 +1177,44 @@
     EXPECT_NE(transferred_device_.id, original_device_.id);
   }
 
+  void RequestDisplayCaptureTypeDevice(bool request_audio = true,
+                                       bool request_video = true,
+                                       bool transfer_audio = true) {
+    base::RunLoop run_loop;
+
+    blink::StreamControls controls(request_audio, request_video);
+    if (request_audio)
+      controls.audio.stream_type =
+          blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE;
+    if (request_video)
+      controls.video.stream_type =
+          blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE;
+
+    blink::MediaStreamDevice video_device;
+    blink::MediaStreamDevice audio_device;
+    MediaStreamManager::GenerateStreamsCallback generate_stream_callback =
+        base::BindOnce(GenerateStreamsCallback, &run_loop, request_audio,
+                       request_video, &audio_device, &video_device);
+
+    media_stream_manager_->GenerateStreams(
+        render_process_id_, render_frame_id_, requester_id_,
+        /*page_request_id=*/1, controls, MediaDeviceSaltAndOrigin(),
+        /*user_gesture=*/false,
+        StreamSelectionInfo::New(
+            blink::mojom::StreamSelectionStrategy::SEARCH_BY_DEVICE_ID,
+            absl::nullopt),
+        std::move(generate_stream_callback),
+        /*device_stopped_cb=*/base::DoNothing(),
+        /*device_changed_cb=*/base::DoNothing(),
+        /*device_request_state_change_cb=*/base::DoNothing(),
+        /*device_capture_handle_change_cb=*/base::DoNothing());
+    run_loop.Run();
+
+    original_device_ = transfer_audio ? audio_device : video_device;
+    existing_device_session_id_ = original_device_.session_id();
+    EXPECT_NE(transferred_device_.id, original_device_.id);
+  }
+
   void GetOpenDevice() {
     MediaStreamManager::GetOpenDeviceCallback get_open_device_cb =
         base::BindLambdaForTesting(
@@ -1199,6 +1236,7 @@
         /*device_changed_cb=*/base::DoNothing(),
         /*device_request_state_change_cb=*/base::DoNothing(),
         /*device_capture_handle_change_cb=*/base::DoNothing());
+    run_loop_.Run();
   }
 
   void KeepDeviceAlive(bool device_should_be_found = true) {
@@ -1247,12 +1285,54 @@
 
 TEST_F(MediaStreamManagerTestForTransfers,
        GetOpenDeviceForExistingDeviceReturnsDevice) {
-  CustomSetUp();
+  RequestDeviceCaptureTypeAudioDevice();
+  // TODO(https://crbug.com/1288839): GetOpenDevice request for stream device
+  // of type DEVICE_CAPTURE should fail, once it is set as unsupported in the
+  // implementation.
   GetOpenDevice();
   KeepDeviceAlive();
   StopDevice();
 
-  run_loop_.Run();
+  EXPECT_EQ(result_, blink::mojom::MediaStreamRequestResult::OK);
+  EXPECT_EQ(transferred_device_.id, original_device_.id);
+  EXPECT_NE(transferred_device_.session_id(), existing_device_session_id_);
+}
+
+TEST_F(MediaStreamManagerTestForTransfers,
+       GetDisplayMediaAudioAndVideoAndGetOpenDeviceAudioReturnsDevice) {
+  RequestDisplayCaptureTypeDevice();
+  GetOpenDevice();
+  KeepDeviceAlive();
+  StopDevice();
+
+  EXPECT_EQ(result_, blink::mojom::MediaStreamRequestResult::OK);
+  EXPECT_EQ(transferred_device_.id, original_device_.id);
+  EXPECT_NE(transferred_device_.session_id(), existing_device_session_id_);
+}
+
+TEST_F(MediaStreamManagerTestForTransfers,
+       GetDisplayMediaAudioAndVideoAndGetOpenDeviceVideoReturnsDevice) {
+  RequestDisplayCaptureTypeDevice(/*request_audio=*/true,
+                                  /*request_video=*/true,
+                                  /*transfer_audio=*/false);
+  GetOpenDevice();
+  KeepDeviceAlive();
+  StopDevice();
+
+  EXPECT_EQ(result_, blink::mojom::MediaStreamRequestResult::OK);
+  EXPECT_EQ(transferred_device_.id, original_device_.id);
+  EXPECT_NE(transferred_device_.session_id(), existing_device_session_id_);
+}
+
+TEST_F(MediaStreamManagerTestForTransfers,
+       GetDisplayMediaVideoAndGetOpenDeviceVideoReturnsDevice) {
+  RequestDisplayCaptureTypeDevice(/*request_audio=*/false,
+                                  /*request_video=*/true,
+                                  /*transfer_audio=*/false);
+  GetOpenDevice();
+  KeepDeviceAlive();
+  StopDevice();
+
   EXPECT_EQ(result_, blink::mojom::MediaStreamRequestResult::OK);
   EXPECT_EQ(transferred_device_.id, original_device_.id);
   EXPECT_NE(transferred_device_.session_id(), existing_device_session_id_);
@@ -1260,23 +1340,21 @@
 
 TEST_F(MediaStreamManagerTestForTransfers,
        GetOpenDeviceWhenKeepAliveAfterStopDoesNotReturnDevice) {
-  CustomSetUp();
+  RequestDisplayCaptureTypeDevice();
   StopDevice();
   KeepDeviceAlive(/*device_should_be_found=*/false);
   GetOpenDevice();
 
-  run_loop_.Run();
   EXPECT_EQ(result_, blink::mojom::MediaStreamRequestResult::INVALID_STATE);
 }
 
 TEST_F(MediaStreamManagerTestForTransfers,
        GetOpenDeviceWhenKeepAliveBeforeStopReturnsDevice) {
-  CustomSetUp();
+  RequestDisplayCaptureTypeDevice();
   KeepDeviceAlive();
   StopDevice();
   GetOpenDevice();
 
-  run_loop_.Run();
   EXPECT_EQ(result_, blink::mojom::MediaStreamRequestResult::OK);
   EXPECT_EQ(transferred_device_.id, original_device_.id);
   EXPECT_NE(transferred_device_.session_id(), existing_device_session_id_);
@@ -1284,11 +1362,10 @@
 
 TEST_F(MediaStreamManagerTestForTransfers,
        GetOpenDeviceWithoutKeepAliveReturnsDeviceButDoesNotStop) {
-  CustomSetUp();
+  RequestDisplayCaptureTypeDevice();
   GetOpenDevice();
   StopDevice(/*should_stop=*/false);
 
-  run_loop_.Run();
   EXPECT_EQ(result_, blink::mojom::MediaStreamRequestResult::OK);
   EXPECT_EQ(transferred_device_.id, original_device_.id);
   EXPECT_NE(transferred_device_.session_id(), existing_device_session_id_);
@@ -1296,12 +1373,11 @@
 
 TEST_F(MediaStreamManagerTestForTransfers,
        GetOpenDeviceWithKeepAliveAfterStopReturnsDevice) {
-  CustomSetUp();
+  RequestDisplayCaptureTypeDevice();
   GetOpenDevice();
   StopDevice();
   KeepDeviceAlive();
 
-  run_loop_.Run();
   EXPECT_EQ(result_, blink::mojom::MediaStreamRequestResult::OK);
   EXPECT_EQ(transferred_device_.id, original_device_.id);
   EXPECT_NE(transferred_device_.session_id(), existing_device_session_id_);
@@ -1309,10 +1385,8 @@
 
 TEST_F(MediaStreamManagerTestForTransfers,
        GetOpenDeviceForNonExistentDeviceReturnsInvalidState) {
-  CustomSetUp(/*create_original_device=*/false);
   GetOpenDevice();
 
-  run_loop_.Run();
   EXPECT_EQ(result_, blink::mojom::MediaStreamRequestResult::INVALID_STATE);
 }
 
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc
index 4c19dbd0..778c7715 100644
--- a/content/child/runtime_features.cc
+++ b/content/child/runtime_features.cc
@@ -336,8 +336,6 @@
           {"AutofillShadowDOM", blink::features::kAutofillShadowDOM},
           {"AndroidDownloadableFontsMatching",
            features::kAndroidDownloadableFontsMatching},
-          {"CancelFormSubmissionInDefaultHandler",
-           blink::features::kCancelFormSubmissionInDefaultHandler},
           {"BatchFetchRequests", blink::features::kBatchFetchRequests},
           {"ClipboardCustomFormats", blink::features::kClipboardCustomFormats},
           {"CSSContainerQueries", blink::features::kCSSContainerQueries},
diff --git a/content/public/browser/url_data_source.cc b/content/public/browser/url_data_source.cc
index 0ac7cac..fc37ef1 100644
--- a/content/public/browser/url_data_source.cc
+++ b/content/public/browser/url_data_source.cc
@@ -54,15 +54,6 @@
   return std::string();
 }
 
-std::string URLDataSource::GetMimeType(const GURL& url) {
-  return GetMimeType(URLDataSource::URLToRequestPath(url));
-}
-
-std::string URLDataSource::GetMimeType(const std::string& path) {
-  NOTREACHED();
-  return std::string();
-}
-
 bool URLDataSource::ShouldReplaceExistingSource() {
   return true;
 }
diff --git a/content/public/browser/url_data_source.h b/content/public/browser/url_data_source.h
index cdda3262..513a2ff 100644
--- a/content/public/browser/url_data_source.h
+++ b/content/public/browser/url_data_source.h
@@ -69,12 +69,7 @@
 
   // Return the mimetype that should be sent with this response, or empty
   // string to specify no mime type.
-  virtual std::string GetMimeType(const GURL& url);
-
-  // Deprecated. Prefer method above.
-  // TODO(crbug.com/1344742): Remove after migrating to
-  // `GetMimeType(const GURL& url)`.
-  virtual std::string GetMimeType(const std::string& path);
+  virtual std::string GetMimeType(const GURL& url) = 0;
 
   // Returns true if the URLDataSource should replace an existing URLDataSource
   // with the same name that has already been registered. The default is true.
diff --git a/content/public/browser/webui_config_map.cc b/content/public/browser/webui_config_map.cc
index 9b38026..10f44ca 100644
--- a/content/public/browser/webui_config_map.cc
+++ b/content/public/browser/webui_config_map.cc
@@ -106,7 +106,7 @@
   return config.get();
 }
 
-std::unique_ptr<WebUIConfig> WebUIConfigMap::RemoveForTesting(
+std::unique_ptr<WebUIConfig> WebUIConfigMap::RemoveConfig(
     const url::Origin& origin) {
   auto it = configs_map_.find(origin);
   if (it == configs_map_.end())
diff --git a/content/public/browser/webui_config_map.h b/content/public/browser/webui_config_map.h
index af6c1a58..51eb1f1 100644
--- a/content/public/browser/webui_config_map.h
+++ b/content/public/browser/webui_config_map.h
@@ -55,7 +55,7 @@
 
   // Removes and returns the WebUIConfig with |origin|. Returns nullptr if
   // there is no WebUIConfig with |origin|.
-  std::unique_ptr<WebUIConfig> RemoveForTesting(const url::Origin& origin);
+  std::unique_ptr<WebUIConfig> RemoveConfig(const url::Origin& origin);
 
   // Returns the size of the map, i.e. how many WebUIConfigs are registered.
   size_t GetSizeForTesting() { return configs_map_.size(); }
diff --git a/content/public/test/scoped_web_ui_controller_factory_registration.cc b/content/public/test/scoped_web_ui_controller_factory_registration.cc
index 2495086e..c8ce1ad 100644
--- a/content/public/test/scoped_web_ui_controller_factory_registration.cc
+++ b/content/public/test/scoped_web_ui_controller_factory_registration.cc
@@ -73,14 +73,14 @@
     std::unique_ptr<WebUIConfig> webui_config)
     : webui_config_origin_(url::Origin::Create(webui_origin)) {
   auto& config_map = WebUIConfigMap::GetInstance();
-  replaced_webui_config_ = config_map.RemoveForTesting(webui_config_origin_);
+  replaced_webui_config_ = config_map.RemoveConfig(webui_config_origin_);
 
   if (webui_config != nullptr)
     AddWebUIConfig(std::move(webui_config));
 }
 
 ScopedWebUIConfigRegistration::~ScopedWebUIConfigRegistration() {
-  WebUIConfigMap::GetInstance().RemoveForTesting(webui_config_origin_);
+  WebUIConfigMap::GetInstance().RemoveConfig(webui_config_origin_);
 
   // If we replaced a WebUIConfig, re-register it to keep the global state
   // clean for future tests.
diff --git a/content/renderer/browser_render_view_browsertest.cc b/content/renderer/browser_render_view_browsertest.cc
index 1e32972..9e2a28e 100644
--- a/content/renderer/browser_render_view_browsertest.cc
+++ b/content/renderer/browser_render_view_browsertest.cc
@@ -142,14 +142,12 @@
 };
 
 // https://crbug.com/788788
-// TODO(crbug.com/1349962): Flaky on linux-tsan too.
-#if (BUILDFLAG(IS_ANDROID) && defined(ADDRESS_SANITIZER)) || \
-    (BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER))
+#if BUILDFLAG(IS_ANDROID) && defined(ADDRESS_SANITIZER)
 #define MAYBE_ConfirmCacheInformationPlumbed \
   DISABLED_ConfirmCacheInformationPlumbed
 #else
 #define MAYBE_ConfirmCacheInformationPlumbed ConfirmCacheInformationPlumbed
-#endif
+#endif  // BUILDFLAG(IS_ANDROID) && defined(ADDRESS_SANITIZER)
 IN_PROC_BROWSER_TEST_F(RenderViewBrowserTest,
                        MAYBE_ConfirmCacheInformationPlumbed) {
   ASSERT_TRUE(embedded_test_server()->Start());
diff --git a/content/renderer/render_thread_impl_discardable_memory_browsertest.cc b/content/renderer/render_thread_impl_discardable_memory_browsertest.cc
index 5f15087..c096a41 100644
--- a/content/renderer/render_thread_impl_discardable_memory_browsertest.cc
+++ b/content/renderer/render_thread_impl_discardable_memory_browsertest.cc
@@ -92,14 +92,8 @@
   base::DiscardableMemoryAllocator* discardable_memory_allocator_;
 };
 
-// Flaky.  http://crbug.com/1350563
-#if defined(THREAD_SANITIZER)
-#define MAYBE_LockDiscardableMemory DISABLED_LockDiscardableMemory
-#else
-#define MAYBE_LockDiscardableMemory LockDiscardableMemory
-#endif
 IN_PROC_BROWSER_TEST_F(RenderThreadImplDiscardableMemoryBrowserTest,
-                       MAYBE_LockDiscardableMemory) {
+                       LockDiscardableMemory) {
   const size_t kSize = 1024 * 1024;  // 1MiB.
 
   std::unique_ptr<base::DiscardableMemory> memory =
@@ -154,14 +148,8 @@
 }
 #endif
 
-// Flaky.  http://crbug.com/1350563
-#if defined(THREAD_SANITIZER)
-#define MAYBE_ReleaseFreeDiscardableMemory DISABLED_ReleaseFreeDiscardableMemory
-#else
-#define MAYBE_ReleaseFreeDiscardableMemory ReleaseFreeDiscardableMemory
-#endif
 IN_PROC_BROWSER_TEST_F(RenderThreadImplDiscardableMemoryBrowserTest,
-                       MAYBE_ReleaseFreeDiscardableMemory) {
+                       ReleaseFreeDiscardableMemory) {
   const size_t kSize = 1024 * 1024;  // 1MiB.
 
   base::DiscardableMemoryBacking impl = base::GetDiscardableMemoryBacking();
@@ -220,14 +208,8 @@
   EXPECT_EQ(0U, discardable_memory_allocator()->GetBytesAllocated());
 }
 
-// Flaky.  http://crbug.com/1350563
-#if defined(THREAD_SANITIZER)
-#define MAYBE_CheckReleaseMemory DISABLED_CheckReleaseMemory
-#else
-#define MAYBE_CheckReleaseMemory CheckReleaseMemory
-#endif
 IN_PROC_BROWSER_TEST_F(RenderThreadImplDiscardableMemoryBrowserTest,
-                       MAYBE_CheckReleaseMemory) {
+                       CheckReleaseMemory) {
   std::vector<std::unique_ptr<base::DiscardableMemory>> all_memory;
   auto* allocator =
       static_cast<discardable_memory::ClientDiscardableSharedMemoryManager*>(
diff --git a/content/test/data/accessibility/html/svg-expected-mac.txt b/content/test/data/accessibility/html/svg-expected-mac.txt
index 0febc6e..22bff96 100644
--- a/content/test/data/accessibility/html/svg-expected-mac.txt
+++ b/content/test/data/accessibility/html/svg-expected-mac.txt
@@ -1,5 +1,5 @@
 AXWebArea
 ++AXGroup
-++++AXImage AXDescription='svg' AXHelp='SVG Title Tag'
+++++AXGroup AXDescription='svg' AXHelp='SVG Title Tag'
 ++++++AXGroup
 ++++++++AXStaticText AXValue='Test'
diff --git a/content/test/data/simple_links.html b/content/test/data/simple_links.html
index 381ed5e..30fc53e 100644
--- a/content/test/data/simple_links.html
+++ b/content/test/data/simple_links.html
@@ -31,6 +31,14 @@
     return simulateClick(document.getElementById("same_site_new_window_link"));
   }
 
+  function clickSameSiteNewWindowWithNoopenerLink() {
+    return simulateClick(document.getElementById("same_site_new_window_with_noopener_link"));
+  }
+
+  function clickSameSiteNewWindowWithOpenerLink() {
+    return simulateClick(document.getElementById("same_site_new_window_with_opener_link"));
+  }
+
   function clickCrossSiteLink() {
     return simulateClick(document.getElementById("cross_site_link"));
   }
@@ -60,7 +68,9 @@
 <a href="title2.html" id="same_site_link">same-site</a><br>
 <a href="http://foo.com/title2.html" id="cross_site_link">cross-site</a><br>
 <a href="view-source:about:blank" id="view_source_link">view-source:</a><br>
-<a href="title2.html" id="same_site_new_window_link" rel="opener" target="_blank">same-site new window</a>
+<a href="title2.html" id="same_site_new_window_link" target="_blank">same-site new window</a>
+<a href="title2.html" id="same_site_new_window_with_noopener_link" rel="noopener" target="_blank">same-site new window with noopener</a>
+<a href="title2.html" id="same_site_new_window_with_opener_link" rel="opener" target="_blank">same-site new window with opener</a>
 <a href="http://foo.com/title2.html" id="cross_site_new_window_link" rel="opener" target="_blank">cross-site new window</a>
 <a href="http://foo.com/title2.html" id="cross_site_new_window_no_opener_link" rel="noopener" target="_blank">cross-site new window no opener</a>
 <a href="" id="linkToSelf" rel="opener" target="_blank">self new window</a>
diff --git a/docs/updater/functional_spec.md b/docs/updater/functional_spec.md
index a1cfdb68..ce0326a 100644
--- a/docs/updater/functional_spec.md
+++ b/docs/updater/functional_spec.md
@@ -382,6 +382,10 @@
 Policies may be set by platform-specific means (group policy on Windows, managed
 preferences on macOS), or by communication with the device management server.
 
+For device management, the enterprise policies for Google applications are
+downloaded from the device management server periodically and stored at
+`%ProgramFiles(x86)%\Google\Policies` in the Windows file system.
+
 TODO(crbug.com/1339451): Document how conflicts between multiple policy sources
 are resolved.
 
diff --git a/extensions/browser/api/socket/socket_api.cc b/extensions/browser/api/socket/socket_api.cc
index 0563edf..7ebf328 100644
--- a/extensions/browser/api/socket/socket_api.cc
+++ b/extensions/browser/api/socket/socket_api.cc
@@ -23,6 +23,7 @@
 #include "extensions/browser/api/socket/tls_socket.h"
 #include "extensions/browser/api/socket/udp_socket.h"
 #include "extensions/browser/extension_system.h"
+#include "extensions/common/api/sockets/sockets_manifest_data.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/permissions/permissions_data.h"
 #include "extensions/common/permissions/socket_permission.h"
@@ -65,6 +66,7 @@
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 const char kFirewallFailure[] = "Failed to open firewall port";
+const char kCrOSTerminal[] = "chrome-untrusted://terminal";
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 bool IsPortValid(int port) {
@@ -85,19 +87,19 @@
 }
 
 Socket* SocketApiFunction::GetSocket(int api_resource_id) {
-  return manager_->Get(extension_id(), api_resource_id);
+  return manager_->Get(GetOriginId(), api_resource_id);
 }
 
 void SocketApiFunction::ReplaceSocket(int api_resource_id, Socket* socket) {
-  manager_->Replace(extension_id(), api_resource_id, socket);
+  manager_->Replace(GetOriginId(), api_resource_id, socket);
 }
 
 std::unordered_set<int>* SocketApiFunction::GetSocketIds() {
-  return manager_->GetResourceIds(extension_id());
+  return manager_->GetResourceIds(GetOriginId());
 }
 
 void SocketApiFunction::RemoveSocket(int api_resource_id) {
-  manager_->Remove(extension_id(), api_resource_id);
+  manager_->Remove(GetOriginId(), api_resource_id);
 }
 
 std::unique_ptr<SocketResourceManagerInterface>
@@ -125,7 +127,7 @@
     AppFirewallHoleManager* manager =
         AppFirewallHoleManager::Get(browser_context());
     std::unique_ptr<AppFirewallHole> hole(
-        manager->Open(type, local_address.port(), extension_id()).release());
+        manager->Open(type, local_address.port(), GetOriginId()).release());
 
     if (!hole) {
       Respond(ErrorWithCode(-1, kFirewallFailure));
@@ -157,6 +159,43 @@
   return ErrorWithArguments(std::move(args), error);
 }
 
+std::string SocketApiFunction::GetOriginId() const {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  // Terminal app is the only non-extension to use sockets (crbug.com/1350479).
+  if (!extension()) {
+    auto origin = url::Origin::Create(source_url()).Serialize();
+    CHECK_EQ(origin, kCrOSTerminal);
+    return origin;
+  }
+#endif
+  return extension_id();
+}
+
+bool SocketApiFunction::CheckPermission(
+    const APIPermission::CheckParam& param) const {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  // Terminal app is the only non-extension to use sockets (crbug.com/1350479).
+  if (!extension()) {
+    CHECK_EQ(url::Origin::Create(source_url()).Serialize(), kCrOSTerminal);
+    return true;
+  }
+#endif
+  return extension()->permissions_data()->CheckAPIPermissionWithParam(
+      APIPermissionID::kSocket, &param);
+}
+
+bool SocketApiFunction::CheckRequest(
+    const content::SocketPermissionRequest& param) const {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  // Terminal app is the only non-extension to use sockets (crbug.com/1350479).
+  if (!extension()) {
+    CHECK_EQ(url::Origin::Create(source_url()).Serialize(), kCrOSTerminal);
+    return true;
+  }
+#endif
+  return SocketsManifestData::CheckRequest(extension(), param);
+}
+
 SocketExtensionWithDnsLookupFunction::SocketExtensionWithDnsLookupFunction() =
     default;
 
@@ -177,7 +216,8 @@
   DCHECK(pending_host_resolver_);
 
   host_resolver_.Bind(std::move(pending_host_resolver_));
-  url::Origin origin = extension_->origin();
+  url::Origin origin =
+      extension() ? extension()->origin() : url::Origin::Create(source_url());
   network::mojom::ResolveHostParametersPtr params =
       network::mojom::ResolveHostParameters::New();
   params->dns_query_type = dns_query_type;
@@ -220,7 +260,7 @@
   Socket* socket = nullptr;
   switch (params->type) {
     case extensions::api::socket::SOCKET_TYPE_TCP:
-      socket = new TCPSocket(browser_context(), extension_id());
+      socket = new TCPSocket(browser_context(), GetOriginId());
       break;
 
     case extensions::api::socket::SOCKET_TYPE_UDP: {
@@ -236,7 +276,7 @@
                             std::move(listener_remote));
       socket =
           new UDPSocket(std::move(udp_socket),
-                        std::move(socket_listener_receiver), extension_id());
+                        std::move(socket_listener_receiver), GetOriginId());
       break;
     }
     case extensions::api::socket::SOCKET_TYPE_NONE:
@@ -302,8 +342,7 @@
   }
 
   SocketPermission::CheckParam param(operation_type, hostname_, port_);
-  if (!extension()->permissions_data()->CheckAPIPermissionWithParam(
-          APIPermissionID::kSocket, &param)) {
+  if (!CheckPermission(param)) {
     return RespondNow(ErrorWithCode(-1, kPermissionError));
   }
 
@@ -488,7 +527,7 @@
   if (result_code == net::OK) {
     Socket* client_socket =
         new TCPSocket(std::move(socket), std::move(receive_pipe_handle),
-                      std::move(send_pipe_handle), remote_addr, extension_id());
+                      std::move(send_pipe_handle), remote_addr, GetOriginId());
     result.SetIntKey(kSocketIdKey, AddSocket(client_socket));
   }
   Respond(OneArgument(std::move(result)));
@@ -640,8 +679,7 @@
   if (socket->GetSocketType() == Socket::TYPE_UDP) {
     SocketPermission::CheckParam param(
         SocketPermissionRequest::UDP_SEND_TO, hostname_, port_);
-    if (!extension()->permissions_data()->CheckAPIPermissionWithParam(
-            APIPermissionID::kSocket, &param)) {
+    if (!CheckPermission(param)) {
       return RespondNow(ErrorWithCode(-1, kPermissionError));
     }
   }
@@ -824,8 +862,7 @@
       kWildcardAddress,
       kWildcardPort);
 
-  if (!extension()->permissions_data()->CheckAPIPermissionWithParam(
-          APIPermissionID::kSocket, &param)) {
+  if (!CheckPermission(param)) {
     return RespondNow(ErrorWithCode(-1, kPermissionError));
   }
 
@@ -866,8 +903,7 @@
       SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP,
       kWildcardAddress,
       kWildcardPort);
-  if (!extension()->permissions_data()->CheckAPIPermissionWithParam(
-          APIPermissionID::kSocket, &param)) {
+  if (!CheckPermission(param)) {
     return RespondNow(ErrorWithCode(-1, kPermissionError));
   }
 
@@ -966,8 +1002,7 @@
       SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP,
       kWildcardAddress,
       kWildcardPort);
-  if (!extension()->permissions_data()->CheckAPIPermissionWithParam(
-          APIPermissionID::kSocket, &param)) {
+  if (!CheckPermission(param)) {
     return RespondNow(ErrorWithCode(-1, kPermissionError));
   }
 
@@ -1028,7 +1063,7 @@
   auto socket =
       std::make_unique<TLSSocket>(std::move(tls_socket), local_addr, peer_addr,
                                   std::move(receive_pipe_handle),
-                                  std::move(send_pipe_handle), extension_id());
+                                  std::move(send_pipe_handle), GetOriginId());
   ReplaceSocket(params_->socket_id, socket.release());
   Respond(OneArgument(base::Value(result)));
 }
diff --git a/extensions/browser/api/socket/socket_api.h b/extensions/browser/api/socket/socket_api.h
index 0d0a635..ba622a6 100644
--- a/extensions/browser/api/socket/socket_api.h
+++ b/extensions/browser/api/socket/socket_api.h
@@ -18,10 +18,12 @@
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/common/socket_permission_request.h"
 #include "extensions/browser/api/api_resource_manager.h"
 #include "extensions/browser/api/async_api_function.h"
 #include "extensions/browser/extension_function.h"
 #include "extensions/common/api/socket.h"
+#include "extensions/common/permissions/api_permission.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
@@ -138,6 +140,16 @@
   // one integer value.
   ResponseValue ErrorWithCode(int error_code, const std::string& error);
 
+  // Either extension_id() or url origin for CrOS Terminal.
+  std::string GetOriginId() const;
+
+  // Checks extension()->permissions_data(), or returns true for CrOS Terminal.
+  bool CheckPermission(const APIPermission::CheckParam& param) const;
+
+  // Checks SocketsManifestData::CheckRequest() if extension(), or returns true
+  // for CrOS Terminal.
+  bool CheckRequest(const content::SocketPermissionRequest& param) const;
+
   virtual std::unique_ptr<SocketResourceManagerInterface>
   CreateSocketResourceManager();
 
diff --git a/extensions/browser/api/sockets_tcp/sockets_tcp_api.cc b/extensions/browser/api/sockets_tcp/sockets_tcp_api.cc
index ef163fe..50937379 100644
--- a/extensions/browser/api/sockets_tcp/sockets_tcp_api.cc
+++ b/extensions/browser/api/sockets_tcp/sockets_tcp_api.cc
@@ -18,7 +18,6 @@
 #include "extensions/browser/api/socket/tcp_socket.h"
 #include "extensions/browser/api/socket/tls_socket.h"
 #include "extensions/browser/api/sockets_tcp/tcp_socket_event_dispatcher.h"
-#include "extensions/common/api/sockets/sockets_manifest_data.h"
 #include "extensions/common/api/sockets_tcp.h"
 #include "net/base/net_errors.h"
 
@@ -129,7 +128,7 @@
   EXTENSION_FUNCTION_VALIDATE(params.get());
 
   ResumableTCPSocket* socket =
-      new ResumableTCPSocket(browser_context(), extension_id());
+      new ResumableTCPSocket(browser_context(), GetOriginId());
 
   sockets_tcp::SocketProperties* properties = params->properties.get();
   if (properties) {
@@ -185,8 +184,7 @@
   if (socket->paused() != params->paused) {
     socket->set_paused(params->paused);
     if (socket->IsConnected() && !params->paused) {
-      socket_event_dispatcher->OnSocketResume(extension_id(),
-                                              params->socket_id);
+      socket_event_dispatcher->OnSocketResume(GetOriginId(), params->socket_id);
     }
   }
 
@@ -291,7 +289,7 @@
   content::SocketPermissionRequest param(SocketPermissionRequest::TCP_CONNECT,
                                          params_->peer_address,
                                          params_->peer_port);
-  if (!SocketsManifestData::CheckRequest(extension(), param)) {
+  if (!CheckRequest(param)) {
     return RespondNow(Error(kPermissionError));
   }
 
@@ -322,7 +320,7 @@
 
 void SocketsTcpConnectFunction::OnCompleted(int net_result) {
   if (net_result == net::OK) {
-    socket_event_dispatcher_->OnSocketConnect(extension_id(),
+    socket_event_dispatcher_->OnSocketConnect(GetOriginId(),
                                               params_->socket_id);
   }
 
@@ -525,7 +523,7 @@
   auto socket =
       std::make_unique<TLSSocket>(std::move(tls_socket), local_addr, peer_addr,
                                   std::move(receive_pipe_handle),
-                                  std::move(send_pipe_handle), extension_id());
+                                  std::move(send_pipe_handle), GetOriginId());
   socket->set_persistent(persistent_);
   socket->set_paused(paused_);
   ReplaceSocket(params_->socket_id, socket.release());
diff --git a/extensions/browser/app_window/app_window.h b/extensions/browser/app_window/app_window.h
index b0d66d3..1d3b30b 100644
--- a/extensions/browser/app_window/app_window.h
+++ b/extensions/browser/app_window/app_window.h
@@ -18,6 +18,7 @@
 #include "components/web_modal/web_contents_modal_dialog_manager_delegate.h"
 #include "content/public/browser/web_contents_delegate.h"
 #include "content/public/browser/web_contents_observer.h"
+#include "extensions/browser/app_window/native_app_window.h"
 #include "extensions/browser/extension_function_dispatcher.h"
 #include "extensions/browser/extension_registry_observer.h"
 #include "ui/base/ui_base_types.h"  // WindowShowState
@@ -42,7 +43,6 @@
 class AppDelegate;
 class AppWebContentsHelper;
 class Extension;
-class NativeAppWindow;
 class PlatformAppBrowserTest;
 
 struct DraggableRegion;
@@ -385,6 +385,11 @@
     app_window_contents_ = std::move(contents);
   }
 
+  void SetNativeAppWindowForTesting(
+      std::unique_ptr<NativeAppWindow> native_app_window) {
+    native_app_window_ = std::move(native_app_window);
+  }
+
   bool DidFinishFirstNavigation() { return did_finish_first_navigation_; }
 
  protected:
diff --git a/extensions/browser/network_permissions_updater.cc b/extensions/browser/network_permissions_updater.cc
index 257e91f..17c7f91 100644
--- a/extensions/browser/network_permissions_updater.cc
+++ b/extensions/browser/network_permissions_updater.cc
@@ -27,6 +27,7 @@
 void NetworkPermissionsUpdater::UpdateExtension(
     content::BrowserContext& browser_context,
     const Extension& extension,
+    ContextSet context_set,
     base::OnceClosure completion_callback) {
   auto updater = std::make_unique<NetworkPermissionsUpdater>(
       PassKey(), browser_context, std::move(completion_callback));
@@ -35,7 +36,7 @@
   // The callback takes ownership of `updater`, ensuring it's deleted when
   // the update completes.
   updater_raw->UpdateExtension(
-      extension,
+      extension, context_set,
       base::BindOnce(&NetworkPermissionsUpdater::OnOriginAccessUpdated,
                      std::move(updater)));
 }
@@ -58,20 +59,30 @@
       base::BindOnce(&NetworkPermissionsUpdater::OnOriginAccessUpdated,
                      std::move(updater)));
 
-  for (const auto& extension : extensions)
-    updater_raw->UpdateExtension(*extension, barrier_closure);
+  // When updating all extensions, we always use "all related contexts".
+  constexpr ContextSet kContextSet = ContextSet::kAllRelatedContexts;
+
+  for (const auto& extension : extensions) {
+    updater_raw->UpdateExtension(*extension, kContextSet, barrier_closure);
+  }
 }
 
 void NetworkPermissionsUpdater::UpdateExtension(
     const Extension& extension,
+    ContextSet context_set,
     base::OnceClosure completion_callback) {
-  // Non-tab-specific extension permissions are shared across profiles (even for
-  // split-mode extensions), so we update all profiles the extension is enabled
-  // for.
-  util::SetCorsOriginAccessListForExtension(
-      ExtensionsBrowserClient::Get()->GetRelatedContextsForExtension(
-          browser_context_, extension),
-      extension, std::move(completion_callback));
+  std::vector<content::BrowserContext*> target_contexts;
+  if (context_set == ContextSet::kCurrentContextOnly) {
+    target_contexts = {browser_context_.get()};
+  } else {
+    DCHECK_EQ(ContextSet::kAllRelatedContexts, context_set);
+    target_contexts =
+        ExtensionsBrowserClient::Get()->GetRelatedContextsForExtension(
+            browser_context_, extension);
+  }
+
+  util::SetCorsOriginAccessListForExtension(target_contexts, extension,
+                                            std::move(completion_callback));
 }
 
 // static
diff --git a/extensions/browser/network_permissions_updater.h b/extensions/browser/network_permissions_updater.h
index d7a95888..e95b8b2 100644
--- a/extensions/browser/network_permissions_updater.h
+++ b/extensions/browser/network_permissions_updater.h
@@ -28,6 +28,17 @@
  public:
   using PassKey = base::PassKey<NetworkPermissionsUpdater>;
 
+  // The contexts to include for when updating the extension.
+  enum class ContextSet {
+    // Only the current context will be updated. Use this when the permission
+    // is related to a specific context (like a specific tab).
+    kCurrentContextOnly,
+    // All related contexts the extension is allowed to run in will be updated.
+    // Use this when the permission is related to both contexts (like a
+    // permission grant on the extension).
+    kAllRelatedContexts,
+  };
+
   // Pseudo-private ctor. This is public so that it can be used with
   // std::make_unique<>, but guarded via the PassKey. Consumers should only use
   // the static methods below.
@@ -40,11 +51,15 @@
   // `completion_callback` when the operation is complete.
   static void UpdateExtension(content::BrowserContext& browser_context,
                               const Extension& extension,
+                              ContextSet context_set,
                               base::OnceClosure completion_callback);
 
   // Updates the permissions of all extensions related to the (original)
   // `browser_context`. Invokes `completion_callback` when the operation is
   // complete.
+  // Updating all extensions always uses `ContextSet::kAllRelatedContexts` as
+  // there (currently) are no situations in which all extensions should be
+  // updated for a context-specific reason.
   static void UpdateAllExtensions(content::BrowserContext& browser_context,
                                   base::OnceClosure completion_callback);
 
@@ -52,6 +67,7 @@
   // Updates a single extension in the network layer, invoking
   // `completion_callback` when the operation is complete.
   void UpdateExtension(const Extension& extension,
+                       ContextSet context_set,
                        base::OnceClosure completion_callback);
 
   // Invoked when all updates are complete in order to dispatch
diff --git a/extensions/browser/renderer_startup_helper.cc b/extensions/browser/renderer_startup_helper.cc
index 7a482889..97fbee44 100644
--- a/extensions/browser/renderer_startup_helper.cc
+++ b/extensions/browser/renderer_startup_helper.cc
@@ -22,9 +22,9 @@
 #include "extensions/browser/extension_util.h"
 #include "extensions/browser/extensions_browser_client.h"
 #include "extensions/browser/guest_view/web_view/web_view_guest.h"
+#include "extensions/browser/network_permissions_updater.h"
 #include "extensions/browser/service_worker_task_queue.h"
 #include "extensions/common/activation_sequence.h"
-#include "extensions/common/cors_util.h"
 #include "extensions/common/extension_messages.h"
 #include "extensions/common/extension_set.h"
 #include "extensions/common/extensions_client.h"
@@ -255,10 +255,13 @@
   // always be called before creating URLLoaderFactory for any extension frames
   // that might be eventually hosted inside the renderer `process` (this
   // Browser-side ordering will be replicated within the NetworkService because
-  // SetCorsOriginAccessListsForOrigin and CreateURLLoaderFactory are 2 methods
+  // `SetCorsOriginAccessListsForOrigin()`, which is used in
+  // NetworkPermissionsUpdater, and `CreateURLLoaderFactory()` are 2 methods
   // of the same mojom::NetworkContext interface).
-  util::SetCorsOriginAccessListForExtension({process->GetBrowserContext()},
-                                            extension, base::DoNothing());
+  NetworkPermissionsUpdater::UpdateExtension(
+      *process->GetBrowserContext(), extension,
+      NetworkPermissionsUpdater::ContextSet::kCurrentContextOnly,
+      base::DoNothing());
 
   auto remote = process_mojo_map_.find(process);
   if (remote != process_mojo_map_.end()) {
diff --git a/extensions/common/api/_api_features.json b/extensions/common/api/_api_features.json
index bb5bae5..aca17d0 100644
--- a/extensions/common/api/_api_features.json
+++ b/extensions/common/api/_api_features.json
@@ -584,10 +584,16 @@
     "dependencies": ["permission:socket"],
     "contexts": ["blessed_extension"]
   },
-  "sockets.tcp": {
+  "sockets.tcp": [{
     "dependencies": ["manifest:sockets"],
     "contexts": ["blessed_extension"]
-  },
+  },{
+    "channel": "stable",
+    "contexts": ["webui_untrusted"],
+    "matches": [
+      "chrome-untrusted://terminal/*"
+    ]
+  }],
   "sockets.tcpServer": {
     "dependencies": ["manifest:sockets"],
     "contexts": ["blessed_extension"]
diff --git a/extensions/common/extension_features.cc b/extensions/common/extension_features.cc
index 60567f3..ea3139b 100644
--- a/extensions/common/extension_features.cc
+++ b/extensions/common/extension_features.cc
@@ -46,9 +46,14 @@
 const base::Feature kAllowSharedArrayBuffersUnconditionally{
     "AllowSharedArrayBuffersUnconditionally", base::FEATURE_ENABLED_BY_DEFAULT};
 
-// Enables the CryptoToken component extension, which implements the deprecated
-// U2F Security Key API. Once this flag is default disabled sites can continue
-// to use CryptoToken via a Deprecation Trail with the same name.
+// Loads the CryptoToken component extension, which implements the deprecated
+// U2F Security Key API.
+// TODO(1224886): Delete together with CryptoToken code.
+const base::Feature kLoadCryptoTokenExtension{
+    "LoadCryptoTokenExtension", base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Enables the CryptoToken component extension to receive messages. This flag
+// has no effect unless `kLoadCryptoTokenExtension` is also enabled.
 // TODO(1224886): Delete together with CryptoToken code.
 const base::Feature kU2FSecurityKeyAPI{"U2FSecurityKeyAPI",
                                        base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/extensions/common/extension_features.h b/extensions/common/extension_features.h
index 100295c4..9e7c281 100644
--- a/extensions/common/extension_features.h
+++ b/extensions/common/extension_features.h
@@ -22,6 +22,8 @@
 
 extern const base::Feature kAllowSharedArrayBuffersUnconditionally;
 
+extern const base::Feature kLoadCryptoTokenExtension;
+
 extern const base::Feature kU2FSecurityKeyAPI;
 
 extern const base::Feature kStructuredCloningForMV3Messaging;
diff --git a/infra/config/generated/builders/ci/ios16-sdk-simulator/properties.json b/infra/config/generated/builders/ci/ios16-sdk-simulator/properties.json
index 550ddce..a7e9bb75 100644
--- a/infra/config/generated/builders/ci/ios16-sdk-simulator/properties.json
+++ b/infra/config/generated/builders/ci/ios16-sdk-simulator/properties.json
@@ -59,5 +59,5 @@
   },
   "builder_group": "chromium.fyi",
   "recipe": "chromium",
-  "xcode_build_version": "14a5284g"
+  "xcode_build_version": "14a5294e"
 }
\ No newline at end of file
diff --git a/infra/config/generated/builders/try/ios16-sdk-simulator/properties.json b/infra/config/generated/builders/try/ios16-sdk-simulator/properties.json
index 833d817..0347bc18 100644
--- a/infra/config/generated/builders/try/ios16-sdk-simulator/properties.json
+++ b/infra/config/generated/builders/try/ios16-sdk-simulator/properties.json
@@ -53,5 +53,5 @@
   },
   "builder_group": "tryserver.chromium.mac",
   "recipe": "chromium_trybot",
-  "xcode_build_version": "14a5284g"
+  "xcode_build_version": "14a5294e"
 }
\ No newline at end of file
diff --git a/infra/config/generated/builders/webrtc.fyi/WebRTC Chromium FYI ios-device/properties.json b/infra/config/generated/builders/webrtc.fyi/WebRTC Chromium FYI ios-device/properties.json
index 4a1ddcf..c1f79de 100644
--- a/infra/config/generated/builders/webrtc.fyi/WebRTC Chromium FYI ios-device/properties.json
+++ b/infra/config/generated/builders/webrtc.fyi/WebRTC Chromium FYI ios-device/properties.json
@@ -13,5 +13,5 @@
   },
   "builder_group": "chromium.webrtc.fyi",
   "recipe": "chromium",
-  "xcode_build_version": "13c100"
+  "xcode_build_version": "14a5284g"
 }
\ No newline at end of file
diff --git a/infra/config/generated/builders/webrtc.fyi/WebRTC Chromium FYI ios-simulator/properties.json b/infra/config/generated/builders/webrtc.fyi/WebRTC Chromium FYI ios-simulator/properties.json
index 4a1ddcf..c1f79de 100644
--- a/infra/config/generated/builders/webrtc.fyi/WebRTC Chromium FYI ios-simulator/properties.json
+++ b/infra/config/generated/builders/webrtc.fyi/WebRTC Chromium FYI ios-simulator/properties.json
@@ -13,5 +13,5 @@
   },
   "builder_group": "chromium.webrtc.fyi",
   "recipe": "chromium",
-  "xcode_build_version": "13c100"
+  "xcode_build_version": "14a5284g"
 }
\ No newline at end of file
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index 3903ce0d..c2194c9 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -33697,8 +33697,8 @@
         '}'
       execution_timeout_secs: 36000
       caches {
-        name: "xcode_ios_14a5284g"
-        path: "xcode_ios_14a5284g.app"
+        name: "xcode_ios_14a5294e"
+        path: "xcode_ios_14a5294e.app"
       }
       build_numbers: YES
       service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
@@ -66197,8 +66197,8 @@
         path: "win_toolchain"
       }
       caches {
-        name: "xcode_ios_14a5284g"
-        path: "xcode_ios_14a5284g.app"
+        name: "xcode_ios_14a5294e"
+        path: "xcode_ios_14a5294e.app"
       }
       build_numbers: YES
       service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
@@ -82205,8 +82205,8 @@
         '}'
       execution_timeout_secs: 7200
       caches {
-        name: "xcode_ios_13c100"
-        path: "xcode_ios_13c100.app"
+        name: "xcode_ios_14a5284g"
+        path: "xcode_ios_14a5284g.app"
       }
       build_numbers: YES
       service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
@@ -82256,8 +82256,8 @@
         '}'
       execution_timeout_secs: 7200
       caches {
-        name: "xcode_ios_13c100"
-        path: "xcode_ios_13c100.app"
+        name: "xcode_ios_14a5284g"
+        path: "xcode_ios_14a5284g.app"
       }
       build_numbers: YES
       service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
diff --git a/infra/config/lib/builders.star b/infra/config/lib/builders.star
index cc387dc..7dea943 100644
--- a/infra/config/lib/builders.star
+++ b/infra/config/lib/builders.star
@@ -188,9 +188,7 @@
     # Xcode14 beta 4 will be used to build Main iOS
     x14main = xcode_enum("14a5284g"),
     # A newer Xcode 14 version used on beta bots.
-    x14betabots = xcode_enum("14a5284g"),
-    # Xcode14 beta 5 used on beta bots.
-    x14beta5bots = xcode_enum("14a5294e"),
+    x14betabots = xcode_enum("14a5294e"),
     # in use by ios-webkit-tot
     x13wk = xcode_enum("13a1030dwk"),
 )
diff --git a/infra/config/subprojects/webrtc/webrtc.fyi.star b/infra/config/subprojects/webrtc/webrtc.fyi.star
index 2212402..146dadf 100644
--- a/infra/config/subprojects/webrtc/webrtc.fyi.star
+++ b/infra/config/subprojects/webrtc/webrtc.fyi.star
@@ -133,12 +133,12 @@
     name = "WebRTC Chromium FYI ios-device",
     goma_backend = goma.backend.RBE_PROD,
     os = os.MAC_ANY,
-    xcode = xcode.x13main,
+    xcode = xcode.x14main,
 )
 
 builder(
     name = "WebRTC Chromium FYI ios-simulator",
     goma_backend = goma.backend.RBE_PROD,
     os = os.MAC_ANY,
-    xcode = xcode.x13main,
+    xcode = xcode.x14main,
 )
diff --git a/ios/chrome/browser/ui/authentication/signin/forced_signin/forced_signin_coordinator.mm b/ios/chrome/browser/ui/authentication/signin/forced_signin/forced_signin_coordinator.mm
index a996495..77f90cb4 100644
--- a/ios/chrome/browser/ui/authentication/signin/forced_signin/forced_signin_coordinator.mm
+++ b/ios/chrome/browser/ui/authentication/signin/forced_signin/forced_signin_coordinator.mm
@@ -148,13 +148,13 @@
 
 // This is called before finishing the presentation of a screen.
 // Stops the child coordinator and prepares the next screen to present.
-- (void)willFinishPresenting {
+- (void)screenWillFinishPresenting {
   [self.childCoordinator stop];
   self.childCoordinator = nil;
   [self presentScreen:[self.screenProvider nextScreenType]];
 }
 
-- (void)skipAll {
+- (void)skipAllScreens {
   [self finishPresentingScreens];
 }
 
diff --git a/ios/chrome/browser/ui/authentication/signin_sync/signin_sync_coordinator.mm b/ios/chrome/browser/ui/authentication/signin_sync/signin_sync_coordinator.mm
index ac3c905..1c82fdb 100644
--- a/ios/chrome/browser/ui/authentication/signin_sync/signin_sync_coordinator.mm
+++ b/ios/chrome/browser/ui/authentication/signin_sync/signin_sync_coordinator.mm
@@ -171,7 +171,7 @@
     // performed with irregular states. We expect sync to be disabled when the
     // FRE is displayed in a regular situation (i.e., first launch after
     // install).
-    [self.delegate willFinishPresenting];
+    [self.delegate screenWillFinishPresenting];
     return;
   }
 
@@ -419,9 +419,9 @@
   }
 
   if (skipRemainingScreens) {
-    [self.delegate skipAll];
+    [self.delegate skipAllScreens];
   } else {
-    [self.delegate willFinishPresenting];
+    [self.delegate screenWillFinishPresenting];
   }
 }
 
diff --git a/ios/chrome/browser/ui/first_run/default_browser/default_browser_screen_coordinator.mm b/ios/chrome/browser/ui/first_run/default_browser/default_browser_screen_coordinator.mm
index 4ffe0a7..8bc21f5 100644
--- a/ios/chrome/browser/ui/first_run/default_browser/default_browser_screen_coordinator.mm
+++ b/ios/chrome/browser/ui/first_run/default_browser/default_browser_screen_coordinator.mm
@@ -72,7 +72,7 @@
                 openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]
                 options:{}
       completionHandler:nil];
-  [self.delegate willFinishPresenting];
+  [self.delegate screenWillFinishPresenting];
 }
 
 - (void)didTapSecondaryActionButton {
@@ -80,7 +80,7 @@
       "FirstRun.Stage",
       first_run::kDefaultBrowserScreenCompletionWithoutSettings);
   LogUserInteractionWithFirstRunPromo(NO);
-  [self.delegate willFinishPresenting];
+  [self.delegate screenWillFinishPresenting];
 }
 
 @end
diff --git a/ios/chrome/browser/ui/first_run/first_run_coordinator.mm b/ios/chrome/browser/ui/first_run/first_run_coordinator.mm
index 59d12d8..b885af9 100644
--- a/ios/chrome/browser/ui/first_run/first_run_coordinator.mm
+++ b/ios/chrome/browser/ui/first_run/first_run_coordinator.mm
@@ -90,7 +90,7 @@
 
 #pragma mark - FirstRunScreenDelegate
 
-- (void)willFinishPresenting {
+- (void)screenWillFinishPresenting {
   [self.childCoordinator stop];
   self.childCoordinator = nil;
   // Usually, finishing presenting the first FRE screen signifies that the user
@@ -106,7 +106,7 @@
   [self presentScreen:[self.screenProvider nextScreenType]];
 }
 
-- (void)skipAll {
+- (void)skipAllScreens {
   [self.childCoordinator stop];
   self.childCoordinator = nil;
   [self willFinishPresentingScreens];
diff --git a/ios/chrome/browser/ui/first_run/first_run_screen_delegate.h b/ios/chrome/browser/ui/first_run/first_run_screen_delegate.h
index 1ff5525f..17a97f3 100644
--- a/ios/chrome/browser/ui/first_run/first_run_screen_delegate.h
+++ b/ios/chrome/browser/ui/first_run/first_run_screen_delegate.h
@@ -9,10 +9,10 @@
 @protocol FirstRunScreenDelegate <NSObject>
 
 // Called when one screen finished presenting.
-- (void)willFinishPresenting;
+- (void)screenWillFinishPresenting;
 
 // Called when user want to skip all screens after.
-- (void)skipAll;
+- (void)skipAllScreens;
 
 @end
 
diff --git a/ios/chrome/browser/ui/first_run/legacy_signin/legacy_signin_screen_coordinator.mm b/ios/chrome/browser/ui/first_run/legacy_signin/legacy_signin_screen_coordinator.mm
index 7dc52f14..7d718ef 100644
--- a/ios/chrome/browser/ui/first_run/legacy_signin/legacy_signin_screen_coordinator.mm
+++ b/ios/chrome/browser/ui/first_run/legacy_signin/legacy_signin_screen_coordinator.mm
@@ -119,7 +119,7 @@
     // Don't show sign in screen if there is already an account signed in (for
     // example going through the FRE then killing the app and restarting the
     // FRE). Don't record any metric as the user didn't take any action.
-    [self.delegate willFinishPresenting];
+    [self.delegate screenWillFinishPresenting];
     return;
   }
 
@@ -306,9 +306,9 @@
                                 self.hadIdentitiesAtStartup);
   }
   if (skipRemainingScreens) {
-    [self.delegate skipAll];
+    [self.delegate skipAllScreens];
   } else {
-    [self.delegate willFinishPresenting];
+    [self.delegate screenWillFinishPresenting];
   }
 }
 
diff --git a/ios/chrome/browser/ui/first_run/signin/signin_screen_coordinator.mm b/ios/chrome/browser/ui/first_run/signin/signin_screen_coordinator.mm
index 22ff212..a5ac866 100644
--- a/ios/chrome/browser/ui/first_run/signin/signin_screen_coordinator.mm
+++ b/ios/chrome/browser/ui/first_run/signin/signin_screen_coordinator.mm
@@ -197,7 +197,7 @@
 // Calls the mediator and the delegate when the coordinator is finished.
 - (void)finishPresentingWithSignIn:(BOOL)signIn {
   [self.mediator finishPresentingWithSignIn:signIn];
-  [self.delegate willFinishPresenting];
+  [self.delegate screenWillFinishPresenting];
 }
 
 // Shows the UMA dialog so the user can manage metric reporting.
diff --git a/ios/chrome/browser/ui/first_run/sync/sync_screen_coordinator.mm b/ios/chrome/browser/ui/first_run/sync/sync_screen_coordinator.mm
index 765fff5..5314103 100644
--- a/ios/chrome/browser/ui/first_run/sync/sync_screen_coordinator.mm
+++ b/ios/chrome/browser/ui/first_run/sync/sync_screen_coordinator.mm
@@ -106,7 +106,7 @@
   if (!authenticationService->GetPrimaryIdentity(
           signin::ConsentLevel::kSignin)) {
     // Don't show sync screen if no logged-in user account.
-    [self.delegate willFinishPresenting];
+    [self.delegate screenWillFinishPresenting];
     return;
   }
 
@@ -120,7 +120,7 @@
           syncer::SyncService::DISABLE_REASON_ENTERPRISE_POLICY) ||
       syncSetupService->IsFirstSetupComplete();
   if (shouldSkipSyncScreen) {
-    [self.delegate willFinishPresenting];
+    [self.delegate screenWillFinishPresenting];
     return;
   }
 
@@ -203,7 +203,7 @@
   // user entered it before canceling the sync opt-in flow, and also to set
   // sync as requested.
   syncService->StopAndClear();
-  [self.delegate willFinishPresenting];
+  [self.delegate screenWillFinishPresenting];
 }
 
 - (void)didTapURLInDisclaimer:(NSURL*)URL {
@@ -234,12 +234,12 @@
       base::UmaHistogramEnumeration("FirstRun.Stage",
                                     first_run::kSyncScreenCompletionWithSync);
     }
-    [self.delegate willFinishPresenting];
+    [self.delegate screenWillFinishPresenting];
   }
 }
 
 - (void)userRemoved {
-  [self.delegate willFinishPresenting];
+  [self.delegate screenWillFinishPresenting];
 }
 
 #pragma mark - PolicyWatcherBrowserAgentObserving
@@ -285,7 +285,7 @@
 - (void)dismissSignedOutModalAndSkipScreens:(BOOL)skipScreens {
   [self.enterprisePromptCoordinator stop];
   self.enterprisePromptCoordinator = nil;
-  [self.delegate skipAll];
+  [self.delegate skipAllScreens];
 }
 
 // Starts syncing or opens `advancedSettings`.
diff --git a/ios/chrome/browser/ui/first_run/sync/sync_screen_coordinator_unittest.mm b/ios/chrome/browser/ui/first_run/sync/sync_screen_coordinator_unittest.mm
index 4ec318f..e79074cd 100644
--- a/ios/chrome/browser/ui/first_run/sync/sync_screen_coordinator_unittest.mm
+++ b/ios/chrome/browser/ui/first_run/sync/sync_screen_coordinator_unittest.mm
@@ -116,7 +116,7 @@
 // Tests that calling the delegate immidiately to stop the coordinator when
 // there's no user identity.
 TEST_F(SyncScreenCoordinatorTest, TestStartWithoutIdentity) {
-  OCMExpect([delegate_ willFinishPresenting]);
+  OCMExpect([delegate_ screenWillFinishPresenting]);
   [coordinator_ start];
 
   EXPECT_OCMOCK_VERIFY(delegate_);
@@ -134,7 +134,7 @@
 
   auth_service_->SignIn(identity, nil);
 
-  OCMExpect([delegate_ willFinishPresenting]);
+  OCMExpect([delegate_ screenWillFinishPresenting]);
   [coordinator_ start];
 
   EXPECT_OCMOCK_VERIFY(delegate_);
@@ -153,7 +153,7 @@
 
   auth_service_->SignIn(identity, nil);
 
-  OCMExpect([delegate_ willFinishPresenting]);
+  OCMExpect([delegate_ screenWillFinishPresenting]);
   [coordinator_ start];
 
   EXPECT_OCMOCK_VERIFY(delegate_);
diff --git a/ios/chrome/browser/ui/first_run/welcome/welcome_screen_coordinator.mm b/ios/chrome/browser/ui/first_run/welcome/welcome_screen_coordinator.mm
index 4a26de1..6988ae7 100644
--- a/ios/chrome/browser/ui/first_run/welcome/welcome_screen_coordinator.mm
+++ b/ios/chrome/browser/ui/first_run/welcome/welcome_screen_coordinator.mm
@@ -114,7 +114,7 @@
     base::RecordAction(base::UserMetricsAction("MobileFreUMALinkTapped"));
   }
 
-  [self.delegate willFinishPresenting];
+  [self.delegate screenWillFinishPresenting];
 }
 
 #pragma mark - TOSCommands
diff --git a/media/gpu/BUILD.gn b/media/gpu/BUILD.gn
index 87b7f16f..622adc0 100644
--- a/media/gpu/BUILD.gn
+++ b/media/gpu/BUILD.gn
@@ -338,10 +338,10 @@
 
   if (is_win || use_vaapi) {
     sources += [
+      "video_rate_control.cc",
+      "video_rate_control.h",
       "vp9_svc_layers.cc",
       "vp9_svc_layers.h",
-      "vpx_rate_control.cc",
-      "vpx_rate_control.h",
     ]
     configs += [ "//third_party/libvpx:libvpx_config" ]
     deps += [ "//third_party/libvpx:libvpxrc" ]
diff --git a/media/gpu/vaapi/vp8_vaapi_video_encoder_delegate.h b/media/gpu/vaapi/vp8_vaapi_video_encoder_delegate.h
index a67bc6c..732eebe 100644
--- a/media/gpu/vaapi/vp8_vaapi_video_encoder_delegate.h
+++ b/media/gpu/vaapi/vp8_vaapi_video_encoder_delegate.h
@@ -9,9 +9,9 @@
 
 #include "media/base/video_bitrate_allocation.h"
 #include "media/gpu/vaapi/vaapi_video_encoder_delegate.h"
+#include "media/gpu/video_rate_control.h"
 #include "media/gpu/vp8_picture.h"
 #include "media/gpu/vp8_reference_frame_vector.h"
-#include "media/gpu/vpx_rate_control.h"
 #include "media/parsers/vp8_parser.h"
 
 namespace libvpx {
@@ -95,9 +95,9 @@
 
   Vp8ReferenceFrameVector reference_frames_;
 
-  using VP8RateControl = VPXRateControl<libvpx::VP8RateControlRtcConfig,
-                                        libvpx::VP8RateControlRTC,
-                                        libvpx::VP8FrameParamsQpRTC>;
+  using VP8RateControl = VideoRateControl<libvpx::VP8RateControlRtcConfig,
+                                          libvpx::VP8RateControlRTC,
+                                          libvpx::VP8FrameParamsQpRTC>;
   std::unique_ptr<VP8RateControl> rate_ctrl_;
 };
 
diff --git a/media/gpu/vaapi/vp9_vaapi_video_encoder_delegate.cc b/media/gpu/vaapi/vp9_vaapi_video_encoder_delegate.cc
index ad8fe8ae..a58e659 100644
--- a/media/gpu/vaapi/vp9_vaapi_video_encoder_delegate.cc
+++ b/media/gpu/vaapi/vp9_vaapi_video_encoder_delegate.cc
@@ -17,8 +17,8 @@
 #include "media/gpu/macros.h"
 #include "media/gpu/vaapi/vaapi_common.h"
 #include "media/gpu/vaapi/vaapi_wrapper.h"
+#include "media/gpu/video_rate_control.h"
 #include "media/gpu/vp9_svc_layers.h"
-#include "media/gpu/vpx_rate_control.h"
 #include "third_party/libvpx/source/libvpx/vp9/ratectrl_rtc.h"
 
 namespace media {
diff --git a/media/gpu/vaapi/vp9_vaapi_video_encoder_delegate.h b/media/gpu/vaapi/vp9_vaapi_video_encoder_delegate.h
index 3bdac33c..d135b0b 100644
--- a/media/gpu/vaapi/vp9_vaapi_video_encoder_delegate.h
+++ b/media/gpu/vaapi/vp9_vaapi_video_encoder_delegate.h
@@ -12,9 +12,9 @@
 #include "media/base/video_bitrate_allocation.h"
 #include "media/filters/vp9_parser.h"
 #include "media/gpu/vaapi/vaapi_video_encoder_delegate.h"
+#include "media/gpu/video_rate_control.h"
 #include "media/gpu/vp9_picture.h"
 #include "media/gpu/vp9_reference_frame_vector.h"
-#include "media/gpu/vpx_rate_control.h"
 
 namespace libvpx {
 struct VP9FrameParamsQpRTC;
@@ -68,9 +68,9 @@
   friend class VP9VaapiVideoEncoderDelegateTest;
   friend class VaapiVideoEncodeAcceleratorTest;
 
-  using VP9RateControl = VPXRateControl<libvpx::VP9RateControlRtcConfig,
-                                        libvpx::VP9RateControlRTC,
-                                        libvpx::VP9FrameParamsQpRTC>;
+  using VP9RateControl = VideoRateControl<libvpx::VP9RateControlRtcConfig,
+                                          libvpx::VP9RateControlRTC,
+                                          libvpx::VP9FrameParamsQpRTC>;
   void set_rate_ctrl_for_testing(std::unique_ptr<VP9RateControl> rate_ctrl);
 
   bool ApplyPendingUpdateRates();
diff --git a/media/gpu/vaapi/vp9_vaapi_video_encoder_delegate_unittest.cc b/media/gpu/vaapi/vp9_vaapi_video_encoder_delegate_unittest.cc
index 423abd5b..d966194 100644
--- a/media/gpu/vaapi/vp9_vaapi_video_encoder_delegate_unittest.cc
+++ b/media/gpu/vaapi/vp9_vaapi_video_encoder_delegate_unittest.cc
@@ -20,8 +20,8 @@
 #include "media/gpu/gpu_video_encode_accelerator_helpers.h"
 #include "media/gpu/vaapi/vaapi_common.h"
 #include "media/gpu/vaapi/vaapi_wrapper.h"
+#include "media/gpu/video_rate_control.h"
 #include "media/gpu/vp9_svc_layers.h"
-#include "media/gpu/vpx_rate_control.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -240,9 +240,9 @@
 };
 
 class MockVP9RateControl
-    : public VPXRateControl<libvpx::VP9RateControlRtcConfig,
-                            libvpx::VP9RateControlRTC,
-                            libvpx::VP9FrameParamsQpRTC> {
+    : public VideoRateControl<libvpx::VP9RateControlRtcConfig,
+                              libvpx::VP9RateControlRTC,
+                              libvpx::VP9FrameParamsQpRTC> {
  public:
   MockVP9RateControl() = default;
   ~MockVP9RateControl() override = default;
diff --git a/media/gpu/vpx_rate_control.cc b/media/gpu/video_rate_control.cc
similarity index 66%
rename from media/gpu/vpx_rate_control.cc
rename to media/gpu/video_rate_control.cc
index 931eba3..90ce362 100644
--- a/media/gpu/vpx_rate_control.cc
+++ b/media/gpu/video_rate_control.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "media/gpu/vpx_rate_control.h"
+#include "media/gpu/video_rate_control.h"
 
 #include "third_party/libvpx/source/libvpx/vp9/ratectrl_rtc.h"
 
@@ -11,10 +11,9 @@
 // Template method specialization for VP9.
 // TODO(mcasas): Remove when VP8 also has a GetLoopfilterLevel() method.
 template <>
-int VPXRateControl<libvpx::VP9RateControlRtcConfig,
-                   libvpx::VP9RateControlRTC,
-                   libvpx::VP9RateControlRtcConfig>::GetLoopfilterLevel()
-    const {
+int VideoRateControl<libvpx::VP9RateControlRtcConfig,
+                     libvpx::VP9RateControlRTC,
+                     libvpx::VP9FrameParamsQpRTC>::GetLoopfilterLevel() const {
   return impl_->GetLoopfilterLevel();
 }
 
diff --git a/media/gpu/vpx_rate_control.h b/media/gpu/video_rate_control.h
similarity index 65%
rename from media/gpu/vpx_rate_control.h
rename to media/gpu/video_rate_control.h
index 61831d9..c5666505 100644
--- a/media/gpu/vpx_rate_control.h
+++ b/media/gpu/video_rate_control.h
@@ -2,35 +2,35 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef MEDIA_GPU_VPX_RATE_CONTROL_H_
-#define MEDIA_GPU_VPX_RATE_CONTROL_H_
+#ifndef MEDIA_GPU_VIDEO_RATE_CONTROL_H_
+#define MEDIA_GPU_VIDEO_RATE_CONTROL_H_
 
 #include <memory>
 
 #include "base/logging.h"
 
 namespace media {
-// VPXRateControl is an interface to compute proper quantization
+// VideoRateControl is an interface to compute proper quantization
 // parameter and loop filter level for vp8 and vp9.
 // T is a libvpx::VP(8|9)RateControlRtcConfig
 // S is a libvpx::VP(8|9)RateControlRTC
-// U is a libvpx::VP(8|9)RateControlRtcConfig
+// U is a libvpx::VP(8|9)FrameParamsQpRTC
 template <typename T, typename S, typename U>
-class VPXRateControl {
+class VideoRateControl {
  public:
-  // Creates VPXRateControl using libvpx implementation.
-  static std::unique_ptr<VPXRateControl> Create(const T& config) {
+  // Creates VideoRateControl using libvpx implementation.
+  static std::unique_ptr<VideoRateControl> Create(const T& config) {
     auto impl = S::Create(config);
     if (!impl) {
-      DLOG(ERROR) << "Failed creating libvpx's VPxRateControlRTC";
+      DLOG(ERROR) << "Failed creating video RateControlRTC";
       return nullptr;
     }
-    return std::make_unique<VPXRateControl>(std::move(impl));
+    return std::make_unique<VideoRateControl>(std::move(impl));
   }
 
-  VPXRateControl() = default;
-  explicit VPXRateControl(std::unique_ptr<S> impl) : impl_(std::move(impl)) {}
-  virtual ~VPXRateControl() = default;
+  VideoRateControl() = default;
+  explicit VideoRateControl(std::unique_ptr<S> impl) : impl_(std::move(impl)) {}
+  virtual ~VideoRateControl() = default;
 
   virtual void UpdateRateControl(const T& rate_control_config) {
     impl_->UpdateRateControl(rate_control_config);
@@ -52,4 +52,4 @@
 };
 
 }  // namespace media
-#endif  // MEDIA_GPU_VPX_RATE_CONTROL_H_
+#endif  // MEDIA_GPU_VIDEO_RATE_CONTROL_H_
diff --git a/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc b/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc
index a28e44cc..3b424904 100644
--- a/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc
+++ b/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc
@@ -66,6 +66,8 @@
 const size_t kMaxFrameRateDenominator = 1;
 const size_t kMaxResolutionWidth = 1920;
 const size_t kMaxResolutionHeight = 1088;
+const size_t kMinResolutionWidth = 32;
+const size_t kMinResolutionHeight = 32;
 const size_t kNumInputBuffers = 3;
 // Media Foundation uses 100 nanosecond units for time, see
 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms697282(v=vs.85).aspx.
@@ -388,6 +390,7 @@
   profile.max_framerate_denominator = kMaxFrameRateDenominator;
   profile.rate_control_modes = kSupportedProfileModes;
   profile.max_resolution = gfx::Size(kMaxResolutionWidth, kMaxResolutionHeight);
+  profile.min_resolution = gfx::Size(kMinResolutionWidth, kMinResolutionHeight);
   if (svc_supported) {
     profile.scalability_modes.push_back(SVCScalabilityMode::kL1T2);
     profile.scalability_modes.push_back(SVCScalabilityMode::kL1T3);
diff --git a/media/video/openh264_video_encoder.cc b/media/video/openh264_video_encoder.cc
index 86f37258..6f55b7c 100644
--- a/media/video/openh264_video_encoder.cc
+++ b/media/video/openh264_video_encoder.cc
@@ -133,6 +133,12 @@
     return;
   }
 
+  if (options.frame_size.height() < 16 || options.frame_size.width() < 16) {
+    std::move(done_cb).Run(
+        EncoderStatus(EncoderStatus::Codes::kEncoderInitializationError,
+                      "Unsupported frame size which is less than 16"));
+    return;
+  }
   SetUpOpenH264Params(options, &params);
 
   if (int err = codec->InitializeExt(&params)) {
diff --git a/mojo/public/cpp/bindings/array_data_view.h b/mojo/public/cpp/bindings/array_data_view.h
index 0c5b79a..adfab0a 100644
--- a/mojo/public/cpp/bindings/array_data_view.h
+++ b/mojo/public/cpp/bindings/array_data_view.h
@@ -7,7 +7,6 @@
 
 #include <type_traits>
 
-#include "base/memory/raw_ptr.h"
 #include "base/memory/raw_ptr_exclusion.h"
 #include "mojo/public/cpp/bindings/lib/array_internal.h"
 #include "mojo/public/cpp/bindings/lib/bindings_internal.h"
@@ -40,10 +39,10 @@
  protected:
   // `data_` is not a raw_ptr<...> for performance reasons (based on analysis of
   // sampling profiler data).
-  RAW_PTR_EXCLUSION raw_ptr<Data_> data_;
+  RAW_PTR_EXCLUSION Data_* data_;
   // `message_` is not a raw_ptr<...> for performance reasons (based on analysis
   // of sampling profiler data).
-  RAW_PTR_EXCLUSION raw_ptr<Message> message_;
+  RAW_PTR_EXCLUSION Message* message_;
 };
 
 template <typename T>
@@ -62,10 +61,10 @@
  protected:
   // `data_` is not a raw_ptr<...> for performance reasons (based on analysis of
   // sampling profiler data).
-  RAW_PTR_EXCLUSION raw_ptr<Data_> data_;
+  RAW_PTR_EXCLUSION Data_* data_;
   // `message_` is not a raw_ptr<...> for performance reasons (based on analysis
   // of sampling profiler data).
-  RAW_PTR_EXCLUSION raw_ptr<Message> message_;
+  RAW_PTR_EXCLUSION Message* message_;
 };
 
 template <typename T>
@@ -94,10 +93,10 @@
  protected:
   // `data_` is not a raw_ptr<...> for performance reasons (based on analysis of
   // sampling profiler data).
-  RAW_PTR_EXCLUSION raw_ptr<Data_> data_;
+  RAW_PTR_EXCLUSION Data_* data_;
   // `message_` is not a raw_ptr<...> for performance reasons (based on analysis
   // of sampling profiler data).
-  RAW_PTR_EXCLUSION raw_ptr<Message> message_;
+  RAW_PTR_EXCLUSION Message* message_;
 };
 
 template <typename T>
@@ -126,10 +125,10 @@
  protected:
   // `data_` is not a raw_ptr<...> for performance reasons (based on analysis of
   // sampling profiler data).
-  RAW_PTR_EXCLUSION raw_ptr<Data_> data_;
+  RAW_PTR_EXCLUSION Data_* data_;
   // `message_` is not a raw_ptr<...> for performance reasons (based on analysis
   // of sampling profiler data).
-  RAW_PTR_EXCLUSION raw_ptr<Message> message_;
+  RAW_PTR_EXCLUSION Message* message_;
 };
 
 template <typename T>
@@ -153,10 +152,10 @@
  protected:
   // `data_` is not a raw_ptr<...> for performance reasons (based on analysis of
   // sampling profiler data).
-  RAW_PTR_EXCLUSION raw_ptr<Data_> data_;
+  RAW_PTR_EXCLUSION Data_* data_;
   // `message_` is not a raw_ptr<...> for performance reasons (based on analysis
   // of sampling profiler data).
-  RAW_PTR_EXCLUSION raw_ptr<Message> message_;
+  RAW_PTR_EXCLUSION Message* message_;
 };
 
 template <typename T>
@@ -185,10 +184,10 @@
  protected:
   // `data_` is not a raw_ptr<...> for performance reasons (based on analysis of
   // sampling profiler data).
-  RAW_PTR_EXCLUSION raw_ptr<Data_> data_;
+  RAW_PTR_EXCLUSION Data_* data_;
   // `message_` is not a raw_ptr<...> for performance reasons (based on analysis
   // of sampling profiler data).
-  RAW_PTR_EXCLUSION raw_ptr<Message> message_;
+  RAW_PTR_EXCLUSION Message* message_;
 };
 
 template <typename T>
@@ -214,10 +213,10 @@
  protected:
   // `data_` is not a raw_ptr<...> for performance reasons (based on analysis of
   // sampling profiler data).
-  RAW_PTR_EXCLUSION raw_ptr<Data_> data_;
+  RAW_PTR_EXCLUSION Data_* data_;
   // `message_` is not a raw_ptr<...> for performance reasons (based on analysis
   // of sampling profiler data).
-  RAW_PTR_EXCLUSION raw_ptr<Message> message_;
+  RAW_PTR_EXCLUSION Message* message_;
 };
 
 }  // namespace internal
diff --git a/mojo/public/cpp/bindings/lib/send_message_helper.cc b/mojo/public/cpp/bindings/lib/send_message_helper.cc
index 13e1be4..30f00026 100644
--- a/mojo/public/cpp/bindings/lib/send_message_helper.cc
+++ b/mojo/public/cpp/bindings/lib/send_message_helper.cc
@@ -12,7 +12,7 @@
 namespace mojo {
 namespace internal {
 
-void SendMessage(MessageReceiver& receiver, Message& message) {
+void SendMojoMessage(MessageReceiver& receiver, Message& message) {
   uint64_t flow_id = message.GetTraceId();
   bool is_sync_non_response = message.has_flag(Message::kFlagIsSync) &&
                               !message.has_flag(Message::kFlagIsResponse);
@@ -29,9 +29,9 @@
   }
 }
 
-void SendMessage(MessageReceiverWithResponder& receiver,
-                 Message& message,
-                 std::unique_ptr<MessageReceiver> responder) {
+void SendMojoMessage(MessageReceiverWithResponder& receiver,
+                     Message& message,
+                     std::unique_ptr<MessageReceiver> responder) {
   uint64_t flow_id = message.GetTraceId();
   bool is_sync_non_response = message.has_flag(Message::kFlagIsSync) &&
                               !message.has_flag(Message::kFlagIsResponse);
diff --git a/mojo/public/cpp/bindings/lib/send_message_helper.h b/mojo/public/cpp/bindings/lib/send_message_helper.h
index 1a10da9..d92f083523 100644
--- a/mojo/public/cpp/bindings/lib/send_message_helper.h
+++ b/mojo/public/cpp/bindings/lib/send_message_helper.h
@@ -18,12 +18,12 @@
 // trace events can be performed without affecting the binary size of the
 // generated bindings.
 COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
-void SendMessage(MessageReceiverWithResponder& receiver,
-                 Message& message,
-                 std::unique_ptr<MessageReceiver> responder);
+void SendMojoMessage(MessageReceiverWithResponder& receiver,
+                     Message& message,
+                     std::unique_ptr<MessageReceiver> responder);
 
 COMPONENT_EXPORT(MOJO_CPP_BINDINGS_BASE)
-void SendMessage(MessageReceiver& receiver, Message& message);
+void SendMojoMessage(MessageReceiver& receiver, Message& message);
 
 }  // namespace internal
 }  // namespace mojo
diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl
index d538eaa..9edd2aa 100644
--- a/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl
@@ -235,7 +235,7 @@
 {%-     for param in method.response_parameters -%}
           , out_param_{{param.name}}
 {%-     endfor %}));
-  ::mojo::internal::SendMessage(*receiver_, message, std::move(responder));
+  ::mojo::internal::SendMojoMessage(*receiver_, message, std::move(responder));
 #if BUILDFLAG(MOJO_TRACE_ENABLED)
   {{interface_macros.trace_event(prefix="out_param_",
                                  method_parameters=method.response_parameters,
@@ -289,11 +289,11 @@
   std::unique_ptr<mojo::MessageReceiver> responder(
       new {{class_name}}_{{method.name}}_ForwardToCallback(
           std::move(callback)));
-  ::mojo::internal::SendMessage(*receiver_, message, std::move(responder));
+  ::mojo::internal::SendMojoMessage(*receiver_, message, std::move(responder));
 {%- else %}
   // This return value may be ignored as false implies the Connector has
   // encountered an error, which will be visible through other means.
-  ::mojo::internal::SendMessage(*receiver_, message);
+  ::mojo::internal::SendMojoMessage(*receiver_, message);
 {%- endif %}
 }
 {%- endfor %}
@@ -424,8 +424,8 @@
 
   message.set_request_id(request_id_);
   message.set_trace_nonce(trace_nonce_);
-  ::mojo::internal::SendMessage(*responder_, message);
-  // SendMessage fails silently if the responder connection is closed,
+  ::mojo::internal::SendMojoMessage(*responder_, message);
+  // SendMojoMessage() fails silently if the responder connection is closed,
   // or if the message is malformed.
   //
   // TODO(darin): If Accept() returns false due to a malformed message, that
diff --git a/mojo/public/tools/mojom/mojom/generate/translate.py b/mojo/public/tools/mojom/mojom/generate/translate.py
index 727a3508..0be7732 100644
--- a/mojo/public/tools/mojom/mojom/generate/translate.py
+++ b/mojo/public/tools/mojom/mojom/generate/translate.py
@@ -576,7 +576,11 @@
     return kind
 
   if spec.startswith('?'):
-    kind = _Kind(kinds, spec[1:], scope).MakeNullableKind()
+    kind = _Kind(kinds, spec[1:], scope)
+    if not mojom.IsReferenceKind(kind):
+      raise Exception('Unknown spec: %s. Check for missing imports.' % spec)
+
+    kind = kind.MakeNullableKind()
   elif spec.startswith('a:'):
     kind = mojom.Array(_Kind(kinds, spec[2:], scope))
   elif spec.startswith('asso:'):
diff --git a/net/cert/cert_verify_proc_builtin_unittest.cc b/net/cert/cert_verify_proc_builtin_unittest.cc
index f554d0ac..f238eed 100644
--- a/net/cert/cert_verify_proc_builtin_unittest.cc
+++ b/net/cert/cert_verify_proc_builtin_unittest.cc
@@ -223,12 +223,13 @@
   // Creates a CRL issued and signed by |crl_issuer|, marking |revoked_serials|
   // as revoked, and registers it to be served by the test server.
   // Returns the full URL to retrieve the CRL from the test server.
-  GURL CreateAndServeCrl(EmbeddedTestServer* test_server,
-                         CertBuilder* crl_issuer,
-                         const std::vector<uint64_t>& revoked_serials,
-                         DigestAlgorithm digest = DigestAlgorithm::Sha256) {
+  GURL CreateAndServeCrl(
+      EmbeddedTestServer* test_server,
+      CertBuilder* crl_issuer,
+      const std::vector<uint64_t>& revoked_serials,
+      absl::optional<SignatureAlgorithm> signature_algorithm = absl::nullopt) {
     std::string crl = BuildCrl(crl_issuer->GetSubject(), crl_issuer->GetKey(),
-                               revoked_serials, digest);
+                               revoked_serials, signature_algorithm);
     std::string crl_path = MakeRandomPath(".crl");
     test_server->RegisterRequestHandler(
         base::BindRepeating(&test_server::HandlePrefixedRequest, crl_path,
diff --git a/net/cert/cert_verify_proc_unittest.cc b/net/cert/cert_verify_proc_unittest.cc
index b5c522cb..7f22a56 100644
--- a/net/cert/cert_verify_proc_unittest.cc
+++ b/net/cert/cert_verify_proc_unittest.cc
@@ -629,11 +629,19 @@
   ASSERT_EQ(3U, orig_certs.size());
 
   for (bool trust_the_intermediate : {false, true}) {
+    SCOPED_TRACE(trust_the_intermediate);
+
     // Need to build unique certs for each try otherwise caching can break
     // things.
     CertBuilder root(orig_certs[2]->cert_buffer(), nullptr);
+    root.SetSignatureAlgorithm(SignatureAlgorithm::kEcdsaSha256);
+    root.GenerateECKey();
     CertBuilder intermediate(orig_certs[1]->cert_buffer(), &root);
+    intermediate.SetSignatureAlgorithm(SignatureAlgorithm::kEcdsaSha256);
+    intermediate.GenerateECKey();
     CertBuilder leaf(orig_certs[0]->cert_buffer(), &intermediate);
+    leaf.SetSignatureAlgorithm(SignatureAlgorithm::kEcdsaSha256);
+    leaf.GenerateECKey();
 
     // The policy that "explicit-policy-chain.pem" target certificate asserts.
     static const char kEVTestCertPolicy[] = "1.2.3.4";
@@ -3119,11 +3127,12 @@
   // Creates a CRL issued and signed by |crl_issuer|, marking |revoked_serials|
   // as revoked, and registers it to be served by the test server.
   // Returns the full URL to retrieve the CRL from the test server.
-  GURL CreateAndServeCrl(CertBuilder* crl_issuer,
-                         const std::vector<uint64_t>& revoked_serials,
-                         DigestAlgorithm digest = DigestAlgorithm::Sha256) {
+  GURL CreateAndServeCrl(
+      CertBuilder* crl_issuer,
+      const std::vector<uint64_t>& revoked_serials,
+      absl::optional<SignatureAlgorithm> signature_algorithm = absl::nullopt) {
     std::string crl = BuildCrl(crl_issuer->GetSubject(), crl_issuer->GetKey(),
-                               revoked_serials, digest);
+                               revoked_serials, signature_algorithm);
     std::string crl_path = MakeRandomPath(".crl");
     return RegisterSimpleTestServerHandler(crl_path, HTTP_OK,
                                            "application/pkix-crl", crl);
@@ -3433,8 +3442,13 @@
 
   // Build slightly modified variants of |orig_certs|.
   CertBuilder root(orig_certs[2]->cert_buffer(), nullptr);
+  root.SetSignatureAlgorithm(SignatureAlgorithm::kEcdsaSha256);
+  root.GenerateECKey();
   CertBuilder intermediate(orig_certs[1]->cert_buffer(), &root);
+  intermediate.GenerateECKey();
   CertBuilder leaf(orig_certs[0]->cert_buffer(), &intermediate);
+  leaf.SetSignatureAlgorithm(SignatureAlgorithm::kEcdsaSha256);
+  leaf.GenerateECKey();
 
   // Make the leaf certificate have an AIA (CA Issuers) that points to the
   // embedded test server. This uses a random URL for predictable behavior in
@@ -3448,11 +3462,11 @@
   // that is SHA1 signed. Note that the subjectKeyIdentifier for `intermediate`
   // is intentionally not changed, so that path building will consider both
   // certificate paths.
-  intermediate.SetSignatureAlgorithmRsaPkca1(DigestAlgorithm::Sha256);
+  intermediate.SetSignatureAlgorithm(SignatureAlgorithm::kEcdsaSha256);
   intermediate.SetRandomSerialNumber();
   auto intermediate_sha256 = intermediate.DupCertBuffer();
 
-  intermediate.SetSignatureAlgorithmRsaPkca1(DigestAlgorithm::Sha1);
+  intermediate.SetSignatureAlgorithm(SignatureAlgorithm::kEcdsaSha1);
   intermediate.SetRandomSerialNumber();
   auto intermediate_sha1 = intermediate.DupCertBuffer();
 
@@ -4003,9 +4017,10 @@
   intermediate->SetCrlDistributionPointUrl(CreateAndServeCrl(root.get(), {}));
 
   // Leaf is revoked by intermediate issued CRL which is signed with
-  // sha1WithRSAEncryption.
-  leaf->SetCrlDistributionPointUrl(CreateAndServeCrl(
-      intermediate.get(), {leaf->GetSerialNumber()}, DigestAlgorithm::Sha1));
+  // ecdsaWithSha256.
+  leaf->SetCrlDistributionPointUrl(
+      CreateAndServeCrl(intermediate.get(), {leaf->GetSerialNumber()},
+                        SignatureAlgorithm::kEcdsaSha1));
 
   // Trust the root and build a chain to verify that includes the intermediate.
   ScopedTestRoot scoped_root(root->GetX509Certificate().get());
@@ -4045,11 +4060,17 @@
 
   // Root-issued CRL which does not revoke intermediate.
   intermediate->SetCrlDistributionPointUrl(CreateAndServeCrl(root.get(), {}));
+  // This test wants to check handling of MD5 CRLs, but ecdsa-with-md5
+  // signatureAlgorithm does not exist. Use an RSA private key for intermediate
+  // so that the CRL will be signed with the md5WithRSAEncryption algorithm.
+  intermediate->GenerateRSAKey();
+  leaf->SetSignatureAlgorithm(SignatureAlgorithm::kRsaPkcs1Sha256);
 
   // Leaf is revoked by intermediate issued CRL which is signed with
   // md5WithRSAEncryption.
-  leaf->SetCrlDistributionPointUrl(CreateAndServeCrl(
-      intermediate.get(), {leaf->GetSerialNumber()}, DigestAlgorithm::Md5));
+  leaf->SetCrlDistributionPointUrl(
+      CreateAndServeCrl(intermediate.get(), {leaf->GetSerialNumber()},
+                        SignatureAlgorithm::kRsaPkcs1Md5));
 
   // Trust the root and build a chain to verify that includes the intermediate.
   ScopedTestRoot scoped_root(root->GetX509Certificate().get());
diff --git a/net/cert/internal/revocation_checker_unittest.cc b/net/cert/internal/revocation_checker_unittest.cc
index 50c1f2f..1ad96505 100644
--- a/net/cert/internal/revocation_checker_unittest.cc
+++ b/net/cert/internal/revocation_checker_unittest.cc
@@ -120,7 +120,7 @@
 
   std::string crl_data_as_string_for_some_reason =
       BuildCrl(root->GetSubject(), root->GetKey(),
-               /*revoked_serials=*/{}, DigestAlgorithm::Sha256);
+               /*revoked_serials=*/{});
   std::vector<uint8_t> crl_data(crl_data_as_string_for_some_reason.begin(),
                                 crl_data_as_string_for_some_reason.end());
 
@@ -193,9 +193,9 @@
   policy.networking_allowed = true;
   policy.crl_allowed = true;
 
-  std::string crl_data_as_string_for_some_reason = BuildCrl(
-      root->GetSubject(), root->GetKey(),
-      /*revoked_serials=*/{leaf->GetSerialNumber()}, DigestAlgorithm::Sha256);
+  std::string crl_data_as_string_for_some_reason =
+      BuildCrl(root->GetSubject(), root->GetKey(),
+               /*revoked_serials=*/{leaf->GetSerialNumber()});
   std::vector<uint8_t> crl_data(crl_data_as_string_for_some_reason.begin(),
                                 crl_data_as_string_for_some_reason.end());
 
@@ -390,7 +390,7 @@
 
   std::string crl_data_as_string_for_some_reason =
       BuildCrl(root->GetSubject(), root->GetKey(),
-               /*revoked_serials=*/{}, DigestAlgorithm::Sha256);
+               /*revoked_serials=*/{});
   std::vector<uint8_t> crl_data(crl_data_as_string_for_some_reason.begin(),
                                 crl_data_as_string_for_some_reason.end());
 
@@ -476,7 +476,7 @@
 
   std::string crl_data_as_string_for_some_reason =
       BuildCrl(root->GetSubject(), root->GetKey(),
-               /*revoked_serials=*/{}, DigestAlgorithm::Sha256);
+               /*revoked_serials=*/{});
   std::vector<uint8_t> crl_data(crl_data_as_string_for_some_reason.begin(),
                                 crl_data_as_string_for_some_reason.end());
 
@@ -546,7 +546,7 @@
 
   std::string crl_data_as_string_for_some_reason =
       BuildCrl(root->GetSubject(), root->GetKey(),
-               /*revoked_serials=*/{}, DigestAlgorithm::Sha256);
+               /*revoked_serials=*/{});
   std::vector<uint8_t> crl_data(crl_data_as_string_for_some_reason.begin(),
                                 crl_data_as_string_for_some_reason.end());
 
@@ -648,7 +648,7 @@
 
   std::string crl_data_as_string_for_some_reason =
       BuildCrl(root->GetSubject(), root->GetKey(),
-               /*revoked_serials=*/{}, DigestAlgorithm::Sha256);
+               /*revoked_serials=*/{});
   std::vector<uint8_t> crl_data(crl_data_as_string_for_some_reason.begin(),
                                 crl_data_as_string_for_some_reason.end());
 
diff --git a/net/dns/dns_config_service_linux.cc b/net/dns/dns_config_service_linux.cc
index df795af..b9d65f6a 100644
--- a/net/dns/dns_config_service_linux.cc
+++ b/net/dns/dns_config_service_linux.cc
@@ -20,11 +20,13 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/check.h"
+#include "base/containers/contains.h"
 #include "base/files/file_path.h"
 #include "base/files/file_path_watcher.h"
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/memory/raw_ptr.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/sequence_checker.h"
 #include "base/threading/scoped_blocking_call.h"
@@ -204,11 +206,12 @@
 void RecordIncompatibleNsswitchReason(
     IncompatibleNsswitchReason reason,
     absl::optional<NsswitchReader::Service> service_token) {
-  UMA_HISTOGRAM_ENUMERATION("Net.DNS.DnsConfig.Nsswitch.IncompatibleReason",
-                            reason);
+  base::UmaHistogramEnumeration("Net.DNS.DnsConfig.Nsswitch.IncompatibleReason",
+                                reason);
   if (service_token) {
-    UMA_HISTOGRAM_ENUMERATION("Net.DNS.DnsConfig.Nsswitch.IncompatibleService",
-                              service_token.value());
+    base::UmaHistogramEnumeration(
+        "Net.DNS.DnsConfig.Nsswitch.IncompatibleService",
+        service_token.value());
   }
 }
 
@@ -371,12 +374,12 @@
 
  private:
   void OnResolvFilePathWatcherChange(const base::FilePath& path, bool error) {
-    UMA_HISTOGRAM_BOOLEAN("Net.DNS.DnsConfig.Resolv.FileChange", true);
+    base::UmaHistogramBoolean("Net.DNS.DnsConfig.Resolv.FileChange", true);
     OnConfigChanged(!error);
   }
 
   void OnNsswitchFilePathWatcherChange(const base::FilePath& path, bool error) {
-    UMA_HISTOGRAM_BOOLEAN("Net.DNS.DnsConfig.Nsswitch.FileChange", true);
+    base::UmaHistogramBoolean("Net.DNS.DnsConfig.Nsswitch.FileChange", true);
     OnConfigChanged(!error);
   }
 
@@ -453,14 +456,14 @@
         }
       }
 
-      UMA_HISTOGRAM_BOOLEAN("Net.DNS.DnsConfig.Resolv.Read",
-                            dns_config_.has_value());
+      base::UmaHistogramBoolean("Net.DNS.DnsConfig.Resolv.Read",
+                                dns_config_.has_value());
       if (!dns_config_.has_value())
         return;
-      UMA_HISTOGRAM_BOOLEAN("Net.DNS.DnsConfig.Resolv.Valid",
-                            dns_config_->IsValid());
-      UMA_HISTOGRAM_BOOLEAN("Net.DNS.DnsConfig.Resolv.Compatible",
-                            !dns_config_->unhandled_options);
+      base::UmaHistogramBoolean("Net.DNS.DnsConfig.Resolv.Valid",
+                                dns_config_->IsValid());
+      base::UmaHistogramBoolean("Net.DNS.DnsConfig.Resolv.Compatible",
+                                !dns_config_->unhandled_options);
 
       // Override `fallback_period` value to match default setting on
       // Windows.
@@ -469,12 +472,16 @@
       if (dns_config_ && !dns_config_->unhandled_options) {
         std::vector<NsswitchReader::ServiceSpecification> nsswitch_hosts =
             nsswitch_reader_->ReadAndParseHosts();
-        UMA_HISTOGRAM_COUNTS_100("Net.DNS.DnsConfig.Nsswitch.NumServices",
-                                 nsswitch_hosts.size());
+        base::UmaHistogramCounts100("Net.DNS.DnsConfig.Nsswitch.NumServices",
+                                    nsswitch_hosts.size());
         dns_config_->unhandled_options =
             !IsNsswitchConfigCompatible(nsswitch_hosts);
-        UMA_HISTOGRAM_BOOLEAN("Net.DNS.DnsConfig.Nsswitch.Compatible",
-                              !dns_config_->unhandled_options);
+        base::UmaHistogramBoolean("Net.DNS.DnsConfig.Nsswitch.Compatible",
+                                  !dns_config_->unhandled_options);
+        base::UmaHistogramBoolean(
+            "Net.DNS.DnsConfig.Nsswitch.NisServiceInHosts",
+            base::Contains(nsswitch_hosts, NsswitchReader::Service::kNis,
+                           &NsswitchReader::ServiceSpecification::service));
       }
     }
 
diff --git a/net/dns/dns_config_service_linux_unittest.cc b/net/dns/dns_config_service_linux_unittest.cc
index 4324085..cd92a04 100644
--- a/net/dns/dns_config_service_linux_unittest.cc
+++ b/net/dns/dns_config_service_linux_unittest.cc
@@ -23,6 +23,7 @@
 #include "base/task/single_thread_task_runner.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
 #include "base/test/test_waitable_event.h"
 #include "base/threading/thread_task_runner_handle.h"
@@ -41,6 +42,9 @@
 
 namespace {
 
+const char kNsswitchNisServiceInHostsHistogramName[] =
+    "Net.DNS.DnsConfig.Nsswitch.NisServiceInHosts";
+
 // MAXNS is normally 3, but let's test 4 if possible.
 const char* const kNameserversIPv4[] = {
     "8.8.8.8",
@@ -879,6 +883,28 @@
   EXPECT_TRUE(config->unhandled_options);
 }
 
+TEST_F(DnsConfigServiceLinuxTest, HistogramNoNisServiceInHosts) {
+  auto res = std::make_unique<struct __res_state>();
+  InitializeResState(res.get());
+  resolv_reader_->set_value(std::move(res));
+
+  nsswitch_reader_->set_value(
+      {NsswitchReader::ServiceSpecification(NsswitchReader::Service::kFiles),
+       NsswitchReader::ServiceSpecification(NsswitchReader::Service::kDns)});
+
+  base::HistogramTester histogram_tester;
+  CallbackHelper callback_helper;
+  service_.ReadConfig(callback_helper.GetCallback());
+  absl::optional<DnsConfig> config = callback_helper.WaitForResult();
+  EXPECT_TRUE(resolv_reader_->closed());
+  histogram_tester.ExpectBucketCount(kNsswitchNisServiceInHostsHistogramName,
+                                     false, 1);
+
+  ASSERT_TRUE(config.has_value());
+  EXPECT_TRUE(config->IsValid());
+  EXPECT_FALSE(config->unhandled_options);
+}
+
 TEST_F(DnsConfigServiceLinuxTest, AcceptsNsswitchNis) {
   auto res = std::make_unique<struct __res_state>();
   InitializeResState(res.get());
@@ -889,10 +915,13 @@
        NsswitchReader::ServiceSpecification(NsswitchReader::Service::kNis),
        NsswitchReader::ServiceSpecification(NsswitchReader::Service::kDns)});
 
+  base::HistogramTester histogram_tester;
   CallbackHelper callback_helper;
   service_.ReadConfig(callback_helper.GetCallback());
   absl::optional<DnsConfig> config = callback_helper.WaitForResult();
   EXPECT_TRUE(resolv_reader_->closed());
+  histogram_tester.ExpectBucketCount(kNsswitchNisServiceInHostsHistogramName,
+                                     true, 1);
 
   ASSERT_TRUE(config.has_value());
   EXPECT_TRUE(config->IsValid());
diff --git a/net/http/http_cache.cc b/net/http/http_cache.cc
index c337be7..1ee1910 100644
--- a/net/http/http_cache.cc
+++ b/net/http/http_cache.cc
@@ -147,11 +147,6 @@
   return readers.count(transaction) > 0;
 }
 
-base::SafeRef<HttpCache::ActiveEntry> HttpCache::ActiveEntry::GetSafeRef()
-    const {
-  return weak_factory_.GetSafeRef();
-}
-
 //-----------------------------------------------------------------------------
 
 // This structure keeps track of work items that are attempting to create or
diff --git a/net/http/http_cache.h b/net/http/http_cache.h
index fc358a2c..987e073e 100644
--- a/net/http/http_cache.h
+++ b/net/http/http_cache.h
@@ -25,7 +25,6 @@
 #include "base/files/file_path.h"
 #include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
-#include "base/memory/safe_ref.h"
 #include "base/memory/weak_ptr.h"
 #include "base/threading/thread_checker.h"
 #include "base/time/clock.h"
@@ -363,8 +362,6 @@
 
     disk_cache::Entry* GetEntry() { return disk_entry.get(); }
 
-    base::SafeRef<ActiveEntry> GetSafeRef() const;
-
     disk_cache::ScopedEntryPtr disk_entry;
 
     // Indicates if the disk_entry was opened or not (i.e.: created).
@@ -396,10 +393,6 @@
 
     // True if entry is doomed.
     bool doomed = false;
-
-    // TODO(ricea): Delete this when undoing the change to
-    // HttpCache::Transaction to use SafeRef.
-    base::WeakPtrFactory<ActiveEntry> weak_factory_{this};
   };
 
   using ActiveEntriesMap =
diff --git a/net/http/http_cache_transaction.cc b/net/http/http_cache_transaction.cc
index 50c1414..405fa57 100644
--- a/net/http/http_cache_transaction.cc
+++ b/net/http/http_cache_transaction.cc
@@ -198,7 +198,7 @@
   callback_.Reset();
 
   if (cache_) {
-    if (safe_entry_) {
+    if (entry_) {
       DoneWithEntry(false /* entry_is_complete */);
     } else if (cache_pending_) {
       cache_->RemovePendingTransaction(this);
@@ -214,7 +214,7 @@
   const HttpTransaction* transaction = network_transaction();
   if (transaction)
     return transaction->GetLoadState();
-  if (safe_entry_ || !request_)
+  if (entry_ || !request_)
     return LOAD_STATE_IDLE;
   return LOAD_STATE_WAITING_FOR_CACHE;
 }
@@ -236,7 +236,7 @@
   DCHECK(callback_.is_null());
   DCHECK(!reading_);
   DCHECK(!network_trans_.get());
-  DCHECK(!safe_entry_);
+  DCHECK(!entry_);
   DCHECK_EQ(next_state_, STATE_NONE);
 
   if (!cache_.get())
@@ -370,7 +370,7 @@
 }
 
 int HttpCache::Transaction::TransitionToReadingState() {
-  if (!safe_entry_) {
+  if (!entry_) {
     if (network_trans_) {
       // This can happen when the request should be handled exclusively by
       // the network layer (skipping the cache entirely using
@@ -399,7 +399,7 @@
   if (!InWriters()) {
     // Since transaction is not a writer and we are in Read(), it must be a
     // reader.
-    DCHECK(safe_entry_.value()->TransactionInReaders(this));
+    DCHECK(entry_->TransactionInReaders(this));
     DCHECK(mode_ == READ || (mode_ == READ_WRITE && partial_));
     next_state_ = STATE_CACHE_READ_DATA;
     return OK;
@@ -410,7 +410,7 @@
   // If it's a writer and it is partial then it may need to read from the cache
   // or from the network based on whether network transaction is present or not.
   if (partial_) {
-    if (safe_entry_.value()->writers->network_transaction())
+    if (entry_->writers->network_transaction())
       next_state_ = STATE_NETWORK_READ_CACHE_WRITE;
     else
       next_state_ = STATE_CACHE_READ_DATA;
@@ -420,10 +420,8 @@
   // Full request.
   // If it's a writer and a full request then it may read from the cache if its
   // offset is behind the current offset else from the network.
-  int disk_entry_size =
-      safe_entry_.value()->GetEntry()->GetDataSize(kResponseContentIndex);
-  if (read_offset_ == disk_entry_size ||
-      safe_entry_.value()->writers->network_read_only()) {
+  int disk_entry_size = entry_->GetEntry()->GetDataSize(kResponseContentIndex);
+  if (read_offset_ == disk_entry_size || entry_->writers->network_read_only()) {
     next_state_ = STATE_NETWORK_READ_CACHE_WRITE;
   } else {
     DCHECK_LT(read_offset_, disk_entry_size);
@@ -464,7 +462,7 @@
 }
 
 void HttpCache::Transaction::DoneReading() {
-  if (cache_.get() && safe_entry_) {
+  if (cache_.get() && entry_) {
     DCHECK_NE(mode_, UPDATE);
     DoneWithEntry(true);
   }
@@ -554,7 +552,7 @@
 
   if (InWriters()) {
     DCHECK(!network_trans_ || partial_);
-    safe_entry_.value()->writers->UpdatePriority();
+    entry_->writers->UpdatePriority();
   }
 }
 
@@ -621,7 +619,7 @@
   if (network_trans_) {
     network_trans_->CloseConnectionOnDestruction();
   } else if (InWriters()) {
-    safe_entry_.value()->writers->CloseConnectionOnDestruction();
+    entry_->writers->CloseConnectionOnDestruction();
   }
 }
 
@@ -631,7 +629,7 @@
   DCHECK_NE(STATE_UNSET, next_state_);
 
   next_state_ = STATE_HEADERS_PHASE_CANNOT_PROCEED;
-  safe_entry_ = absl::nullopt;
+  entry_ = nullptr;
 }
 
 void HttpCache::Transaction::WriterAboutToBeRemovedFromEntry(int result) {
@@ -642,12 +640,11 @@
   // Since the transaction can no longer access the network transaction, save
   // all network related info now.
   if (moved_network_transaction_to_writers_ &&
-      safe_entry_.value()->writers->network_transaction()) {
-    SaveNetworkTransactionInfo(
-        *(safe_entry_.value()->writers->network_transaction()));
+      entry_->writers->network_transaction()) {
+    SaveNetworkTransactionInfo(*(entry_->writers->network_transaction()));
   }
 
-  safe_entry_ = absl::nullopt;
+  entry_ = nullptr;
   mode_ = NONE;
 
   // Transactions in the midst of a Read call through writers will get any error
@@ -664,9 +661,8 @@
       TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
   mode_ = READ;
   if (moved_network_transaction_to_writers_ &&
-      safe_entry_.value()->writers->network_transaction()) {
-    SaveNetworkTransactionInfo(
-        *(safe_entry_.value()->writers->network_transaction()));
+      entry_->writers->network_transaction()) {
+    SaveNetworkTransactionInfo(*(entry_->writers->network_transaction()));
   }
 }
 
@@ -1021,7 +1017,7 @@
 
   // Assert Start() state machine's allowed last state in successful cases when
   // caching is happening.
-  DCHECK(reading_ || rv != OK || !safe_entry_ ||
+  DCHECK(reading_ || rv != OK || !entry_ ||
          state == STATE_FINISH_HEADERS_COMPLETE);
 
   if (rv != ERR_IO_PENDING && !callback_.is_null()) {
@@ -1466,7 +1462,7 @@
   cache_pending_ = false;
 
   if (result == OK)
-    safe_entry_ = new_entry_->GetSafeRef();
+    entry_ = new_entry_;
 
   // If there is a failure, the cache should have taken care of new_entry_.
   new_entry_ = nullptr;
@@ -1496,7 +1492,7 @@
   // already written, to avoid data race since cache thread can also access
   // this.
   if (!cache_->IsWritingInProgress(entry()))
-    open_entry_last_used_ = safe_entry_.value()->GetEntry()->GetLastUsed();
+    open_entry_last_used_ = entry_->GetEntry()->GetLastUsed();
 
   // TODO(jkarlin): We should either handle the case or DCHECK.
   if (result != OK) {
@@ -1541,11 +1537,10 @@
     return OK;
   }
 
-  safe_entry_ = new_entry_->GetSafeRef();
-
+  entry_ = new_entry_;
   DCHECK_NE(response_.headers->response_code(), net::HTTP_NOT_MODIFIED);
   DCHECK(cache_->CanTransactionWriteResponseHeaders(
-      entry(), this, partial_ != nullptr, false));
+      entry_, this, partial_ != nullptr, false));
   TransitionToState(STATE_CACHE_WRITE_RESPONSE);
   return OK;
 }
@@ -1554,16 +1549,15 @@
   TRACE_EVENT_WITH_FLOW0("net", "HttpCacheTransaction::DoCacheReadResponse",
                          TRACE_ID_LOCAL(trace_id_),
                          TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
-  DCHECK(safe_entry_);
+  DCHECK(entry_);
   TransitionToState(STATE_CACHE_READ_RESPONSE_COMPLETE);
 
-  io_buf_len_ =
-      safe_entry_.value()->GetEntry()->GetDataSize(kResponseInfoIndex);
+  io_buf_len_ = entry_->GetEntry()->GetDataSize(kResponseInfoIndex);
   read_buf_ = base::MakeRefCounted<IOBuffer>(io_buf_len_);
 
   net_log_.BeginEvent(NetLogEventType::HTTP_CACHE_READ_INFO);
-  return safe_entry_.value()->GetEntry()->ReadData(
-      kResponseInfoIndex, 0, read_buf_.get(), io_buf_len_, io_callback_);
+  return entry_->GetEntry()->ReadData(kResponseInfoIndex, 0, read_buf_.get(),
+                                      io_buf_len_, io_callback_);
 }
 
 int HttpCache::Transaction::DoCacheReadResponseComplete(int result) {
@@ -1606,8 +1600,7 @@
   // currently writing the response body due to the data race mentioned in the
   // associated bug.
   if (!cache_->IsWritingInProgress(entry())) {
-    int current_size =
-        safe_entry_.value()->GetEntry()->GetDataSize(kResponseContentIndex);
+    int current_size = entry_->GetEntry()->GetDataSize(kResponseContentIndex);
     int64_t full_response_length = response_.headers->GetContentLength();
 
     // Some resources may have slipped in as truncated when they're not.
@@ -1704,7 +1697,7 @@
                          "HttpCacheTransaction::DoCacheDispatchValidation",
                          TRACE_ID_LOCAL(trace_id_),
                          TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
-  if (!safe_entry_) {
+  if (!entry_) {
     // Entry got destroyed when twiddling unused-since-prefetch bit.
     TransitionToState(STATE_HEADERS_PHASE_CANNOT_PROCEED);
     return OK;
@@ -1744,7 +1737,7 @@
 
 int HttpCache::Transaction::DoCacheQueryData() {
   TransitionToState(STATE_CACHE_QUERY_DATA_COMPLETE);
-  return safe_entry_.value()->GetEntry()->ReadyForSparseIO(io_callback_);
+  return entry_->GetEntry()->ReadyForSparseIO(io_callback_);
 }
 
 int HttpCache::Transaction::DoCacheQueryDataComplete(int result) {
@@ -1765,8 +1758,7 @@
   }
 
   TransitionToState(STATE_COMPLETE_PARTIAL_CACHE_VALIDATION);
-  return partial_->ShouldValidateCache(safe_entry_.value()->GetEntry(),
-                                       io_callback_);
+  return partial_->ShouldValidateCache(entry_->GetEntry(), io_callback_);
 }
 
 int HttpCache::Transaction::DoCompletePartialCacheValidation(int result) {
@@ -1782,7 +1774,7 @@
     return result;
   }
 
-  partial_->PrepareCacheValidation(safe_entry_.value()->GetEntry(),
+  partial_->PrepareCacheValidation(entry_->GetEntry(),
                                    &custom_request_->extra_headers);
 
   if (reading_ && partial_->IsCurrentRangeCached()) {
@@ -1936,7 +1928,7 @@
     // has been read and we have no way to gather credentials.  We would
     // fail again, and potentially loop.  This can happen if the credentials
     // expire while chrome is suspended.
-    if (safe_entry_)
+    if (entry_)
       DoomPartialEntry(false);
     mode_ = NONE;
     partial_.reset();
@@ -1987,7 +1979,7 @@
   if (mode_ == WRITE &&
       (method_ == "PUT" || method_ == "DELETE" || method_ == "PATCH")) {
     if (NonErrorResponse(new_response_->headers->response_code()) &&
-        (safe_entry_ && !safe_entry_.value()->doomed)) {
+        (entry_ && !entry_->doomed)) {
       int ret = cache_->DoomEntry(cache_key_, nullptr);
       DCHECK_EQ(OK, ret);
     }
@@ -2061,7 +2053,7 @@
   response_.vary_data.Init(*request_, *response_.headers);
 
   if (ShouldDisableCaching(*response_.headers)) {
-    if (!safe_entry_.value()->doomed) {
+    if (!entry_->doomed) {
       int ret = cache_->DoomEntry(cache_key_, nullptr);
       DCHECK_EQ(OK, ret);
     }
@@ -2117,16 +2109,16 @@
     // than the cached 200 response, is what will be returned to the user.
     UpdateSecurityHeadersBeforeForwarding();
     DoneWithEntry(true);
-  } else if (safe_entry_ && !handling_206_) {
+  } else if (entry_ && !handling_206_) {
     DCHECK_EQ(READ_WRITE, mode_);
-    if ((!partial_ && !cache_->IsWritingInProgress(entry())) ||
+    if ((!partial_ && !cache_->IsWritingInProgress(entry_)) ||
         (partial_ && partial_->IsLastRange())) {
       mode_ = READ;
     }
     // We no longer need the network transaction, so destroy it.
     if (network_trans_)
       ResetNetworkTransaction();
-  } else if (safe_entry_ && handling_206_ && truncated_ &&
+  } else if (entry_ && handling_206_ && truncated_ &&
              partial_->initial_validation()) {
     // We just finished the validation of a truncated entry, and the server
     // is willing to resume the operation. Now we go back and start serving
@@ -2194,8 +2186,8 @@
   // cannot write to this entry. This transaction then continues to read from
   // the network without writing to the backend.
   bool is_match = response_.headers->response_code() == net::HTTP_NOT_MODIFIED;
-  if (safe_entry_ && !cache_->CanTransactionWriteResponseHeaders(
-                         entry(), this, partial_ != nullptr, is_match)) {
+  if (entry_ && !cache_->CanTransactionWriteResponseHeaders(
+                    entry_, this, partial_ != nullptr, is_match)) {
     done_headers_create_new_entry_ = true;
 
     // The transaction needs to overwrite this response. Doom the current entry,
@@ -2205,8 +2197,8 @@
     // so that this transaction can go straight to writing a response.
     mode_ = WRITE;
     TransitionToState(STATE_INIT_ENTRY);
-    cache_->DoomEntryValidationNoMatch(entry());
-    safe_entry_ = absl::nullopt;
+    cache_->DoomEntryValidationNoMatch(entry_);
+    entry_ = nullptr;
     return OK;
   }
 
@@ -2234,13 +2226,13 @@
                          TRACE_ID_LOCAL(trace_id_),
                          TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
   TransitionToState(STATE_TRUNCATE_CACHED_DATA_COMPLETE);
-  if (!safe_entry_)
+  if (!entry_)
     return OK;
   net_log_.BeginEvent(NetLogEventType::HTTP_CACHE_WRITE_DATA);
   // Truncate the stream.
-  return safe_entry_.value()->GetEntry()->WriteData(
-      kResponseContentIndex, /*offset=*/0,
-      /*buf=*/nullptr, /*buf_len=*/0, io_callback_, /*truncate=*/true);
+  return entry_->GetEntry()->WriteData(kResponseContentIndex, /*offset=*/0,
+                                       /*buf=*/nullptr, /*buf_len=*/0,
+                                       io_callback_, /*truncate=*/true);
 }
 
 int HttpCache::Transaction::DoTruncateCachedDataComplete(int result) {
@@ -2248,7 +2240,7 @@
       "net", "HttpCacheTransaction::DoTruncateCachedDataComplete",
       TRACE_ID_LOCAL(trace_id_),
       TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "result", result);
-  if (safe_entry_) {
+  if (entry_) {
     net_log_.EndEventWithNetErrorCode(NetLogEventType::HTTP_CACHE_WRITE_DATA,
                                       result);
   }
@@ -2283,7 +2275,7 @@
 
   SetRequest(net_log_);
 
-  safe_entry_ = absl::nullopt;
+  entry_ = nullptr;
   new_entry_ = nullptr;
 
   // TODO(https://crbug.com/1219402): This should probably clear `response_`,
@@ -2301,7 +2293,7 @@
   TRACE_EVENT_WITH_FLOW1(
       "net", "HttpCacheTransaction::DoFinishHeaders", TRACE_ID_LOCAL(trace_id_),
       TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "result", result);
-  if (!cache_.get() || !safe_entry_ || result != OK) {
+  if (!cache_.get() || !entry_ || result != OK) {
     TransitionToState(STATE_NONE);
     return result;
   }
@@ -2319,13 +2311,13 @@
   // If the transaction needs to wait because another transaction is still
   // writing the response body, it will return ERR_IO_PENDING now and the
   // io_callback_ will be invoked when the wait is done.
-  int rv = cache_->DoneWithResponseHeaders(entry(), this, partial_ != nullptr);
+  int rv = cache_->DoneWithResponseHeaders(entry_, this, partial_ != nullptr);
   DCHECK(!reading_ || rv == OK) << "Expected OK, but got " << rv;
 
   if (rv == ERR_IO_PENDING) {
     DCHECK(entry_lock_waiting_since_.is_null());
     entry_lock_waiting_since_ = TimeTicks::Now();
-    AddCacheLockTimeoutHandler(entry());
+    AddCacheLockTimeoutHandler(entry_);
   }
   return rv;
 }
@@ -2342,8 +2334,8 @@
   }
 
   if (network_trans_ && InWriters()) {
-    safe_entry_.value()->writers->SetNetworkTransaction(
-        this, std::move(network_trans_), std::move(checksum_));
+    entry_->writers->SetNetworkTransaction(this, std::move(network_trans_),
+                                           std::move(checksum_));
     moved_network_transaction_to_writers_ = true;
   }
 
@@ -2367,8 +2359,7 @@
                          read_buf_len_);
   DCHECK(InWriters());
   TransitionToState(STATE_NETWORK_READ_CACHE_WRITE_COMPLETE);
-  return safe_entry_.value()->writers->Read(read_buf_, read_buf_len_,
-                                            io_callback_, this);
+  return entry_->writers->Read(read_buf_, read_buf_len_, io_callback_, this);
 }
 
 int HttpCache::Transaction::DoNetworkReadCacheWriteComplete(int result) {
@@ -2388,7 +2379,7 @@
     // We should have discovered this error in WriterAboutToBeRemovedFromEntry
     DCHECK_EQ(result, shared_writing_error_);
     DCHECK_EQ(NONE, mode_);
-    DCHECK(!safe_entry_);
+    DCHECK(!entry_);
     TransitionToState(STATE_NONE);
     return result;
   }
@@ -2399,7 +2390,7 @@
 
   if (result == 0) {
     DCHECK_EQ(NONE, mode_);
-    DCHECK(!safe_entry_);
+    DCHECK(!entry_);
   } else {
     read_offset_ += result;
     if (checksum_)
@@ -2423,11 +2414,9 @@
       // We need to move on to the next range.
       if (network_trans_) {
         ResetNetworkTransaction();
-      } else if (InWriters() &&
-                 safe_entry_.value()->writers->network_transaction()) {
-        SaveNetworkTransactionInfo(
-            *(safe_entry_.value()->writers->network_transaction()));
-        safe_entry_.value()->writers->ResetNetworkTransaction();
+      } else if (InWriters() && entry_->writers->network_transaction()) {
+        SaveNetworkTransactionInfo(*(entry_->writers->network_transaction()));
+        entry_->writers->ResetNetworkTransaction();
       }
       TransitionToState(STATE_START_PARTIAL_CACHE_VALIDATION);
     } else {
@@ -2483,18 +2472,18 @@
     return 0;
   }
 
-  DCHECK(safe_entry_);
+  DCHECK(entry_);
   TransitionToState(STATE_CACHE_READ_DATA_COMPLETE);
 
   net_log_.BeginEvent(NetLogEventType::HTTP_CACHE_READ_DATA);
   if (partial_) {
-    return partial_->CacheRead(safe_entry_.value()->GetEntry(), read_buf_.get(),
+    return partial_->CacheRead(entry_->GetEntry(), read_buf_.get(),
                                read_buf_len_, io_callback_);
   }
 
-  return safe_entry_.value()->GetEntry()->ReadData(
-      kResponseContentIndex, read_offset_, read_buf_.get(), read_buf_len_,
-      io_callback_);
+  return entry_->GetEntry()->ReadData(kResponseContentIndex, read_offset_,
+                                      read_buf_.get(), read_buf_len_,
+                                      io_callback_);
 }
 
 int HttpCache::Transaction::DoCacheReadDataComplete(int result) {
@@ -2864,7 +2853,7 @@
   DCHECK_EQ(mode_, READ_WRITE);
 
   if (!partial_->UpdateFromStoredHeaders(
-          response_.headers.get(), safe_entry_.value()->GetEntry(), truncated_,
+          response_.headers.get(), entry_->GetEntry(), truncated_,
           cache_->IsWritingInProgress(entry()))) {
     return DoRestartPartialRequest();
   }
@@ -3188,7 +3177,7 @@
   bool partial_response = (response_code == net::HTTP_PARTIAL_CONTENT);
   handling_206_ = false;
 
-  if (!safe_entry_ || method_ != "GET")
+  if (!entry_ || method_ != "GET")
     return true;
 
   if (invalid_range_) {
@@ -3367,7 +3356,7 @@
   if (network_trans_)
     ResetNetworkTransaction();
 
-  if (!safe_entry_) {
+  if (!entry_) {
     // Entry got destroyed when twiddling SWR bits.
     TransitionToState(STATE_HEADERS_PHASE_CANNOT_PROCEED);
     return OK;
@@ -3389,7 +3378,7 @@
     }
   }
 
-  if (!cache_->IsWritingInProgress(entry()))
+  if (!cache_->IsWritingInProgress(entry_))
     mode_ = READ;
 
   if (method_ == "HEAD")
@@ -3404,7 +3393,7 @@
     bool truncated) {
   DCHECK(response.headers);
 
-  if (!safe_entry_)
+  if (!entry_)
     return OK;
 
   net_log_.BeginEvent(NetLogEventType::HTTP_CACHE_WRITE_INFO);
@@ -3442,19 +3431,19 @@
 
   // Summarize some info on cacheability in memory. Don't do it if doomed
   // since then |entry_| isn't definitive for |cache_key_|.
-  if (!safe_entry_.value()->doomed) {
+  if (!entry_->doomed) {
     cache_->GetCurrentBackend()->SetEntryInMemoryData(
         cache_key_, ComputeUnusablePerCachingHeaders()
                         ? HINT_UNUSABLE_PER_CACHING_HEADERS
                         : 0);
   }
 
-  return safe_entry_.value()->GetEntry()->WriteData(
-      kResponseInfoIndex, 0, data.get(), io_buf_len_, io_callback_, true);
+  return entry_->disk_entry->WriteData(kResponseInfoIndex, 0, data.get(),
+                                       io_buf_len_, io_callback_, true);
 }
 
 int HttpCache::Transaction::OnWriteResponseInfoToEntryComplete(int result) {
-  if (!safe_entry_)
+  if (!entry_)
     return OK;
   net_log_.EndEventWithNetErrorCode(NetLogEventType::HTTP_CACHE_WRITE_INFO,
                                     result);
@@ -3470,11 +3459,10 @@
   bool stopped = false;
   // Let writers know so that it doesn't attempt to write to the cache.
   if (InWriters()) {
-    stopped =
-        safe_entry_.value()->writers->StopCaching(success /* keep_entry */);
+    stopped = entry_->writers->StopCaching(success /* keep_entry */);
     if (stopped)
       mode_ = NONE;
-  } else if (safe_entry_) {
+  } else if (entry_) {
     stopped = true;
     DoneWithEntry(success /* entry_is_complete */);
   }
@@ -3482,21 +3470,21 @@
 }
 
 void HttpCache::Transaction::DoneWithEntry(bool entry_is_complete) {
-  if (!safe_entry_)
+  if (!entry_)
     return;
 
-  cache_->DoneWithEntry(entry(), this, entry_is_complete, partial_ != nullptr);
-  safe_entry_ = absl::nullopt;
+  cache_->DoneWithEntry(entry_, this, entry_is_complete, partial_ != nullptr);
+  entry_ = nullptr;
   mode_ = NONE;  // switch to 'pass through' mode
 }
 
 void HttpCache::Transaction::DoneWithEntryForRestartWithCache() {
-  if (!safe_entry_)
+  if (!entry_)
     return;
 
-  cache_->DoneWithEntry(entry(), this, /*entry_is_complete=*/true,
+  cache_->DoneWithEntry(entry_, this, /*entry_is_complete=*/true,
                         partial_ != nullptr);
-  safe_entry_ = absl::nullopt;
+  entry_ = nullptr;
   new_entry_ = nullptr;
 }
 
@@ -3522,9 +3510,9 @@
     // Since we are going to add this to a new entry, not recording histograms
     // or setting mode to NONE at this point by invoking the wrapper
     // DoneWithEntry.
-    cache_->DoneWithEntry(entry(), this, true /* entry_is_complete */,
+    cache_->DoneWithEntry(entry_, this, true /* entry_is_complete */,
                           partial_ != nullptr);
-    safe_entry_ = absl::nullopt;
+    entry_ = nullptr;
     is_sparse_ = false;
     // It's OK to use PartialData::RestoreHeaders here as |restart| is only set
     // when the HttpResponseInfo couldn't even be read, at which point it's
@@ -3559,14 +3547,14 @@
 
 void HttpCache::Transaction::DoomPartialEntry(bool delete_object) {
   DVLOG(2) << "DoomPartialEntry";
-  if (entry() && !safe_entry_.value()->doomed) {
+  if (entry_ && !entry_->doomed) {
     int rv = cache_->DoomEntry(cache_key_, nullptr);
     DCHECK_EQ(OK, rv);
   }
 
-  cache_->DoneWithEntry(entry(), this, false /* entry_is_complete */,
+  cache_->DoneWithEntry(entry_, this, false /* entry_is_complete */,
                         partial_ != nullptr);
-  safe_entry_ = absl::nullopt;
+  entry_ = nullptr;
   is_sparse_ = false;
   truncated_ = false;
   if (delete_object)
@@ -3628,7 +3616,7 @@
   if (network_trans_)
     return network_trans_.get();
   if (InWriters())
-    return safe_entry_.value()->writers->network_transaction();
+    return entry_->writers->network_transaction();
   return nullptr;
 }
 
@@ -3637,7 +3625,7 @@
   if (network_trans_)
     return network_trans_.get();
   if (InWriters() && moved_network_transaction_to_writers_)
-    return safe_entry_.value()->writers->network_transaction();
+    return entry_->writers->network_transaction();
   return nullptr;
 }
 
@@ -3661,8 +3649,7 @@
 //
 bool HttpCache::Transaction::CanResume(bool has_data) {
   // Double check that there is something worth keeping.
-  if (has_data &&
-      !safe_entry_.value()->GetEntry()->GetDataSize(kResponseContentIndex))
+  if (has_data && !entry_->GetEntry()->GetDataSize(kResponseContentIndex))
     return false;
 
   if (method_ != "GET")
@@ -3871,8 +3858,7 @@
 }
 
 bool HttpCache::Transaction::InWriters() const {
-  return safe_entry_ && safe_entry_.value()->writers &&
-         safe_entry_.value()->writers->HasTransaction(this);
+  return entry_ && entry_->writers && entry_->writers->HasTransaction(this);
 }
 
 HttpCache::Transaction::NetworkTransactionInfo::NetworkTransactionInfo() =
diff --git a/net/http/http_cache_transaction.h b/net/http/http_cache_transaction.h
index d17f282..0a937a3 100644
--- a/net/http/http_cache_transaction.h
+++ b/net/http/http_cache_transaction.h
@@ -16,7 +16,6 @@
 
 #include "base/memory/raw_ptr.h"
 #include "base/memory/ref_counted.h"
-#include "base/memory/safe_ref.h"
 #include "base/memory/weak_ptr.h"
 #include "base/time/time.h"
 #include "net/base/completion_once_callback.h"
@@ -36,7 +35,6 @@
 #include "net/log/net_log_with_source.h"
 #include "net/socket/connection_attempts.h"
 #include "net/websockets/websocket_handshake_stream_base.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace crypto {
 class SecureHash;
@@ -107,6 +105,8 @@
 
   const std::string& key() const { return cache_key_; }
 
+  HttpCache::ActiveEntry* entry() { return entry_; }
+
   // Returns the LoadState of the writer transaction of a given ActiveEntry. In
   // other words, returns the LoadState of this transaction without asking the
   // http cache, because this transaction should be the one currently writing
@@ -608,13 +608,6 @@
   // forwarded might need to set these headers again to avoid being blocked.
   void UpdateSecurityHeadersBeforeForwarding();
 
-  HttpCache::ActiveEntry* entry() {
-    // SafeRef lacks a method to extract the pointer so we have to convert it to
-    // a reference then back to a pointer. This will CHECK() if `safe_entry_` is
-    // not set.
-    return &*safe_entry_.value();
-  }
-
   State next_state_{STATE_NONE};
 
   // Used for tracing.
@@ -638,16 +631,7 @@
   // |external_validation_| contains the value of those headers.
   ValidationHeaders external_validation_;
   base::WeakPtr<HttpCache> cache_;
-  // This member is temporarily an optional SafeRef. The name has been
-  // temporarily changed from `entry_` to `safe_entry_` to ensure that no
-  // references are missed. It is wrapped in an optional because SafeRef cannot
-  // be null. There are many unchecked calls to safe_entry_.value(). The safety
-  // of these rely on the fact that absl::optional's implementation of value()
-  // aborts when not set.
-  // TODO(ricea): Change this back to a raw_ptr<ActiveEntry, DangingUntriaged>
-  // or possibly a WeakPtr.
-  absl::optional<base::SafeRef<HttpCache::ActiveEntry>> safe_entry_;
-
+  raw_ptr<HttpCache::ActiveEntry, DanglingUntriaged> entry_ = nullptr;
   HttpCache::ActiveEntry* new_entry_ = nullptr;
   std::unique_ptr<HttpTransaction> network_trans_;
   CompletionOnceCallback callback_;  // Consumer's callback.
diff --git a/net/http/http_cache_writers.cc b/net/http/http_cache_writers.cc
index b70dbec..fdb6fd4 100644
--- a/net/http/http_cache_writers.cc
+++ b/net/http/http_cache_writers.cc
@@ -69,8 +69,9 @@
     default;
 
 HttpCache::Writers::Writers(HttpCache* cache, HttpCache::ActiveEntry* entry)
-    : cache_(cache), entry_(entry->GetSafeRef()) {
+    : cache_(cache), entry_(entry) {
   DCHECK(cache_);
+  DCHECK(entry_);
 }
 
 HttpCache::Writers::~Writers() = default;
@@ -122,7 +123,7 @@
   network_read_only_ = true;
   if (!keep_entry) {
     should_keep_entry_ = false;
-    cache_->WritersDoomEntryRestartTransactions(entry());
+    cache_->WritersDoomEntryRestartTransactions(entry_);
   }
 
   return true;
@@ -197,7 +198,7 @@
   if (!success && ShouldTruncate())
     TruncateEntry();
 
-  cache_->WritersDoneWritingToEntry(entry(), success, should_keep_entry_,
+  cache_->WritersDoneWritingToEntry(entry_, success, should_keep_entry_,
                                     TransactionSet());
 }
 
@@ -631,7 +632,7 @@
   if (all_writers_.empty()) {
     SetCacheCallback(false, TransactionSet());
   } else {
-    cache_->WritersDoomEntryRestartTransactions(entry());
+    cache_->WritersDoomEntryRestartTransactions(entry_);
   }
 }
 
@@ -680,7 +681,7 @@
                                           const TransactionSet& make_readers) {
   DCHECK(!cache_callback_);
   cache_callback_ = base::BindOnce(&HttpCache::WritersDoneWritingToEntry,
-                                   cache_->GetWeakPtr(), entry(), success,
+                                   cache_->GetWeakPtr(), entry_, success,
                                    should_keep_entry_, make_readers);
 }
 
diff --git a/net/http/http_cache_writers.h b/net/http/http_cache_writers.h
index b9374fd..7de8dde 100644
--- a/net/http/http_cache_writers.h
+++ b/net/http/http_cache_writers.h
@@ -9,7 +9,6 @@
 #include <memory>
 
 #include "base/memory/raw_ptr.h"
-#include "base/memory/safe_ref.h"
 #include "base/memory/weak_ptr.h"
 #include "net/base/completion_once_callback.h"
 #include "net/http/http_cache.h"
@@ -242,8 +241,6 @@
   // IO Completion callback function.
   void OnIOComplete(int result);
 
-  ActiveEntry* entry() const { return &*entry_; }
-
   State next_state_ = State::NONE;
 
   // True if only reading from network and not writing to cache.
@@ -252,7 +249,7 @@
   raw_ptr<HttpCache> const cache_ = nullptr;
 
   // Owner of |this|.
-  base::SafeRef<ActiveEntry> const entry_;
+  raw_ptr<ActiveEntry> const entry_ = nullptr;
 
   std::unique_ptr<HttpTransaction> network_transaction_;
 
diff --git a/net/test/cert_builder.cc b/net/test/cert_builder.cc
index f564d81..60755e4a 100644
--- a/net/test/cert_builder.cc
+++ b/net/test/cert_builder.cc
@@ -7,9 +7,11 @@
 #include "base/files/file_path.h"
 #include "base/memory/ptr_util.h"
 #include "base/strings/string_number_conversions.h"
+#include "crypto/ec_private_key.h"
 #include "crypto/openssl_util.h"
 #include "crypto/rsa_private_key.h"
 #include "net/cert/asn1_util.h"
+#include "net/cert/pki/verify_signed_data.h"
 #include "net/cert/x509_util.h"
 #include "net/der/encode_values.h"
 #include "net/der/input.h"
@@ -51,6 +53,26 @@
                      std::end(kSha1WithRSAEncryption));
 }
 
+std::string Md5WithRSAEncryption() {
+  const uint8_t kMd5WithRSAEncryption[] = {0x30, 0x0d, 0x06, 0x09, 0x2a,
+                                           0x86, 0x48, 0x86, 0xf7, 0x0d,
+                                           0x01, 0x01, 0x04, 0x05, 0x00};
+  return std::string(std::begin(kMd5WithRSAEncryption),
+                     std::end(kMd5WithRSAEncryption));
+}
+
+std::string EcdsaWithSha256() {
+  const uint8_t kDer[] = {0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86,
+                          0x48, 0xce, 0x3d, 0x04, 0x03, 0x02};
+  return std::string(std::begin(kDer), std::end(kDer));
+}
+
+std::string EcdsaWithSha1() {
+  const uint8_t kDer[] = {0x30, 0x09, 0x06, 0x07, 0x2a, 0x86,
+                          0x48, 0xce, 0x3d, 0x04, 0x01};
+  return std::string(std::begin(kDer), std::end(kDer));
+}
+
 // Adds bytes (specified as a StringPiece) to the given CBB.
 // The argument ordering follows the boringssl CBB_* api style.
 bool CBBAddBytes(CBB* cbb, base::StringPiece bytes) {
@@ -171,17 +193,26 @@
   // Build slightly modified variants of |orig_certs|.
   *out_root =
       std::make_unique<CertBuilder>(orig_certs[2]->cert_buffer(), nullptr);
+  (*out_root)->SetSignatureAlgorithm(SignatureAlgorithm::kEcdsaSha256);
+  (*out_root)->GenerateECKey();
+
   *out_intermediate = std::make_unique<CertBuilder>(
       orig_certs[1]->cert_buffer(), out_root->get());
   (*out_intermediate)->EraseExtension(der::Input(kCrlDistributionPointsOid));
   (*out_intermediate)->EraseExtension(der::Input(kAuthorityInfoAccessOid));
+  (*out_intermediate)->SetSignatureAlgorithm(SignatureAlgorithm::kEcdsaSha256);
+  (*out_intermediate)->GenerateECKey();
+
   *out_leaf = std::make_unique<CertBuilder>(orig_certs[0]->cert_buffer(),
                                             out_intermediate->get());
   (*out_leaf)->SetSubjectAltName(kHostname);
   (*out_leaf)->EraseExtension(der::Input(kCrlDistributionPointsOid));
   (*out_leaf)->EraseExtension(der::Input(kAuthorityInfoAccessOid));
+  (*out_leaf)->SetSignatureAlgorithm(SignatureAlgorithm::kEcdsaSha256);
+  (*out_leaf)->GenerateECKey();
 }
 
+// static
 void CertBuilder::CreateSimpleChain(std::unique_ptr<CertBuilder>* out_leaf,
                                     std::unique_ptr<CertBuilder>* out_root) {
   const char kHostname[] = "www.example.com";
@@ -194,10 +225,120 @@
 
   // Build slightly modified variants of |orig_certs|.
   *out_root = std::make_unique<CertBuilder>(orig_root->cert_buffer(), nullptr);
+  (*out_root)->SetSignatureAlgorithm(SignatureAlgorithm::kEcdsaSha256);
+  (*out_root)->GenerateECKey();
 
   *out_leaf =
       std::make_unique<CertBuilder>(orig_leaf->cert_buffer(), out_root->get());
   (*out_leaf)->SetSubjectAltName(kHostname);
+  (*out_leaf)->SetSignatureAlgorithm(SignatureAlgorithm::kEcdsaSha256);
+  (*out_leaf)->GenerateECKey();
+}
+
+// static
+absl::optional<SignatureAlgorithm> CertBuilder::DefaultSignatureAlgorithmForKey(
+    EVP_PKEY* key) {
+  if (EVP_PKEY_id(key) == EVP_PKEY_RSA)
+    return SignatureAlgorithm::kRsaPkcs1Sha256;
+  if (EVP_PKEY_id(key) == EVP_PKEY_EC)
+    return SignatureAlgorithm::kEcdsaSha256;
+  return absl::nullopt;
+}
+
+// static
+bool CertBuilder::SignData(SignatureAlgorithm signature_algorithm,
+                           base::StringPiece tbs_data,
+                           EVP_PKEY* key,
+                           CBB* out_signature) {
+  if (!key)
+    return false;
+
+  int expected_pkey_id = 1;
+  const EVP_MD* digest;
+
+  switch (signature_algorithm) {
+    case SignatureAlgorithm::kRsaPkcs1Md5:
+      expected_pkey_id = EVP_PKEY_RSA;
+      digest = EVP_md5();
+      break;
+    case SignatureAlgorithm::kRsaPkcs1Sha1:
+      expected_pkey_id = EVP_PKEY_RSA;
+      digest = EVP_sha1();
+      break;
+    case SignatureAlgorithm::kRsaPkcs1Sha256:
+      expected_pkey_id = EVP_PKEY_RSA;
+      digest = EVP_sha256();
+      break;
+    case SignatureAlgorithm::kRsaPkcs1Sha384:
+      expected_pkey_id = EVP_PKEY_RSA;
+      digest = EVP_sha384();
+      break;
+    case SignatureAlgorithm::kRsaPkcs1Sha512:
+      expected_pkey_id = EVP_PKEY_RSA;
+      digest = EVP_sha512();
+      break;
+
+    case SignatureAlgorithm::kEcdsaSha1:
+      expected_pkey_id = EVP_PKEY_EC;
+      digest = EVP_sha1();
+      break;
+    case SignatureAlgorithm::kEcdsaSha256:
+      expected_pkey_id = EVP_PKEY_EC;
+      digest = EVP_sha256();
+      break;
+    case SignatureAlgorithm::kEcdsaSha384:
+      expected_pkey_id = EVP_PKEY_EC;
+      digest = EVP_sha384();
+      break;
+    case SignatureAlgorithm::kEcdsaSha512:
+      expected_pkey_id = EVP_PKEY_EC;
+      digest = EVP_sha512();
+      break;
+
+    case SignatureAlgorithm::kRsaPssSha256:
+    case SignatureAlgorithm::kRsaPssSha384:
+    case SignatureAlgorithm::kRsaPssSha512:
+    case SignatureAlgorithm::kRsaPkcs1Md2:
+    case SignatureAlgorithm::kRsaPkcs1Md4:
+    case SignatureAlgorithm::kDsaSha1:
+    case SignatureAlgorithm::kDsaSha256:
+      // Unsupported algorithms.
+      return false;
+  }
+
+  const uint8_t* tbs_bytes = reinterpret_cast<const uint8_t*>(tbs_data.data());
+  bssl::ScopedEVP_MD_CTX ctx;
+  uint8_t* sig_out;
+  size_t sig_len;
+
+  return expected_pkey_id == EVP_PKEY_id(key) &&
+         EVP_DigestSignInit(ctx.get(), nullptr, digest, nullptr, key) &&
+         EVP_DigestSign(ctx.get(), nullptr, &sig_len, tbs_bytes,
+                        tbs_data.size()) &&
+         CBB_reserve(out_signature, &sig_out, sig_len) &&
+         EVP_DigestSign(ctx.get(), sig_out, &sig_len, tbs_bytes,
+                        tbs_data.size()) &&
+         CBB_did_write(out_signature, sig_len);
+}
+
+// static
+std::string CertBuilder::SignatureAlgorithmToDer(
+    SignatureAlgorithm signature_algorithm) {
+  switch (signature_algorithm) {
+    case SignatureAlgorithm::kRsaPkcs1Md5:
+      return Md5WithRSAEncryption();
+    case SignatureAlgorithm::kRsaPkcs1Sha1:
+      return Sha1WithRSAEncryption();
+    case SignatureAlgorithm::kRsaPkcs1Sha256:
+      return Sha256WithRSAEncryption();
+    case SignatureAlgorithm::kEcdsaSha1:
+      return EcdsaWithSha1();
+    case SignatureAlgorithm::kEcdsaSha256:
+      return EcdsaWithSha256();
+    default:
+      ADD_FAILURE();
+      return std::string();
+  }
 }
 
 void CertBuilder::SetExtension(const der::Input& oid,
@@ -544,25 +685,9 @@
   SetExtension(der::Input(kAuthorityKeyIdentifierOid), FinishCBB(cbb.get()));
 }
 
-void CertBuilder::SetSignatureAlgorithmRsaPkca1(DigestAlgorithm digest) {
-  switch (digest) {
-    case DigestAlgorithm::Sha256: {
-      SetSignatureAlgorithm(Sha256WithRSAEncryption());
-      break;
-    }
-
-    case DigestAlgorithm::Sha1: {
-      SetSignatureAlgorithm(Sha1WithRSAEncryption());
-      break;
-    }
-
-    default:
-      ASSERT_TRUE(false);
-  }
-}
-
-void CertBuilder::SetSignatureAlgorithm(std::string algorithm_tlv) {
-  signature_algorithm_tlv_ = std::move(algorithm_tlv);
+void CertBuilder::SetSignatureAlgorithm(
+    SignatureAlgorithm signature_algorithm) {
+  signature_algorithm_ = signature_algorithm;
   Invalidate();
 }
 
@@ -625,8 +750,16 @@
 }
 
 EVP_PKEY* CertBuilder::GetKey() {
-  if (!key_)
-    GenerateKey();
+  if (!key_) {
+    switch (default_pkey_id_) {
+      case EVP_PKEY_RSA:
+        GenerateRSAKey();
+        break;
+      case EVP_PKEY_EC:
+        GenerateECKey();
+        break;
+    }
+  }
   return key_.get();
 }
 
@@ -670,11 +803,16 @@
   cert_.reset();
 }
 
-void CertBuilder::GenerateKey() {
-  ASSERT_FALSE(key_);
+void CertBuilder::GenerateECKey() {
+  auto private_key = crypto::ECPrivateKey::Create();
+  key_ = bssl::UpRef(private_key->key());
+  Invalidate();
+}
 
+void CertBuilder::GenerateRSAKey() {
   auto private_key = crypto::RSAPrivateKey::Create(2048);
   key_ = bssl::UpRef(private_key->key());
+  Invalidate();
 }
 
 void CertBuilder::GenerateSubjectKeyIdentifier() {
@@ -736,7 +874,10 @@
   // signature
   der::Input signature_algorithm_tlv;
   ASSERT_TRUE(tbs_certificate.ReadRawTLV(&signature_algorithm_tlv));
-  signature_algorithm_tlv_ = signature_algorithm_tlv.AsString();
+  auto signature_algorithm =
+      ParseSignatureAlgorithm(signature_algorithm_tlv, nullptr);
+  ASSERT_TRUE(signature_algorithm);
+  signature_algorithm_ = *signature_algorithm;
 
   // issuer
   ASSERT_TRUE(tbs_certificate.SkipTag(der::kSequence));
@@ -748,8 +889,14 @@
 
   // subject
   ASSERT_TRUE(tbs_certificate.SkipTag(der::kSequence));
+
   // subjectPublicKeyInfo
-  ASSERT_TRUE(tbs_certificate.SkipTag(der::kSequence));
+  der::Input spki_tlv;
+  ASSERT_TRUE(tbs_certificate.ReadRawTLV(&spki_tlv));
+  bssl::UniquePtr<EVP_PKEY> public_key;
+  ASSERT_TRUE(ParsePublicKey(spki_tlv, &public_key));
+  default_pkey_id_ = EVP_PKEY_id(public_key.get());
+
   // issuerUniqueID
   ASSERT_TRUE(tbs_certificate.SkipOptionalTag(der::ContextSpecificPrimitive(1),
                                               &unused));
@@ -773,7 +920,8 @@
   }
 }
 
-void CertBuilder::BuildTBSCertificate(std::string* out) {
+void CertBuilder::BuildTBSCertificate(base::StringPiece signature_algorithm_tlv,
+                                      std::string* out) {
   bssl::ScopedCBB cbb;
   CBB tbs_cert, version, extensions_context, extensions;
 
@@ -785,10 +933,11 @@
   // Always use v3 certificates.
   ASSERT_TRUE(CBB_add_asn1_uint64(&version, 2));
   ASSERT_TRUE(CBB_add_asn1_uint64(&tbs_cert, GetSerialNumber()));
-  ASSERT_TRUE(AddSignatureAlgorithm(&tbs_cert));
+  ASSERT_TRUE(CBBAddBytes(&tbs_cert, signature_algorithm_tlv));
   ASSERT_TRUE(CBBAddBytes(&tbs_cert, issuer_->GetSubject()));
   ASSERT_TRUE(CBBAddBytes(&tbs_cert, validity_tlv_));
   ASSERT_TRUE(CBBAddBytes(&tbs_cert, GetSubject()));
+  ASSERT_TRUE(GetKey());
   ASSERT_TRUE(EVP_marshal_public_key(&tbs_cert, GetKey()));
 
   // Serialize all the extensions.
@@ -826,60 +975,33 @@
   *out = FinishCBB(cbb.get());
 }
 
-bool CertBuilder::AddSignatureAlgorithm(CBB* cbb) {
-  return CBBAddBytes(cbb, signature_algorithm_tlv_);
-}
-
 void CertBuilder::GenerateCertificate() {
   ASSERT_FALSE(cert_);
 
+  absl::optional<SignatureAlgorithm> signature_algorithm = signature_algorithm_;
+  if (!signature_algorithm)
+    signature_algorithm = DefaultSignatureAlgorithmForKey(issuer_->GetKey());
+  ASSERT_TRUE(signature_algorithm.has_value());
+
+  std::string signature_algorithm_tlv =
+      SignatureAlgorithmToDer(*signature_algorithm);
+  ASSERT_FALSE(signature_algorithm_tlv.empty());
+
   std::string tbs_cert;
-  BuildTBSCertificate(&tbs_cert);
-  const uint8_t* tbs_cert_bytes =
-      reinterpret_cast<const uint8_t*>(tbs_cert.data());
-
-  // Determine the correct digest algorithm to use (assumes RSA PKCS#1
-  // signatures).
-  auto signature_algorithm =
-      ParseSignatureAlgorithm(der::Input(&signature_algorithm_tlv_), nullptr);
-  ASSERT_TRUE(signature_algorithm);
-  const EVP_MD* md = nullptr;
-  switch (*signature_algorithm) {
-    case SignatureAlgorithm::kRsaPkcs1Sha256:
-      md = EVP_sha256();
-      break;
-
-    case SignatureAlgorithm::kRsaPkcs1Sha1:
-      md = EVP_sha1();
-      break;
-
-    default:
-      ASSERT_TRUE(false) << "Only rsaEncryptionWithSha256 or "
-                            "rsaEncryptionWithSha1 are supported";
-      break;
-  }
+  BuildTBSCertificate(signature_algorithm_tlv, &tbs_cert);
 
   // Sign the TBSCertificate and write the entire certificate.
   bssl::ScopedCBB cbb;
   CBB cert, signature;
-  bssl::ScopedEVP_MD_CTX ctx;
-  uint8_t* sig_out;
-  size_t sig_len;
 
   ASSERT_TRUE(CBB_init(cbb.get(), tbs_cert.size()));
   ASSERT_TRUE(CBB_add_asn1(cbb.get(), &cert, CBS_ASN1_SEQUENCE));
   ASSERT_TRUE(CBBAddBytes(&cert, tbs_cert));
-  ASSERT_TRUE(AddSignatureAlgorithm(&cert));
+  ASSERT_TRUE(CBBAddBytes(&cert, signature_algorithm_tlv));
   ASSERT_TRUE(CBB_add_asn1(&cert, &signature, CBS_ASN1_BITSTRING));
   ASSERT_TRUE(CBB_add_u8(&signature, 0 /* no unused bits */));
   ASSERT_TRUE(
-      EVP_DigestSignInit(ctx.get(), nullptr, md, nullptr, issuer_->GetKey()));
-  ASSERT_TRUE(EVP_DigestSign(ctx.get(), nullptr, &sig_len, tbs_cert_bytes,
-                             tbs_cert.size()));
-  ASSERT_TRUE(CBB_reserve(&signature, &sig_out, sig_len));
-  ASSERT_TRUE(EVP_DigestSign(ctx.get(), sig_out, &sig_len, tbs_cert_bytes,
-                             tbs_cert.size()));
-  ASSERT_TRUE(CBB_did_write(&signature, sig_len));
+      SignData(*signature_algorithm, tbs_cert, issuer_->GetKey(), &signature));
 
   auto cert_der = FinishCBB(cbb.get());
   cert_ =
diff --git a/net/test/cert_builder.h b/net/test/cert_builder.h
index d7cda44..d8e1bdc 100644
--- a/net/test/cert_builder.h
+++ b/net/test/cert_builder.h
@@ -10,13 +10,14 @@
 
 #include "base/memory/raw_ptr.h"
 #include "base/rand_util.h"
-#include "base/strings/string_piece.h"
+#include "base/strings/string_piece_forward.h"
 #include "net/base/ip_address.h"
 #include "net/cert/pki/parse_certificate.h"
 #include "net/cert/pki/signature_algorithm.h"
 #include "net/cert/x509_certificate.h"
 #include "third_party/boringssl/src/include/openssl/base.h"
 #include "third_party/boringssl/src/include/openssl/bytestring.h"
+#include "third_party/boringssl/src/include/openssl/evp.h"
 #include "third_party/boringssl/src/include/openssl/pool.h"
 
 class GURL;
@@ -87,6 +88,22 @@
   static void CreateSimpleChain(std::unique_ptr<CertBuilder>* out_leaf,
                                 std::unique_ptr<CertBuilder>* out_root);
 
+  // Returns a compatible signature algorithm for |key|.
+  static absl::optional<SignatureAlgorithm> DefaultSignatureAlgorithmForKey(
+      EVP_PKEY* key);
+
+  // Signs |tbs_data| with |key| using |signature_algorithm| appending the
+  // signature onto |out_signature| and returns true if successful.
+  static bool SignData(SignatureAlgorithm signature_algorithm,
+                       base::StringPiece tbs_data,
+                       EVP_PKEY* key,
+                       CBB* out_signature);
+
+  // Returns a DER encoded AlgorithmIdentifier TLV for |signature_algorithm|
+  // empty string on error.
+  static std::string SignatureAlgorithmToDer(
+      SignatureAlgorithm signature_algorithm);
+
   // Sets a value for the indicated X.509 (v3) extension.
   void SetExtension(const der::Input& oid,
                     std::string value,
@@ -163,14 +180,25 @@
   // introducing AKI/SKI chain building issues.
   void SetAuthorityKeyIdentifier(const std::string& authority_key_identifier);
 
-  // Sets the signature algorithm for the certificate to either
-  // sha256WithRSAEncryption or sha1WithRSAEncryption.
-  void SetSignatureAlgorithmRsaPkca1(DigestAlgorithm digest);
-
-  void SetSignatureAlgorithm(std::string algorithm_tlv);
+  // Sets the signature algorithm to use in generating the certificate's
+  // signature. The signature algorithm should be compatible with
+  // the type of |issuer_->GetKey()|. If this method is not called, and the
+  // CertBuilder was initialized from a template cert, the signature algorithm
+  // of that cert will be used, or if there was no template cert, a default
+  // algorithm will be used base on the signing key type.
+  void SetSignatureAlgorithm(SignatureAlgorithm signature_algorithm);
 
   void SetRandomSerialNumber();
 
+  // Sets the private key for the generated certificate to an EC key. If a key
+  // was already set, it will be replaced.
+  void GenerateECKey();
+
+  // Sets the private key for the generated certificate to a 2048-bit RSA key.
+  // RSA key generation is expensive, so this should not be used unless an RSA
+  // key is specifically needed. If a key was already set, it will be replaced.
+  void GenerateRSAKey();
+
   // Returns the CertBuilder that issues this certificate. (Will be |this| if
   // certificate is self-signed.)
   CertBuilder* issuer() { return issuer_; }
@@ -197,7 +225,7 @@
   // |not_before| and |not_after|, returning true on success.
   bool GetValidity(base::Time* not_before, base::Time* not_after) const;
 
-  // Returns the (RSA) key for the generated certificate.
+  // Returns the key for the generated certificate.
   EVP_PKEY* GetKey();
 
   // Returns an X509Certificate for the generated certificate.
@@ -230,9 +258,6 @@
   // be re-generated next time the DER is accessed.
   void Invalidate();
 
-  // Sets the |key_| to a 2048-bit RSA key.
-  void GenerateKey();
-
   // Generates a random Subject Key Identifier for the certificate. This is
   // necessary for Windows, which otherwises uses SKI/AKI matching for lookups
   // with greater precedence than subject/issuer name matching, and on newer
@@ -252,9 +277,8 @@
   void InitFromCert(const der::Input& cert);
 
   // Assembles the CertBuilder into a TBSCertificate.
-  void BuildTBSCertificate(std::string* out);
-
-  bool AddSignatureAlgorithm(CBB* cbb);
+  void BuildTBSCertificate(base::StringPiece signature_algorithm_tlv,
+                           std::string* out);
 
   void GenerateCertificate();
 
@@ -265,8 +289,9 @@
 
   std::string validity_tlv_;
   std::string subject_tlv_;
-  std::string signature_algorithm_tlv_;
+  absl::optional<SignatureAlgorithm> signature_algorithm_;
   uint64_t serial_number_ = 0;
+  int default_pkey_id_ = EVP_PKEY_EC;
 
   std::map<std::string, ExtensionValue> extensions_;
 
diff --git a/net/test/revocation_builder.cc b/net/test/revocation_builder.cc
index 090ea56..01f9d3ad 100644
--- a/net/test/revocation_builder.cc
+++ b/net/test/revocation_builder.cc
@@ -11,6 +11,7 @@
 #include "net/cert/x509_util.h"
 #include "net/der/encode_values.h"
 #include "net/der/input.h"
+#include "net/test/cert_builder.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/boringssl/src/include/openssl/bytestring.h"
 #include "third_party/boringssl/src/include/openssl/mem.h"
@@ -19,30 +20,6 @@
 
 namespace {
 
-std::string Sha256WithRSAEncryption() {
-  const uint8_t kSha256WithRSAEncryption[] = {0x30, 0x0D, 0x06, 0x09, 0x2a,
-                                              0x86, 0x48, 0x86, 0xf7, 0x0d,
-                                              0x01, 0x01, 0x0b, 0x05, 0x00};
-  return std::string(std::begin(kSha256WithRSAEncryption),
-                     std::end(kSha256WithRSAEncryption));
-}
-
-std::string Sha1WithRSAEncryption() {
-  const uint8_t kSha1WithRSAEncryption[] = {0x30, 0x0D, 0x06, 0x09, 0x2a,
-                                            0x86, 0x48, 0x86, 0xf7, 0x0d,
-                                            0x01, 0x01, 0x05, 0x05, 0x00};
-  return std::string(std::begin(kSha1WithRSAEncryption),
-                     std::end(kSha1WithRSAEncryption));
-}
-
-std::string Md5WithRSAEncryption() {
-  const uint8_t kMd5WithRSAEncryption[] = {0x30, 0x0d, 0x06, 0x09, 0x2a,
-                                           0x86, 0x48, 0x86, 0xf7, 0x0d,
-                                           0x01, 0x01, 0x04, 0x05, 0x00};
-  return std::string(std::begin(kMd5WithRSAEncryption),
-                     std::end(kMd5WithRSAEncryption));
-}
-
 std::string Sha1() {
   // SEQUENCE { OBJECT_IDENTIFIER { 1.3.14.3.2.26 } }
   const uint8_t kSHA1[] = {0x30, 0x07, 0x06, 0x05, 0x2b,
@@ -343,7 +320,8 @@
 
 std::string BuildOCSPResponseWithResponseData(
     EVP_PKEY* responder_key,
-    const std::string& tbs_response_data) {
+    const std::string& tbs_response_data,
+    absl::optional<SignatureAlgorithm> signature_algorithm) {
   //    For a basic OCSP responder, responseType will be id-pkix-ocsp-basic.
   //
   //    id-pkix-ocsp           OBJECT IDENTIFIER ::= { id-ad-ocsp }
@@ -366,28 +344,29 @@
   //
   bssl::ScopedCBB basic_ocsp_response_cbb;
   CBB basic_ocsp_response, signature;
-  bssl::ScopedEVP_MD_CTX ctx;
-  uint8_t* sig_out;
-  size_t sig_len;
-  if (!CBB_init(basic_ocsp_response_cbb.get(), 64 + tbs_response_data.size()) ||
+  if (!responder_key) {
+    ADD_FAILURE();
+    return std::string();
+  }
+  if (!signature_algorithm)
+    signature_algorithm =
+        CertBuilder::DefaultSignatureAlgorithmForKey(responder_key);
+  if (!signature_algorithm) {
+    ADD_FAILURE();
+    return std::string();
+  }
+  std::string signature_algorithm_tlv =
+      CertBuilder::SignatureAlgorithmToDer(*signature_algorithm);
+  if (signature_algorithm_tlv.empty() ||
+      !CBB_init(basic_ocsp_response_cbb.get(), 64 + tbs_response_data.size()) ||
       !CBB_add_asn1(basic_ocsp_response_cbb.get(), &basic_ocsp_response,
                     CBS_ASN1_SEQUENCE) ||
       !CBBAddBytes(&basic_ocsp_response, tbs_response_data) ||
-      !CBBAddBytes(&basic_ocsp_response, Sha256WithRSAEncryption()) ||
+      !CBBAddBytes(&basic_ocsp_response, signature_algorithm_tlv) ||
       !CBB_add_asn1(&basic_ocsp_response, &signature, CBS_ASN1_BITSTRING) ||
       !CBB_add_u8(&signature, 0 /* no unused bits */) ||
-      !EVP_DigestSignInit(ctx.get(), nullptr, EVP_sha256(), nullptr,
-                          responder_key) ||
-      !EVP_DigestSign(
-          ctx.get(), nullptr, &sig_len,
-          reinterpret_cast<const uint8_t*>(tbs_response_data.data()),
-          tbs_response_data.size()) ||
-      !CBB_reserve(&signature, &sig_out, sig_len) ||
-      !EVP_DigestSign(
-          ctx.get(), sig_out, &sig_len,
-          reinterpret_cast<const uint8_t*>(tbs_response_data.data()),
-          tbs_response_data.size()) ||
-      !CBB_did_write(&signature, sig_len)) {
+      !CertBuilder::SignData(*signature_algorithm, tbs_response_data,
+                             responder_key, &signature)) {
     ADD_FAILURE();
     return std::string();
   }
@@ -402,31 +381,23 @@
 std::string BuildCrl(const std::string& crl_issuer_subject,
                      EVP_PKEY* crl_issuer_key,
                      const std::vector<uint64_t>& revoked_serials,
-                     DigestAlgorithm digest) {
-  std::string signature_algorithm;
-  const EVP_MD* md = nullptr;
-  switch (digest) {
-    case DigestAlgorithm::Sha256: {
-      signature_algorithm = Sha256WithRSAEncryption();
-      md = EVP_sha256();
-      break;
-    }
-
-    case DigestAlgorithm::Sha1: {
-      signature_algorithm = Sha1WithRSAEncryption();
-      md = EVP_sha1();
-      break;
-    }
-
-    case DigestAlgorithm::Md5: {
-      signature_algorithm = Md5WithRSAEncryption();
-      md = EVP_md5();
-      break;
-    }
-
-    default:
-      ADD_FAILURE();
-      return std::string();
+                     absl::optional<SignatureAlgorithm> signature_algorithm) {
+  if (!crl_issuer_key) {
+    ADD_FAILURE();
+    return std::string();
+  }
+  if (!signature_algorithm)
+    signature_algorithm =
+        CertBuilder::DefaultSignatureAlgorithmForKey(crl_issuer_key);
+  if (!signature_algorithm) {
+    ADD_FAILURE();
+    return std::string();
+  }
+  std::string signature_algorithm_tlv =
+      CertBuilder::SignatureAlgorithmToDer(*signature_algorithm);
+  if (signature_algorithm_tlv.empty()) {
+    ADD_FAILURE();
+    return std::string();
   }
   //    TBSCertList  ::=  SEQUENCE  {
   //         version                 Version OPTIONAL,
@@ -449,7 +420,7 @@
   if (!CBB_init(tbs_cbb.get(), 10) ||
       !CBB_add_asn1(tbs_cbb.get(), &tbs_cert_list, CBS_ASN1_SEQUENCE) ||
       !CBB_add_asn1_uint64(&tbs_cert_list, 1 /* V2 */) ||
-      !CBBAddBytes(&tbs_cert_list, signature_algorithm) ||
+      !CBBAddBytes(&tbs_cert_list, signature_algorithm_tlv) ||
       !CBBAddBytes(&tbs_cert_list, crl_issuer_subject) ||
       !x509_util::CBBAddTime(&tbs_cert_list,
                              base::Time::Now() - base::Days(1)) ||
@@ -486,24 +457,14 @@
   //         signatureValue       BIT STRING  }
   bssl::ScopedCBB crl_cbb;
   CBB cert_list, signature;
-  bssl::ScopedEVP_MD_CTX ctx;
-  uint8_t* sig_out;
-  size_t sig_len;
   if (!CBB_init(crl_cbb.get(), 10) ||
       !CBB_add_asn1(crl_cbb.get(), &cert_list, CBS_ASN1_SEQUENCE) ||
       !CBBAddBytes(&cert_list, tbs_tlv) ||
-      !CBBAddBytes(&cert_list, signature_algorithm) ||
+      !CBBAddBytes(&cert_list, signature_algorithm_tlv) ||
       !CBB_add_asn1(&cert_list, &signature, CBS_ASN1_BITSTRING) ||
       !CBB_add_u8(&signature, 0 /* no unused bits */) ||
-      !EVP_DigestSignInit(ctx.get(), nullptr, md, nullptr, crl_issuer_key) ||
-      !EVP_DigestSign(ctx.get(), nullptr, &sig_len,
-                      reinterpret_cast<const uint8_t*>(tbs_tlv.data()),
-                      tbs_tlv.size()) ||
-      !CBB_reserve(&signature, &sig_out, sig_len) ||
-      !EVP_DigestSign(ctx.get(), sig_out, &sig_len,
-                      reinterpret_cast<const uint8_t*>(tbs_tlv.data()),
-                      tbs_tlv.size()) ||
-      !CBB_did_write(&signature, sig_len)) {
+      !CertBuilder::SignData(*signature_algorithm, tbs_tlv, crl_issuer_key,
+                             &signature)) {
     ADD_FAILURE();
     return std::string();
   }
diff --git a/net/test/revocation_builder.h b/net/test/revocation_builder.h
index b011a35..e98d475 100644
--- a/net/test/revocation_builder.h
+++ b/net/test/revocation_builder.h
@@ -11,6 +11,8 @@
 #include "base/time/time.h"
 #include "net/cert/ocsp_revocation_status.h"
 #include "net/cert/pki/ocsp.h"
+#include "net/cert/pki/signature_algorithm.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/boringssl/src/include/openssl/evp.h"
 
 namespace net {
@@ -45,18 +47,23 @@
     base::Time produced_at,
     const std::vector<OCSPBuilderSingleResponse>& responses);
 
-// Creates an OCSPResponse signed by |responder_key| with |tbs_response_data| as
-// the to-be-signed ResponseData.
-std::string BuildOCSPResponseWithResponseData(EVP_PKEY* responder_key,
-                                              const std::string& response_data);
+// Creates an OCSPResponse signed by |responder_key| with |tbs_response_data|
+// as the to-be-signed ResponseData. If |signature_algorithm| is nullopt, a
+// default algorithm will be chosen based on the key type.
+std::string BuildOCSPResponseWithResponseData(
+    EVP_PKEY* responder_key,
+    const std::string& response_data,
+    absl::optional<SignatureAlgorithm> signature_algorithm = absl::nullopt);
 
 // Creates a CRL issued by |crl_issuer_subject| and signed by |crl_issuer_key|,
-// marking |revoked_serials| as revoked.
+// marking |revoked_serials| as revoked. If |signature_algorithm| is nullopt, a
+// default algorithm will be chosen based on the key type.
 // Returns the DER-encoded CRL.
-std::string BuildCrl(const std::string& crl_issuer_subject,
-                     EVP_PKEY* crl_issuer_key,
-                     const std::vector<uint64_t>& revoked_serials,
-                     DigestAlgorithm digest);
+std::string BuildCrl(
+    const std::string& crl_issuer_subject,
+    EVP_PKEY* crl_issuer_key,
+    const std::vector<uint64_t>& revoked_serials,
+    absl::optional<SignatureAlgorithm> signature_algorithm = absl::nullopt);
 
 }  // namespace net
 
diff --git a/sandbox/policy/mac/common.sb b/sandbox/policy/mac/common.sb
index 3cf7b61..9c6638f 100644
--- a/sandbox/policy/mac/common.sb
+++ b/sandbox/policy/mac/common.sb
@@ -198,6 +198,16 @@
   (path "/System/Library/CoreServices/checkfixlist")
 )
 
+; From /System/Library/Sandbox/Profiles/dyld-support.sb on macOS 13.
+(if (>= os-version 1300)
+  (allow file-read* file-map-executable
+    (subpath "/System/Cryptexes/App")
+    (subpath "/System/Cryptexes/OS")
+    (subpath "/System/Volumes/Preboot/Cryptexes/App/System")
+    (subpath "/System/Volumes/Preboot/Cryptexes/OS")
+  )
+)
+
 ; Reads from /usr.
 (allow file-read-data
   (subpath "/usr/share/icu")
diff --git a/storage/browser/file_system/file_system_operation_runner.h b/storage/browser/file_system/file_system_operation_runner.h
index 8e9eaa5..f07f5ea 100644
--- a/storage/browser/file_system/file_system_operation_runner.h
+++ b/storage/browser/file_system/file_system_operation_runner.h
@@ -304,7 +304,7 @@
   void FinishOperation(OperationID id);
 
   // Not owned; whatever owns this has to make sure context outlives this.
-  raw_ptr<FileSystemContext, DanglingUntriaged> file_system_context_;
+  raw_ptr<FileSystemContext> file_system_context_;
 
   using Operations =
       std::map<OperationID, std::unique_ptr<FileSystemOperation>>;
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 84c22e00..4405a71 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -76622,7 +76622,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "absl_hardening_tests",
@@ -76652,7 +76652,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -76674,7 +76674,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "base_unittests",
@@ -76704,7 +76704,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -76726,7 +76726,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "base_unittests",
@@ -76756,7 +76756,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -76778,7 +76778,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "base_unittests",
@@ -76808,7 +76808,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -76830,7 +76830,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "boringssl_crypto_tests",
@@ -76860,7 +76860,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -76882,7 +76882,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "boringssl_ssl_tests",
@@ -76912,7 +76912,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -76934,7 +76934,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "components_unittests",
@@ -76964,7 +76964,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -76986,7 +76986,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "components_unittests",
@@ -77016,7 +77016,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -77038,7 +77038,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "components_unittests",
@@ -77068,7 +77068,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -77090,7 +77090,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "crashpad_tests",
@@ -77120,7 +77120,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -77142,7 +77142,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "crypto_unittests",
@@ -77172,7 +77172,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -77194,7 +77194,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "gfx_unittests",
@@ -77224,7 +77224,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -77246,7 +77246,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "gfx_unittests",
@@ -77276,7 +77276,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -77298,7 +77298,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "gfx_unittests",
@@ -77328,7 +77328,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -77350,7 +77350,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "google_apis_unittests",
@@ -77380,7 +77380,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -77402,7 +77402,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -77433,7 +77433,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -77455,7 +77455,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -77486,7 +77486,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -77508,7 +77508,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -77539,7 +77539,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -77561,7 +77561,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -77592,7 +77592,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -77614,7 +77614,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -77645,7 +77645,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -77668,7 +77668,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -77699,7 +77699,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -77722,7 +77722,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -77753,7 +77753,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -77776,7 +77776,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -77807,7 +77807,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -77830,7 +77830,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -77861,7 +77861,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -77884,7 +77884,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -77915,7 +77915,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -77938,7 +77938,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -77969,7 +77969,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -77992,7 +77992,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -78023,7 +78023,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -78046,7 +78046,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -78077,7 +78077,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -78100,7 +78100,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -78131,7 +78131,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -78154,7 +78154,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -78185,7 +78185,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -78207,7 +78207,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -78238,7 +78238,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -78260,7 +78260,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -78291,7 +78291,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -78313,7 +78313,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -78344,7 +78344,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -78366,7 +78366,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -78397,7 +78397,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -78420,7 +78420,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -78451,7 +78451,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -78474,7 +78474,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -78505,7 +78505,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -78528,7 +78528,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "ios_chrome_unittests",
@@ -78558,7 +78558,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -78580,7 +78580,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "ios_chrome_unittests",
@@ -78610,7 +78610,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -78632,7 +78632,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "ios_chrome_unittests",
@@ -78662,7 +78662,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -78684,7 +78684,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -78715,7 +78715,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -78737,7 +78737,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -78768,7 +78768,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -78790,7 +78790,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -78821,7 +78821,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -78843,7 +78843,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -78874,7 +78874,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -78896,7 +78896,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "ios_components_unittests",
@@ -78926,7 +78926,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -78948,7 +78948,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -78979,7 +78979,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -79001,7 +79001,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "ios_net_unittests",
@@ -79031,7 +79031,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -79054,7 +79054,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "ios_remoting_unittests",
@@ -79084,7 +79084,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -79106,7 +79106,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -79137,7 +79137,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -79159,7 +79159,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -79190,7 +79190,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -79212,7 +79212,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -79243,7 +79243,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -79265,7 +79265,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -79296,7 +79296,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -79318,7 +79318,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "ios_testing_unittests",
@@ -79348,7 +79348,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -79370,7 +79370,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "ios_web_inttests",
@@ -79400,7 +79400,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -79422,7 +79422,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "ios_web_inttests",
@@ -79452,7 +79452,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -79474,7 +79474,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "ios_web_inttests",
@@ -79504,7 +79504,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -79526,7 +79526,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -79557,7 +79557,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -79579,7 +79579,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -79610,7 +79610,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -79632,7 +79632,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest",
           "--xcode-parallelization"
         ],
@@ -79663,7 +79663,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -79685,7 +79685,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "ios_web_unittests",
@@ -79715,7 +79715,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -79737,7 +79737,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "ios_web_unittests",
@@ -79767,7 +79767,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -79789,7 +79789,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "ios_web_unittests",
@@ -79819,7 +79819,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -79841,7 +79841,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "ios_web_view_inttests",
@@ -79871,7 +79871,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -79893,7 +79893,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "ios_web_view_inttests",
@@ -79923,7 +79923,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -79945,7 +79945,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "ios_web_view_inttests",
@@ -79975,7 +79975,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -79997,7 +79997,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "ios_web_view_unittests",
@@ -80027,7 +80027,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -80049,7 +80049,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "ios_web_view_unittests",
@@ -80079,7 +80079,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -80101,7 +80101,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "ios_web_view_unittests",
@@ -80131,7 +80131,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -80153,7 +80153,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "net_unittests",
@@ -80183,7 +80183,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -80205,7 +80205,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "services_unittests",
@@ -80235,7 +80235,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -80257,7 +80257,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "skia_unittests",
@@ -80287,7 +80287,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -80309,7 +80309,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "skia_unittests",
@@ -80339,7 +80339,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -80361,7 +80361,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "skia_unittests",
@@ -80391,7 +80391,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -80413,7 +80413,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "sql_unittests",
@@ -80443,7 +80443,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -80465,7 +80465,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "ui_base_unittests",
@@ -80495,7 +80495,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -80517,7 +80517,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "ui_base_unittests",
@@ -80547,7 +80547,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -80569,7 +80569,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "ui_base_unittests",
@@ -80599,7 +80599,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
@@ -80621,7 +80621,7 @@
           "--out-dir",
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
-          "14a5284g",
+          "14a5294e",
           "--xctest"
         ],
         "isolate_name": "url_unittests",
@@ -80651,7 +80651,7 @@
           ],
           "named_caches": [
             {
-              "name": "xcode_ios_14a5284g",
+              "name": "xcode_ios_14a5294e",
               "path": "Xcode.app"
             },
             {
diff --git a/testing/buildbot/gn_isolate_map.pyl b/testing/buildbot/gn_isolate_map.pyl
index 5d01837..8dac0d61 100644
--- a/testing/buildbot/gn_isolate_map.pyl
+++ b/testing/buildbot/gn_isolate_map.pyl
@@ -332,7 +332,7 @@
   },
   "cast_runner_pkg":{
     "label": "//fuchsia_web/runners:cast_runner_pkg",
-    "type": "additonal_compile_target",
+    "type": "additional_compile_target",
   },
   "cast_runner_unittests": {
     "label": "//fuchsia_web/runners:cast_runner_unittests",
@@ -2003,7 +2003,7 @@
   },
   "web_runner_pkg": {
     "label": "//fuchsia_web/runners:web_runner_pkg",
-    "type": "additonal_compile_target",
+    "type": "additional_compile_target",
   },
   "webapk_client_junit_tests": {
     "label": "//chrome/android/webapk/libs/client:webapk_client_junit_tests",
diff --git a/testing/buildbot/mixins.pyl b/testing/buildbot/mixins.pyl
index cc04210..b83c659 100644
--- a/testing/buildbot/mixins.pyl
+++ b/testing/buildbot/mixins.pyl
@@ -1370,23 +1370,6 @@
     '$mixin_append': {
       'args': [
         '--xcode-build-version',
-        '14a5284g'
-      ],
-    },
-    'swarming': {
-      'named_caches': [
-        {
-          'name': 'xcode_ios_14a5284g',
-          'path': 'Xcode.app',
-        },
-      ],
-    },
-  },
-  # Xcode 14 beta 5.
-  'xcode_14_beta_5': {
-    '$mixin_append': {
-      'args': [
-        '--xcode-build-version',
         '14a5294e'
       ],
     },
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index d55ad97..cc58025 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -3454,7 +3454,7 @@
           'mac_beta_x64',
           'mac_toolchain',
           'out_dir_arg',
-          'xcode_14_beta_5',
+          'xcode_14_beta',
           # crbug/1343123: temporarily increasing readline timeout due to slow simulator start up time.
           'xcode_14_readline_timeout',
           'xctest',
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index 172cdd4..c199054 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -756,13 +756,6 @@
 const base::Feature kOffsetParentNewSpecBehavior{
     "OffsetParentNewSpecBehavior", base::FEATURE_DISABLED_BY_DEFAULT};
 
-// Makes form elements cancel previous form submissions made by the same form
-// when the default event handler schedules a form submission.
-// TODO(crbug.com/1234409): Remove this flag when this feature has been in
-// stable for a release with no issues
-const base::Feature kCancelFormSubmissionInDefaultHandler{
-    "CancelFormSubmissionInDefaultHandler", base::FEATURE_ENABLED_BY_DEFAULT};
-
 // Enables the JPEG XL Image File Format (JXL).
 const base::Feature kJXL{"JXL", base::FEATURE_DISABLED_BY_DEFAULT};
 
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index 210090334..8fb70b3 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -290,9 +290,6 @@
 BLINK_COMMON_EXPORT extern const base::Feature kOffsetParentNewSpecBehavior;
 
 BLINK_COMMON_EXPORT extern const base::Feature
-    kCancelFormSubmissionInDefaultHandler;
-
-BLINK_COMMON_EXPORT extern const base::Feature
     kAlignFontDisplayAutoTimeoutWithLCPGoal;
 BLINK_COMMON_EXPORT extern const base::FeatureParam<int>
     kAlignFontDisplayAutoTimeoutWithLCPGoalTimeoutParam;
diff --git a/third_party/blink/renderer/core/css/check_pseudo_has_argument_context.cc b/third_party/blink/renderer/core/css/check_pseudo_has_argument_context.cc
index 26c444c..99ce343 100644
--- a/third_party/blink/renderer/core/css/check_pseudo_has_argument_context.cc
+++ b/third_party/blink/renderer/core/css/check_pseudo_has_argument_context.cc
@@ -95,6 +95,8 @@
     }
   }
   DCHECK_NE(leftmost_relation_, CSSSelector::kSubSelector);
+  DCHECK_LE(adjacent_distance_limit_, kInfiniteAdjacentDistance);
+  DCHECK_LE(depth_limit_, kInfiniteDepth);
 
   switch (leftmost_relation_) {
     case CSSSelector::kRelativeDescendant:
diff --git a/third_party/blink/renderer/core/css/check_pseudo_has_argument_context.h b/third_party/blink/renderer/core/css/check_pseudo_has_argument_context.h
index c64733c..0b4ac8f 100644
--- a/third_party/blink/renderer/core/css/check_pseudo_has_argument_context.h
+++ b/third_party/blink/renderer/core/css/check_pseudo_has_argument_context.h
@@ -58,8 +58,19 @@
   //            (.e.g. :has(~ .a > .b), :has(+ .a ~ .b > .c),
   //                   :has(~ .a > .b ~ .c), :has(+ .a ~ .b > .c ~ .d),
   kAllNextSiblingsFixedDepthDescendants,
+
+  kTraversalScopeMax = kAllNextSiblingsFixedDepthDescendants,
 };
 
+// Unique value of each traversal type. The value can be used as a key of
+// fast reject filter cache.
+//
+// These 3 values are stored by dividing the 4-byte field by:
+// - depth limit : 0 ~ 13 (14bits)
+// - adjacent distance limit : 14 ~ 27 (14 bits)
+// - traversal scope : 28 ~ 31 (4 bits)
+using CheckPseudoHasArgumentTraversalType = uint32_t;
+
 class CORE_EXPORT CheckPseudoHasArgumentContext {
   STACK_ALLOCATED();
 
@@ -98,9 +109,24 @@
     return pseudo_has_argument_hashes_;
   }
 
+  CheckPseudoHasArgumentTraversalType TraversalType() const {
+    return depth_limit_ | (adjacent_distance_limit_ << kDepthBits) |
+           (traversal_scope_ << (kDepthBits + kAdjacentBits));
+  }
+
  private:
-  const static int kInfiniteDepth = std::numeric_limits<int>::max();
-  const static int kInfiniteAdjacentDistance = std::numeric_limits<int>::max();
+  const static size_t kDepthBits = 14;
+  const static size_t kAdjacentBits = 14;
+  const static size_t kTraversalScopeBits = 4;
+
+  const static int kInfiniteDepth = (1 << kDepthBits) - 1;
+  const static int kInfiniteAdjacentDistance = (1 << kAdjacentBits) - 1;
+
+  static_assert(kTraversalScopeMax <= ((1 << kTraversalScopeBits) - 1),
+                "traversal scope size check");
+  static_assert((kDepthBits + kAdjacentBits + kTraversalScopeBits) <=
+                    sizeof(CheckPseudoHasArgumentTraversalType) * 8,
+                "traversal type size check");
 
   inline const CSSSelector* GetCurrentRelationAndNextCompound(
       const CSSSelector* compound_selector,
@@ -236,6 +262,8 @@
   const CSSSelector* has_argument_;
 
   Vector<unsigned> pseudo_has_argument_hashes_;
+
+  friend class CheckPseudoHasArgumentContextTest;
 };
 
 // Subtree traversal iterator class for :has() argument checking. To solve the
diff --git a/third_party/blink/renderer/core/css/check_pseudo_has_argument_context_test.cc b/third_party/blink/renderer/core/css/check_pseudo_has_argument_context_test.cc
index 3201bc3b..86580441 100644
--- a/third_party/blink/renderer/core/css/check_pseudo_has_argument_context_test.cc
+++ b/third_party/blink/renderer/core/css/check_pseudo_has_argument_context_test.cc
@@ -16,7 +16,9 @@
 
 class CheckPseudoHasArgumentContextTest : public PageTestBase {
  protected:
-  const int kMax = std::numeric_limits<int>::max();
+  const int kDepthMax = CheckPseudoHasArgumentContext::kInfiniteDepth;
+  const int kAdjacentMax =
+      CheckPseudoHasArgumentContext::kInfiniteAdjacentDistance;
 
   void TestArgumentContext(
       const String& selector_text,
@@ -77,6 +79,18 @@
     }
   }
 
+  CheckPseudoHasArgumentTraversalType GetTraversalType(
+      const char* selector_text) const {
+    CSSSelectorList selector_list =
+        css_test_helpers::ParseSelectorList(selector_text);
+
+    EXPECT_EQ(selector_list.First()->GetPseudoType(), CSSSelector::kPseudoHas);
+
+    CheckPseudoHasArgumentContext context(
+        selector_list.First()->SelectorList()->First());
+    return context.TraversalType();
+  }
+
   template <unsigned length>
   void TestTraversalIteratorSteps(
       Document* document,
@@ -142,28 +156,28 @@
 TEST_F(CheckPseudoHasArgumentContextTest, TestArgumentMatchContext) {
   TestArgumentContext(":has(.a)", CSSSelector::kRelativeDescendant,
                       /* expected_adjacent_distance_limit */ 0,
-                      /* expected_depth_limit */ kMax, kSubtree);
+                      /* expected_depth_limit */ kDepthMax, kSubtree);
   TestArgumentContext(":has(.a ~ .b)", CSSSelector::kRelativeDescendant,
                       /* expected_adjacent_distance_limit */ 0,
-                      /* expected_depth_limit */ kMax, kSubtree);
+                      /* expected_depth_limit */ kDepthMax, kSubtree);
   TestArgumentContext(":has(.a ~ .b > .c)", CSSSelector::kRelativeDescendant,
                       /* expected_adjacent_distance_limit */ 0,
-                      /* expected_depth_limit */ kMax, kSubtree);
+                      /* expected_depth_limit */ kDepthMax, kSubtree);
   TestArgumentContext(":has(.a > .b)", CSSSelector::kRelativeDescendant,
                       /* expected_adjacent_distance_limit */ 0,
-                      /* expected_depth_limit */ kMax, kSubtree);
+                      /* expected_depth_limit */ kDepthMax, kSubtree);
   TestArgumentContext(":has(.a + .b)", CSSSelector::kRelativeDescendant,
                       /* expected_adjacent_distance_limit */ 0,
-                      /* expected_depth_limit */ kMax, kSubtree);
+                      /* expected_depth_limit */ kDepthMax, kSubtree);
   TestArgumentContext(":has(> .a .b)", CSSSelector::kRelativeChild,
                       /* expected_adjacent_distance_limit */ 0,
-                      /* expected_depth_limit */ kMax, kSubtree);
+                      /* expected_depth_limit */ kDepthMax, kSubtree);
   TestArgumentContext(":has(> .a ~ .b .c)", CSSSelector::kRelativeChild,
                       /* expected_adjacent_distance_limit */ 0,
-                      /* expected_depth_limit */ kMax, kSubtree);
+                      /* expected_depth_limit */ kDepthMax, kSubtree);
   TestArgumentContext(":has(> .a + .b .c)", CSSSelector::kRelativeChild,
                       /* expected_adjacent_distance_limit */ 0,
-                      /* expected_depth_limit */ kMax, kSubtree);
+                      /* expected_depth_limit */ kDepthMax, kSubtree);
   TestArgumentContext(":has(> .a)", CSSSelector::kRelativeChild,
                       /* expected_adjacent_distance_limit */ 0,
                       /* expected_depth_limit */ 1, kFixedDepthDescendants);
@@ -180,83 +194,85 @@
                       /* expected_adjacent_distance_limit */ 0,
                       /* expected_depth_limit */ 2, kFixedDepthDescendants);
   TestArgumentContext(":has(~ .a .b)", CSSSelector::kRelativeIndirectAdjacent,
-                      /* expected_adjacent_distance_limit */ kMax,
-                      /* expected_depth_limit */ kMax, kAllNextSiblingSubtrees);
-  TestArgumentContext(":has(~ .a + .b > .c ~ .d .e)",
-                      CSSSelector::kRelativeIndirectAdjacent,
-                      /* expected_adjacent_distance_limit */ kMax,
-                      /* expected_depth_limit */ kMax, kAllNextSiblingSubtrees);
+                      /* expected_adjacent_distance_limit */ kAdjacentMax,
+                      /* expected_depth_limit */ kDepthMax,
+                      kAllNextSiblingSubtrees);
+  TestArgumentContext(
+      ":has(~ .a + .b > .c ~ .d .e)", CSSSelector::kRelativeIndirectAdjacent,
+      /* expected_adjacent_distance_limit */ kAdjacentMax,
+      /* expected_depth_limit */ kDepthMax, kAllNextSiblingSubtrees);
   TestArgumentContext(":has(~ .a)", CSSSelector::kRelativeIndirectAdjacent,
-                      /* expected_adjacent_distance_limit */ kMax,
+                      /* expected_adjacent_distance_limit */ kAdjacentMax,
                       /* expected_depth_limit */ 0, kAllNextSiblings);
   TestArgumentContext(":has(~ .a ~ .b)", CSSSelector::kRelativeIndirectAdjacent,
-                      /* expected_adjacent_distance_limit */ kMax,
+                      /* expected_adjacent_distance_limit */ kAdjacentMax,
                       /* expected_depth_limit */ 0, kAllNextSiblings);
   TestArgumentContext(":has(~ .a + .b)", CSSSelector::kRelativeIndirectAdjacent,
-                      /* expected_adjacent_distance_limit */ kMax,
+                      /* expected_adjacent_distance_limit */ kAdjacentMax,
                       /* expected_depth_limit */ 0, kAllNextSiblings);
   TestArgumentContext(":has(~ .a + .b ~ .c)",
                       CSSSelector::kRelativeIndirectAdjacent,
-                      /* expected_adjacent_distance_limit */ kMax,
+                      /* expected_adjacent_distance_limit */ kAdjacentMax,
                       /* expected_depth_limit */ 0, kAllNextSiblings);
   TestArgumentContext(":has(~ .a > .b)", CSSSelector::kRelativeIndirectAdjacent,
-                      /* expected_adjacent_distance_limit */ kMax,
+                      /* expected_adjacent_distance_limit */ kAdjacentMax,
                       /* expected_depth_limit */ 1,
                       kAllNextSiblingsFixedDepthDescendants);
   TestArgumentContext(
       ":has(~ .a + .b > .c ~ .d > .e)", CSSSelector::kRelativeIndirectAdjacent,
-      /* expected_adjacent_distance_limit */ kMax,
+      /* expected_adjacent_distance_limit */ kAdjacentMax,
       /* expected_depth_limit */ 2, kAllNextSiblingsFixedDepthDescendants);
-  TestArgumentContext(":has(+ .a ~ .b .c)",
-                      CSSSelector::kRelativeDirectAdjacent,
-                      /* expected_adjacent_distance_limit */ kMax,
-                      /* expected_depth_limit */ kMax, kAllNextSiblingSubtrees);
-  TestArgumentContext(":has(+ .a ~ .b > .c + .d .e)",
-                      CSSSelector::kRelativeDirectAdjacent,
-                      /* expected_adjacent_distance_limit */ kMax,
-                      /* expected_depth_limit */ kMax, kAllNextSiblingSubtrees);
+  TestArgumentContext(
+      ":has(+ .a ~ .b .c)", CSSSelector::kRelativeDirectAdjacent,
+      /* expected_adjacent_distance_limit */ kAdjacentMax,
+      /* expected_depth_limit */ kDepthMax, kAllNextSiblingSubtrees);
+  TestArgumentContext(
+      ":has(+ .a ~ .b > .c + .d .e)", CSSSelector::kRelativeDirectAdjacent,
+      /* expected_adjacent_distance_limit */ kAdjacentMax,
+      /* expected_depth_limit */ kDepthMax, kAllNextSiblingSubtrees);
   TestArgumentContext(":has(+ .a ~ .b)", CSSSelector::kRelativeDirectAdjacent,
-                      /* expected_adjacent_distance_limit */ kMax,
+                      /* expected_adjacent_distance_limit */ kAdjacentMax,
                       /* expected_depth_limit */ 0, kAllNextSiblings);
   TestArgumentContext(":has(+ .a + .b ~ .c)",
                       CSSSelector::kRelativeDirectAdjacent,
-                      /* expected_adjacent_distance_limit */ kMax,
+                      /* expected_adjacent_distance_limit */ kAdjacentMax,
                       /* expected_depth_limit */ 0, kAllNextSiblings);
   TestArgumentContext(
       ":has(+ .a ~ .b > .c)", CSSSelector::kRelativeDirectAdjacent,
-      /* expected_adjacent_distance_limit */ kMax,
+      /* expected_adjacent_distance_limit */ kAdjacentMax,
       /* expected_depth_limit */ 1, kAllNextSiblingsFixedDepthDescendants);
   TestArgumentContext(
       ":has(+ .a ~ .b > .c + .d > .e)", CSSSelector::kRelativeDirectAdjacent,
-      /* expected_adjacent_distance_limit */ kMax,
+      /* expected_adjacent_distance_limit */ kAdjacentMax,
       /* expected_depth_limit */ 2, kAllNextSiblingsFixedDepthDescendants);
   TestArgumentContext(":has(+ .a .b)", CSSSelector::kRelativeDirectAdjacent,
                       /* expected_adjacent_distance_limit */ 1,
-                      /* expected_depth_limit */ kMax, kOneNextSiblingSubtree);
-  TestArgumentContext(":has(+ .a > .b .c)",
-                      CSSSelector::kRelativeDirectAdjacent,
-                      /* expected_adjacent_distance_limit */ 1,
-                      /* expected_depth_limit */ kMax, kOneNextSiblingSubtree);
-  TestArgumentContext(":has(+ .a .b > .c)",
-                      CSSSelector::kRelativeDirectAdjacent,
-                      /* expected_adjacent_distance_limit */ 1,
-                      /* expected_depth_limit */ kMax, kOneNextSiblingSubtree);
-  TestArgumentContext(":has(+ .a .b ~ .c)",
-                      CSSSelector::kRelativeDirectAdjacent,
-                      /* expected_adjacent_distance_limit */ 1,
-                      /* expected_depth_limit */ kMax, kOneNextSiblingSubtree);
-  TestArgumentContext(":has(+ .a + .b .c)",
-                      CSSSelector::kRelativeDirectAdjacent,
-                      /* expected_adjacent_distance_limit */ 2,
-                      /* expected_depth_limit */ kMax, kOneNextSiblingSubtree);
-  TestArgumentContext(":has(+ .a > .b + .c .d)",
-                      CSSSelector::kRelativeDirectAdjacent,
-                      /* expected_adjacent_distance_limit */ 1,
-                      /* expected_depth_limit */ kMax, kOneNextSiblingSubtree);
-  TestArgumentContext(":has(+ .a + .b > .c .d)",
-                      CSSSelector::kRelativeDirectAdjacent,
-                      /* expected_adjacent_distance_limit */ 2,
-                      /* expected_depth_limit */ kMax, kOneNextSiblingSubtree);
+                      /* expected_depth_limit */ kDepthMax,
+                      kOneNextSiblingSubtree);
+  TestArgumentContext(
+      ":has(+ .a > .b .c)", CSSSelector::kRelativeDirectAdjacent,
+      /* expected_adjacent_distance_limit */ 1,
+      /* expected_depth_limit */ kDepthMax, kOneNextSiblingSubtree);
+  TestArgumentContext(
+      ":has(+ .a .b > .c)", CSSSelector::kRelativeDirectAdjacent,
+      /* expected_adjacent_distance_limit */ 1,
+      /* expected_depth_limit */ kDepthMax, kOneNextSiblingSubtree);
+  TestArgumentContext(
+      ":has(+ .a .b ~ .c)", CSSSelector::kRelativeDirectAdjacent,
+      /* expected_adjacent_distance_limit */ 1,
+      /* expected_depth_limit */ kDepthMax, kOneNextSiblingSubtree);
+  TestArgumentContext(
+      ":has(+ .a + .b .c)", CSSSelector::kRelativeDirectAdjacent,
+      /* expected_adjacent_distance_limit */ 2,
+      /* expected_depth_limit */ kDepthMax, kOneNextSiblingSubtree);
+  TestArgumentContext(
+      ":has(+ .a > .b + .c .d)", CSSSelector::kRelativeDirectAdjacent,
+      /* expected_adjacent_distance_limit */ 1,
+      /* expected_depth_limit */ kDepthMax, kOneNextSiblingSubtree);
+  TestArgumentContext(
+      ":has(+ .a + .b > .c .d)", CSSSelector::kRelativeDirectAdjacent,
+      /* expected_adjacent_distance_limit */ 2,
+      /* expected_depth_limit */ kDepthMax, kOneNextSiblingSubtree);
   TestArgumentContext(":has(+ .a)", CSSSelector::kRelativeDirectAdjacent,
                       /* expected_adjacent_distance_limit */ 1,
                       /* expected_depth_limit */ 0, kOneNextSibling);
@@ -281,6 +297,122 @@
       /* expected_depth_limit */ 2, kOneNextSiblingFixedDepthDescendants);
 }
 
+TEST_F(CheckPseudoHasArgumentContextTest, TestTraversalType) {
+  CheckPseudoHasArgumentTraversalType traversal_type;
+
+  // traversal scope: kSubtree
+  // adjacent distance: 0
+  // depth: Max
+  traversal_type = GetTraversalType(":has(.a)");
+  EXPECT_EQ(traversal_type, 0x00003fffu);
+  EXPECT_EQ(GetTraversalType(":has(.a ~ .b)"), traversal_type);
+  EXPECT_EQ(GetTraversalType(":has(.a ~ .b > .c)"), traversal_type);
+  EXPECT_EQ(GetTraversalType(":has(.a > .b)"), traversal_type);
+  EXPECT_EQ(GetTraversalType(":has(.a + .b)"), traversal_type);
+  EXPECT_EQ(GetTraversalType(":has(> .a .b)"), traversal_type);
+  EXPECT_EQ(GetTraversalType(":has(> .a ~ .b .c)"), traversal_type);
+  EXPECT_EQ(GetTraversalType(":has(> .a + .b .c)"), traversal_type);
+
+  // traversal scope: kAllNextSiblings
+  // adjacent distance: Max
+  // depth: 0
+  traversal_type = GetTraversalType(":has(~ .a)");
+  EXPECT_EQ(traversal_type, 0x1fffc000u);
+  EXPECT_EQ(GetTraversalType(":has(~ .a ~ .b)"), traversal_type);
+  EXPECT_EQ(GetTraversalType(":has(~ .a + .b)"), traversal_type);
+  EXPECT_EQ(GetTraversalType(":has(~ .a + .b ~ .c)"), traversal_type);
+  EXPECT_EQ(GetTraversalType(":has(+ .a ~ .b)"), traversal_type);
+  EXPECT_EQ(GetTraversalType(":has(+ .a + .b ~ .c)"), traversal_type);
+
+  // traversal scope: kOneNextSiblingSubtree
+  // adjacent distance: 1
+  // depth: Max
+  traversal_type = GetTraversalType(":has(+ .a .b)");
+  EXPECT_EQ(traversal_type, 0x20007fffu);
+  EXPECT_EQ(GetTraversalType(":has(+ .a > .b .c)"), traversal_type);
+  EXPECT_EQ(GetTraversalType(":has(+ .a .b > .c)"), traversal_type);
+  EXPECT_EQ(GetTraversalType(":has(+ .a .b ~ .c)"), traversal_type);
+  EXPECT_EQ(GetTraversalType(":has(+ .a > .b + .c .d)"), traversal_type);
+
+  // traversal scope: kOneNextSiblingSubtree
+  // adjacent distance: 2
+  // depth: Max
+  traversal_type = GetTraversalType(":has(+ .a + .b .c)");
+  EXPECT_EQ(traversal_type, 0x2000bfffu);
+  EXPECT_EQ(GetTraversalType(":has(+ .a + .b > .c .d)"), traversal_type);
+
+  // traversal scope: kAllNextSiblingSubtrees
+  // adjacent distance: Max
+  // depth: Max
+  traversal_type = GetTraversalType(":has(~ .a .b)");
+  EXPECT_EQ(traversal_type, 0x3fffffffu);
+  EXPECT_EQ(GetTraversalType(":has(~ .a + .b > .c ~ .d .e)"), traversal_type);
+  EXPECT_EQ(GetTraversalType(":has(+ .a ~ .b .c)"), traversal_type);
+  EXPECT_EQ(GetTraversalType(":has(+ .a ~ .b > .c + .d .e)"), traversal_type);
+
+  // traversal scope: kOneNextSibling
+  // adjacent distance: 1
+  // depth: 0
+  traversal_type = GetTraversalType(":has(+ .a)");
+  EXPECT_EQ(traversal_type, 0x40004000u);
+
+  // traversal scope: kOneNextSibling
+  // adjacent distance: 2
+  // depth: 0
+  traversal_type = GetTraversalType(":has(+ .a + .b)");
+  EXPECT_EQ(traversal_type, 0x40008000u);
+
+  // traversal scope: kOneNextSibling
+  // adjacent distance: 3
+  // depth: 0
+  traversal_type = GetTraversalType(":has(+ .a + .b + .c)");
+  EXPECT_EQ(traversal_type, 0x4000c000u);
+
+  // traversal scope: kFixedDepthDescendants
+  // adjacent distance: 0
+  // depth: 1
+  traversal_type = GetTraversalType(":has(> .a)");
+  EXPECT_EQ(traversal_type, 0x50000001u);
+  EXPECT_EQ(GetTraversalType(":has(> .a + .b)"), traversal_type);
+  EXPECT_EQ(GetTraversalType(":has(> .a ~ .b)"), traversal_type);
+
+  // traversal scope: kFixedDepthDescendants
+  // adjacent distance: 0
+  // depth: 2
+  traversal_type = GetTraversalType(":has(> .a > .b)");
+  EXPECT_EQ(traversal_type, 0x50000002u);
+  EXPECT_EQ(GetTraversalType(":has(> .a ~ .b > .c)"), traversal_type);
+
+  // traversal scope: kOneNextSiblingFixedDepthDescendants
+  // adjacent distance: 1
+  // depth: 1
+  traversal_type = GetTraversalType(":has(+ .a > .b)");
+  EXPECT_EQ(traversal_type, 0x60004001u);
+  EXPECT_EQ(GetTraversalType(":has(+ .a > .b ~ .c)"), traversal_type);
+
+  // traversal scope: kOneNextSiblingFixedDepthDescendants
+  // adjacent distance: 2
+  // depth: 2
+  traversal_type = GetTraversalType(":has(+ .a + .b > .c ~ .d > .e)");
+  EXPECT_EQ(traversal_type, 0x60008002u);
+  EXPECT_EQ(GetTraversalType(":has(+ .a + .b > .c ~ .d > .e ~ .f)"),
+            traversal_type);
+
+  // traversal scope: kAllNextSiblingsFixedDepthDescendants
+  // adjacent distance: Max
+  // depth: 1
+  traversal_type = GetTraversalType(":has(~ .a > .b)");
+  EXPECT_EQ(traversal_type, 0x7fffc001u);
+  EXPECT_EQ(GetTraversalType(":has(+ .a ~ .b > .c)"), traversal_type);
+
+  // traversal scope: kAllNextSiblingsFixedDepthDescendants
+  // adjacent distance: Max
+  // depth: 2
+  traversal_type = GetTraversalType(":has(~ .a > .b > .c)");
+  EXPECT_EQ(traversal_type, 0x7fffc002u);
+  EXPECT_EQ(GetTraversalType(":has(+ .a ~ .b > .c + .d > .e)"), traversal_type);
+}
+
 TEST_F(CheckPseudoHasArgumentContextTest, TestTraversalIteratorCase1) {
   // CheckPseudoHasArgumentTraversalScope::kSubtree
 
diff --git a/third_party/blink/renderer/core/css/check_pseudo_has_cache_scope.cc b/third_party/blink/renderer/core/css/check_pseudo_has_cache_scope.cc
index d28d77ad..b4df6a13 100644
--- a/third_party/blink/renderer/core/css/check_pseudo_has_cache_scope.cc
+++ b/third_party/blink/renderer/core/css/check_pseudo_has_cache_scope.cc
@@ -52,6 +52,25 @@
   return *entry.stored_value->value;
 }
 
+// static
+ElementCheckPseudoHasFastRejectFilterMap&
+CheckPseudoHasCacheScope::GetFastRejectFilterMap(
+    const Document* document,
+    CheckPseudoHasArgumentTraversalType traversal_type) {
+  DCHECK(document);
+  DCHECK(document->GetCheckPseudoHasCacheScope());
+
+  auto entry = document->GetCheckPseudoHasCacheScope()
+                   ->GetFastRejectFilterCache()
+                   .insert(traversal_type, nullptr);
+  if (entry.is_new_entry) {
+    entry.stored_value->value =
+        MakeGarbageCollected<ElementCheckPseudoHasFastRejectFilterMap>();
+  }
+  DCHECK(entry.stored_value->value);
+  return *entry.stored_value->value;
+}
+
 CheckPseudoHasCacheScope::Context::Context(
     const Document* document,
     const CheckPseudoHasArgumentContext& argument_context)
@@ -64,6 +83,9 @@
       cache_allowed_ = true;
       result_map_ = &CheckPseudoHasCacheScope::GetResultMap(
           document, argument_context.HasArgument());
+      fast_reject_filter_map_ =
+          &CheckPseudoHasCacheScope::GetFastRejectFilterMap(
+              document, argument_context.TraversalType());
       break;
     default:
       cache_allowed_ = false;
@@ -225,4 +247,21 @@
   return false;
 }
 
+CheckPseudoHasFastRejectFilter&
+CheckPseudoHasCacheScope::Context::EnsureFastRejectFilter(Element* element,
+                                                          bool& is_new_entry) {
+  DCHECK(element);
+  DCHECK(cache_allowed_);
+  DCHECK(fast_reject_filter_map_);
+
+  auto entry = fast_reject_filter_map_->insert(element, nullptr);
+  is_new_entry = entry.is_new_entry;
+  if (entry.is_new_entry) {
+    entry.stored_value->value =
+        std::make_unique<CheckPseudoHasFastRejectFilter>();
+  }
+  DCHECK(entry.stored_value->value);
+  return *entry.stored_value->value.get();
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/check_pseudo_has_cache_scope.h b/third_party/blink/renderer/core/css/check_pseudo_has_cache_scope.h
index fdb32852..7a0e72b63 100644
--- a/third_party/blink/renderer/core/css/check_pseudo_has_cache_scope.h
+++ b/third_party/blink/renderer/core/css/check_pseudo_has_cache_scope.h
@@ -6,6 +6,8 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_CHECK_PSEUDO_HAS_CACHE_SCOPE_H_
 
 #include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/css/check_pseudo_has_argument_context.h"
+#include "third_party/blink/renderer/core/css/check_pseudo_has_fast_reject_filter.h"
 #include "third_party/blink/renderer/core/dom/element.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h"
 
@@ -141,7 +143,7 @@
 // because we can get the result of ':has(.a)' from the cache with the cache
 // key '.a'.
 //
-// The :has() checking result cache uses 2 dimensional hash map to store the
+// The :has() checking result cache uses a 2 dimensional hash map to store the
 // result.
 // - hashmap[<argument-selector>][<element>] = <result>
 //
@@ -153,12 +155,39 @@
 using CheckPseudoHasResultCache =
     HeapHashMap<String, Member<ElementCheckPseudoHasResultMap>>;
 
-// CheckPseudoHasCacheScope is the stack-allocated scoping class for :has()
-// pseudo class checking result cache.
+// The :has() result cache keeps a bloom filter for rejecting :has() argument
+// selector checking.
 //
-// This class has hashmap to hold the checking result, so the lifecycle of the
-// cache follows the lifecycle of the CheckPseudoHasCacheScope instance.
-// (The hashmap for caching will be created at the construction of a
+// The element identifier hashes in the bloom filter depend on the relationship
+// between the :has() anchor element and the :has() argument subject element.
+// The relationship can be categorized by this information in
+// CheckPseudoHasArgumentContext.
+// - traversal scope
+// - adjacent limit
+// - depth limit
+// (Please refer the comment of CheckPseudoHasArgumentTraversalType)
+//
+// The CheckPseudoHasFastRejectFilterCache uses a 2 dimensional hash map to
+// store the filter.
+// - hashmap[<traversal type>][<element>] = <filter>
+//
+// ElementCheckPseudoHasFastRejectFilterMap is a hash map that stores the
+// filter for each element.
+// - hashmap[<element>] = <filter>
+using ElementCheckPseudoHasFastRejectFilterMap =
+    HeapHashMap<Member<const Element>,
+                std::unique_ptr<CheckPseudoHasFastRejectFilter>>;
+using CheckPseudoHasFastRejectFilterCache =
+    HeapHashMap<CheckPseudoHasArgumentTraversalType,
+                Member<ElementCheckPseudoHasFastRejectFilterMap>>;
+
+// CheckPseudoHasCacheScope is the stack-allocated scoping class for :has()
+// pseudo class checking result cache and :has() pseudo class checking fast
+// reject filter cache.
+//
+// This class has hashmap to hold the checking result and filter, so the
+// lifecycle of the caches follow the lifecycle of the CheckPseudoHasCacheScope
+// instance. (The hashmap for caching will be created at the construction of a
 // CheckPseudoHasCacheScope instance, and removed at the destruction of the
 // instance)
 //
@@ -198,8 +227,10 @@
   explicit CheckPseudoHasCacheScope(Document*);
   ~CheckPseudoHasCacheScope();
 
-  // Context provides getter and setter of the cached :has()
-  // pseudo class checking result in ElementCheckPseudoHasResultMap.
+  // Context provides getter and setter of the following cache items.
+  // - :has() pseudo class checking result in ElementCheckPseudoHasResultMap
+  // - :has() pseudo class checking fast reject filter in
+  //   ElementCheckPseudoHasFastRejectFilterMap.
   class CORE_EXPORT Context {
     STACK_ALLOCATED();
 
@@ -218,6 +249,9 @@
 
     bool AlreadyChecked(Element*) const;
 
+    CheckPseudoHasFastRejectFilter& EnsureFastRejectFilter(Element*,
+                                                           bool& is_new_entry);
+
     inline bool CacheAllowed() const { return cache_allowed_; }
 
    private:
@@ -231,20 +265,35 @@
     bool HasSiblingsWithAllDescendantsOrNextSiblingsChecked(Element*) const;
     bool HasAncestorsWithAllDescendantsOrNextSiblingsChecked(Element*) const;
 
-    size_t GetCacheCount() { return cache_allowed_ ? result_map_->size() : 0; }
+    size_t GetResultCacheCountForTesting() const {
+      return cache_allowed_ ? result_map_->size() : 0;
+    }
+
+    size_t GetFastRejectFilterCacheCountForTesting() const {
+      return cache_allowed_ ? fast_reject_filter_map_->size() : 0;
+    }
 
     bool cache_allowed_;
     ElementCheckPseudoHasResultMap* result_map_;
+    ElementCheckPseudoHasFastRejectFilterMap* fast_reject_filter_map_;
     const CheckPseudoHasArgumentContext& argument_context_;
   };
 
  private:
   static ElementCheckPseudoHasResultMap& GetResultMap(const Document*,
                                                       const CSSSelector*);
+  static ElementCheckPseudoHasFastRejectFilterMap& GetFastRejectFilterMap(
+      const Document*,
+      CheckPseudoHasArgumentTraversalType);
 
   CheckPseudoHasResultCache& GetResultCache() { return result_cache_; }
 
+  CheckPseudoHasFastRejectFilterCache& GetFastRejectFilterCache() {
+    return fast_reject_filter_cache_;
+  }
+
   CheckPseudoHasResultCache result_cache_;
+  CheckPseudoHasFastRejectFilterCache fast_reject_filter_cache_;
 
   Document* document_;
 };
diff --git a/third_party/blink/renderer/core/css/check_pseudo_has_cache_scope_context_test.cc b/third_party/blink/renderer/core/css/check_pseudo_has_cache_scope_context_test.cc
index 74581b4..0d1a543 100644
--- a/third_party/blink/renderer/core/css/check_pseudo_has_cache_scope_context_test.cc
+++ b/third_party/blink/renderer/core/css/check_pseudo_has_cache_scope_context_test.cc
@@ -95,12 +95,13 @@
   }
 
   template <unsigned length>
-  void CheckCacheResults(Document* document,
-                         String query_name,
-                         const char* selector_text,
-                         unsigned expected_result_cache_count,
-                         const ExpectedResultCacheEntry (
-                             &expected_result_cache_entries)[length]) const {
+  void CheckCacheResults(
+      Document* document,
+      String query_name,
+      const char* selector_text,
+      unsigned expected_result_cache_count,
+      const ExpectedResultCacheEntry (&expected_result_cache_entries)[length],
+      unsigned expected_fast_reject_filter_cache_count) const {
     CSSSelectorVector selector_vector = CSSParser::ParseSelector(
         MakeGarbageCollected<CSSParserContext>(
             *document, NullURL(), true /* origin_clean */, Referrer(),
@@ -124,7 +125,8 @@
     CheckPseudoHasCacheScope::Context cache_scope_context(document,
                                                           argument_context);
 
-    EXPECT_EQ(expected_result_cache_count, cache_scope_context.GetCacheCount())
+    EXPECT_EQ(expected_result_cache_count,
+              cache_scope_context.GetResultCacheCountForTesting())
         << "Failed : " << query_name;
 
     for (ExpectedResultCacheEntry expected_result_cache_entry :
@@ -161,6 +163,10 @@
           break;
       }
     }
+
+    EXPECT_EQ(expected_fast_reject_filter_cache_count,
+              cache_scope_context.GetFastRejectFilterCacheCountForTesting())
+        << "Failed : " << query_name;
   }
 
   template <unsigned cache_size>
@@ -170,7 +176,8 @@
                    bool expected_match_result,
                    unsigned expected_result_cache_count,
                    const ExpectedResultCacheEntry (
-                       &expected_result_cache_entries)[cache_size]) const {
+                       &expected_result_cache_entries)[cache_size],
+                   unsigned expected_fast_reject_filter_cache_count) const {
     Element* query_scope_element =
         document->getElementById(query_scope_element_id);
     ASSERT_TRUE(query_scope_element);
@@ -184,9 +191,9 @@
               query_scope_element->matches(selector_text))
         << "Failed : " << query_name;
 
-    CheckCacheResults(document, query_name, selector_text,
-                      expected_result_cache_count,
-                      expected_result_cache_entries);
+    CheckCacheResults(
+        document, query_name, selector_text, expected_result_cache_count,
+        expected_result_cache_entries, expected_fast_reject_filter_cache_count);
   }
 
   template <unsigned query_result_size, unsigned cache_size>
@@ -197,7 +204,8 @@
       const String (&expected_results)[query_result_size],
       unsigned expected_result_cache_count,
       const ExpectedResultCacheEntry (
-          &expected_result_cache_entries)[cache_size]) const {
+          &expected_result_cache_entries)[cache_size],
+      unsigned expected_fast_reject_filter_cache_count) const {
     Element* query_scope_element =
         document->getElementById(query_scope_element_id);
     ASSERT_TRUE(query_scope_element);
@@ -220,9 +228,9 @@
           << "Failed :" << query_name << " result at index " << i;
     }
 
-    CheckCacheResults(document, query_name, selector_text,
-                      expected_result_cache_count,
-                      expected_result_cache_entries);
+    CheckCacheResults(
+        document, query_name, selector_text, expected_result_cache_count,
+        expected_result_cache_entries, expected_fast_reject_filter_cache_count);
   }
 };
 
@@ -302,7 +310,8 @@
                {"#div3", kNotCached, kNotYetChecked},
                {"#div31", kNotCached, kNotYetChecked},
                {"#div4", kNotCached, kNotYetChecked},
-               {"#div41", kNotCached, kNotYetChecked}});
+               {"#div41", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 
   TestMatches(document, "div2", ":has(.b)",
               /* expected_match_result */ true,
@@ -333,7 +342,8 @@
                {"#div3", kNotCached, kNotYetChecked},
                {"#div31", kNotCached, kNotYetChecked},
                {"#div4", kNotCached, kNotYetChecked},
-               {"#div41", kNotCached, kNotYetChecked}});
+               {"#div41", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 
   TestMatches(document, "div2", ":has(.c)",
               /* expected_match_result */ false,
@@ -363,7 +373,8 @@
                {"#div3", kNotCached, kNotYetChecked},
                {"#div31", kNotCached, kNotYetChecked},
                {"#div4", kNotCached, kNotYetChecked},
-               {"#div41", kNotCached, kNotYetChecked}});
+               {"#div41", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 }
 
 TEST_F(CheckPseudoHasCacheScopeContextTest, Case1StartsWithChildCombinator) {
@@ -477,7 +488,8 @@
                {"#div231", kNotCached, kNotYetChecked},
                {"#div24", kNotCached, kNotYetChecked},
                {"#div3", kNotCached, kNotYetChecked},
-               {"#div31", kNotCached, kNotYetChecked}});
+               {"#div31", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 
   TestMatches(document, "div2", ":has(> .a .b)",
               /* expected_match_result */ false,
@@ -522,7 +534,8 @@
                {"#div231", kNotCached, kAlreadyNotMatched},
                {"#div24", kNotCached, kAlreadyNotMatched},
                {"#div3", kNotCached, kNotYetChecked},
-               {"#div31", kNotCached, kNotYetChecked}});
+               {"#div31", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 
   TestMatches(document, "div2", ":has(> .a .c)",
               /* expected_match_result */ false,
@@ -567,7 +580,8 @@
                {"#div231", kNotCached, kAlreadyNotMatched},
                {"#div24", kNotCached, kAlreadyNotMatched},
                {"#div3", kNotCached, kNotYetChecked},
-               {"#div31", kNotCached, kNotYetChecked}});
+               {"#div31", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 }
 
 TEST_F(CheckPseudoHasCacheScopeContextTest, Case2StartsWithIndirectAdjacent) {
@@ -632,7 +646,8 @@
                {"#div251", kNotCached, kNotYetChecked},
                {"#div252", kNotCached, kNotYetChecked},
                {"#div3", kNotCached, kNotYetChecked},
-               {"#div31", kNotCached, kNotYetChecked}});
+               {"#div31", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 
   TestMatches(document, "div22", ":has(~ .b)",
               /* expected_match_result */ false,
@@ -658,7 +673,8 @@
                {"#div251", kNotCached, kNotYetChecked},
                {"#div252", kNotCached, kNotYetChecked},
                {"#div3", kNotCached, kNotYetChecked},
-               {"#div31", kNotCached, kNotYetChecked}});
+               {"#div31", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 }
 
 TEST_F(CheckPseudoHasCacheScopeContextTest, Case2StartsWithDirectAdjacent) {
@@ -756,7 +772,8 @@
                {"#div3", kNotCached, kNotYetChecked},
                {"#div31", kNotCached, kNotYetChecked},
                {"#div4", kNotCached, kNotYetChecked},
-               {"#div41", kNotCached, kNotYetChecked}});
+               {"#div41", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 
   TestMatches(document, "div22", ":has(+ .a ~ .b)",
               /* expected_match_result */ false,
@@ -797,7 +814,8 @@
                {"#div3", kNotCached, kNotYetChecked},
                {"#div31", kNotCached, kNotYetChecked},
                {"#div4", kNotCached, kNotYetChecked},
-               {"#div41", kNotCached, kNotYetChecked}});
+               {"#div41", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 
   TestMatches(document, "div22", ":has(+ .a ~ .c)",
               /* expected_match_result */ false,
@@ -838,7 +856,8 @@
                {"#div3", kNotCached, kNotYetChecked},
                {"#div31", kNotCached, kNotYetChecked},
                {"#div4", kNotCached, kNotYetChecked},
-               {"#div41", kNotCached, kNotYetChecked}});
+               {"#div41", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 }
 
 TEST_F(CheckPseudoHasCacheScopeContextTest, Case3) {
@@ -945,7 +964,8 @@
                {"#div241", kNotCached, kNotYetChecked},
                {"#div25", kNotCached, kNotYetChecked},
                {"#div3", kNotCached, kNotYetChecked},
-               {"#div4", kNotCached, kNotYetChecked}});
+               {"#div4", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 
   TestMatches(document, "div1", ":has(+ .a .b)",
               /* expected_match_result */ false,
@@ -988,7 +1008,8 @@
                {"#div241", kNotCached, kAlreadyNotMatched},
                {"#div25", kNotCached, kAlreadyNotMatched},
                {"#div3", kNotCached, kNotYetChecked},
-               {"#div4", kNotCached, kNotYetChecked}});
+               {"#div4", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 
   TestMatches(document, "div22", ":has(+ .a .c)",
               /* expected_match_result */ false,
@@ -1031,7 +1052,8 @@
                {"#div241", kNotCached, kNotYetChecked},
                {"#div25", kNotCached, kNotYetChecked},
                {"#div3", kNotCached, kNotYetChecked},
-               {"#div4", kNotCached, kNotYetChecked}});
+               {"#div4", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 }
 
 TEST_F(CheckPseudoHasCacheScopeContextTest, Case4) {
@@ -1151,7 +1173,8 @@
                {"#div3", kNotCached, kNotYetChecked},
                {"#div31", kNotCached, kNotYetChecked},
                {"#div4", kNotCached, kNotYetChecked},
-               {"#div41", kNotCached, kNotYetChecked}});
+               {"#div41", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 
   TestMatches(document, "div21", ":has(~ .a .b)",
               /* expected_match_result */ true,
@@ -1201,7 +1224,8 @@
                {"#div3", kNotCached, kNotYetChecked},
                {"#div31", kNotCached, kNotYetChecked},
                {"#div4", kNotCached, kNotYetChecked},
-               {"#div41", kNotCached, kNotYetChecked}});
+               {"#div41", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 
   TestMatches(document, "div1", ":has(~ .a .b)",
               /* expected_match_result */ false,
@@ -1249,7 +1273,8 @@
                {"#div3", kNotCached, kAlreadyNotMatched},
                {"#div31", kNotCached, kAlreadyNotMatched},
                {"#div4", kNotCached, kAlreadyNotMatched},
-               {"#div41", kNotCached, kAlreadyNotMatched}});
+               {"#div41", kNotCached, kAlreadyNotMatched}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 
   TestMatches(document, "div22", ":has(~ .a .c)",
               /* expected_match_result */ false,
@@ -1297,7 +1322,8 @@
                {"#div3", kNotCached, kNotYetChecked},
                {"#div31", kNotCached, kNotYetChecked},
                {"#div4", kNotCached, kNotYetChecked},
-               {"#div41", kNotCached, kNotYetChecked}});
+               {"#div41", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 }
 
 TEST_F(CheckPseudoHasCacheScopeContextTest,
@@ -1341,12 +1367,14 @@
                {"#div131", kNotMatchedAndAllDescendantsOrNextSiblingsChecked,
                 kSameAsCached},
                {"#div14", kNotMatchedAndAllDescendantsOrNextSiblingsChecked,
-                kSameAsCached}});
+                kSameAsCached}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 
   TestMatches(document, "div11", ":has(.a .b)",
               /* expected_match_result */ false,
               /* expected_result_cache_count */ 1,
-              {{"#div11", kNotMatched, kSameAsCached}});
+              {{"#div11", kNotMatched, kSameAsCached}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 
   TestMatches(document, "div12", ":has(.a .b)",
               /* expected_match_result */ true,
@@ -1363,7 +1391,8 @@
                 kSameAsCached},
                {"#div13", kNotCached, kNotYetChecked},
                {"#div131", kNotCached, kNotYetChecked},
-               {"#div14", kNotCached, kNotYetChecked}});
+               {"#div14", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 
   // ':has(.a .b)' does not match #div1211 but this caches possibly matched
   // elements because argument selector checking can cross over the :has()
@@ -1383,7 +1412,8 @@
                 kSameAsCached},
                {"#div13", kNotCached, kNotYetChecked},
                {"#div131", kNotCached, kNotYetChecked},
-               {"#div14", kNotCached, kNotYetChecked}});
+               {"#div14", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 
   // ':has(.a .b)' does not match #div13 but this caches possibly matched
   // elements because argument selector checking can cross over the :has()
@@ -1403,7 +1433,8 @@
                {"#div13", kNotMatchedAndSomeChildrenChecked, kSameAsCached},
                {"#div131", kNotMatchedAndAllDescendantsOrNextSiblingsChecked,
                 kSameAsCached},
-               {"#div14", kNotCached, kNotYetChecked}});
+               {"#div14", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 
   TestQuerySelectorAll(
       document, "main", ":has(.a .b)", {"div1", "div12", "div121"},
@@ -1422,7 +1453,8 @@
        {"#div131", kNotMatchedAndAllDescendantsOrNextSiblingsChecked,
         kSameAsCached},
        {"#div14", kNotMatchedAndAllDescendantsOrNextSiblingsChecked,
-        kSameAsCached}});
+        kSameAsCached}},
+      /* expected_fast_reject_filter_cache_count */ 5);
 }
 
 TEST_F(CheckPseudoHasCacheScopeContextTest,
@@ -1453,7 +1485,8 @@
                {"#div111", kMatched, kSameAsCached},
                {"#div1111", kNotCheckedAndSomeChildrenChecked, kNotYetChecked},
                {"#div11111", kNotMatchedAndAllDescendantsOrNextSiblingsChecked,
-                kSameAsCached}});
+                kSameAsCached}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 
   TestMatches(document, "div11", ":has(> .a .b)",
               /* expected_match_result */ false,
@@ -1463,7 +1496,8 @@
                {"#div111", kMatchedAndAllDescendantsOrNextSiblingsChecked,
                 kSameAsCached},
                {"#div1111", kNotCached, kAlreadyNotMatched},
-               {"#div11111", kNotCached, kAlreadyNotMatched}});
+               {"#div11111", kNotCached, kAlreadyNotMatched}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 
   TestQuerySelectorAll(
       document, "main", ":has(> .a .b)", {"div1", "div111"},
@@ -1474,7 +1508,8 @@
         kSameAsCached},
        {"#div1111", kNotMatchedAndSomeChildrenChecked, kSameAsCached},
        {"#div11111", kNotMatchedAndAllDescendantsOrNextSiblingsChecked,
-        kSameAsCached}});
+        kSameAsCached}},
+      /* expected_fast_reject_filter_cache_count */ 2);
 }
 
 TEST_F(CheckPseudoHasCacheScopeContextTest,
@@ -1515,7 +1550,8 @@
               /* expected_result_cache_count */ 2,
               {{"#div112", kNotMatchedAndSomeChildrenChecked, kSameAsCached},
                {"#div1121", kNotMatchedAndAllDescendantsOrNextSiblingsChecked,
-                kSameAsCached}});
+                kSameAsCached}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 
   TestMatches(document, "div111", ":has(> .a .b)",
               /* expected_match_result */ true,
@@ -1525,7 +1561,8 @@
                {"#div11111", kNotMatchedAndAllDescendantsOrNextSiblingsChecked,
                 kSameAsCached},
                {"#div1112", kNotMatchedAndAllDescendantsOrNextSiblingsChecked,
-                kSameAsCached}});
+                kSameAsCached}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 
   TestMatches(document, "div11", ":has(> .a .b)",
               /* expected_match_result */ true,
@@ -1541,7 +1578,8 @@
                 kSameAsCached},
                {"#div1121", kNotCached, kAlreadyNotMatched},
                {"#div113", kNotCached, kAlreadyNotMatched},
-               {"#div1131", kNotCached, kAlreadyNotMatched}});
+               {"#div1131", kNotCached, kAlreadyNotMatched}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 
   TestMatches(document, "div1", ":has(> .a .b)",
               /* expected_match_result */ false,
@@ -1558,7 +1596,8 @@
                {"#div113", kNotCached, kAlreadyNotMatched},
                {"#div1131", kNotCached, kAlreadyNotMatched},
                {"#div12", kNotCached, kAlreadyNotMatched},
-               {"#div121", kNotCached, kAlreadyNotMatched}});
+               {"#div121", kNotCached, kAlreadyNotMatched}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 
   TestQuerySelectorAll(
       document, "main", ":has(> .a .b) ~ .c .d", {"div1131", "div121"},
@@ -1583,7 +1622,250 @@
        {"#div12", kNotCached, kAlreadyNotMatched},
        {"#div121", kNotCached, kAlreadyNotMatched},
        {"#div2", kNotCached, kNotYetChecked},
-       {"#div21", kNotCached, kNotYetChecked}});
+       {"#div21", kNotCached, kNotYetChecked}},
+      /* expected_fast_reject_filter_cache_count */ 4);
+}
+
+TEST_F(CheckPseudoHasCacheScopeContextTest,
+       QuerySelectorAllCase2NonSubjectHas) {
+  // CheckPseudoHasArgumentTraversalScope::kAllNextSiblings
+
+  auto* document = HTMLDocument::CreateForTest();
+  document->write(R"HTML(
+    <!DOCTYPE html>
+    <main id=main>
+      <div id=div1>
+        <div id=div11 class=a>
+          <div id=div111>
+            <div id=div1111 class=b></div>
+          </div>
+          <div id=div112 class=a></div>
+        </div>
+        <div id=div12>
+          <div id=div121>
+            <div id=div1211 class=b></div>
+          </div>
+          <div id=div122></div>
+        </div>
+        <div id=div13></div>
+      </div>
+      <div id=div2 class=a></div>
+    </main>
+  )HTML");
+
+  TestMatches(document, "div1111", ":has(~ .a) .b",
+              /* expected_match_result */ true,
+              /* expected_result_cache_count */ 3,
+              {{"main", kNotCached, kNotYetChecked},
+               {"#div1", kNotCached, kNotYetChecked},
+               {"#div11", kNotCheckedAndSomeChildrenChecked, kNotYetChecked},
+               {"#div111", kMatched, kSameAsCached},
+               {"#div1111", kNotCached, kNotYetChecked},
+               {"#div112", kNotMatchedAndAllDescendantsOrNextSiblingsChecked,
+                kSameAsCached},
+               {"#div12", kNotCached, kNotYetChecked},
+               {"#div121", kNotCached, kNotYetChecked},
+               {"#div1211", kNotCached, kNotYetChecked},
+               {"#div122", kNotCached, kNotYetChecked},
+               {"#div13", kNotCached, kNotYetChecked},
+               {"#div2", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 1);
+
+  TestMatches(document, "div1211", ":has(~ .a) .b",
+              /* expected_match_result */ true,
+              /* expected_result_cache_count */ 7,
+              {{"main", kNotCheckedAndSomeChildrenChecked, kNotYetChecked},
+               {"#div1", kMatchedAndSomeChildrenChecked, kSameAsCached},
+               {"#div11", kNotCached, kNotYetChecked},
+               {"#div111", kNotCached, kNotYetChecked},
+               {"#div1111", kNotCached, kNotYetChecked},
+               {"#div112", kNotCached, kNotYetChecked},
+               {"#div12", kNotMatchedAndSomeChildrenChecked, kSameAsCached},
+               {"#div121", kNotMatched, kSameAsCached},
+               {"#div1211", kNotCached, kNotYetChecked},
+               {"#div122", kNotMatchedAndAllDescendantsOrNextSiblingsChecked,
+                kSameAsCached},
+               {"#div13", kNotMatchedAndAllDescendantsOrNextSiblingsChecked,
+                kSameAsCached},
+               {"#div2", kNotMatchedAndAllDescendantsOrNextSiblingsChecked,
+                kSameAsCached}},
+              /* expected_fast_reject_filter_cache_count */ 3);
+
+  TestQuerySelectorAll(
+      document, "main", ":has(~ .a) .b", {"div1111", "div1211"},
+      /* expected_result_cache_count */ 10,
+      {{"main", kNotCheckedAndSomeChildrenChecked, kNotYetChecked},
+       {"#div1", kMatchedAndSomeChildrenChecked, kSameAsCached},
+       {"#div11", kNotCheckedAndSomeChildrenChecked, kNotYetChecked},
+       {"#div111", kMatched, kSameAsCached},
+       {"#div1111", kNotCached, kNotYetChecked},
+       {"#div112", kNotMatchedAndAllDescendantsOrNextSiblingsChecked,
+        kSameAsCached},
+       {"#div12", kNotMatchedAndSomeChildrenChecked, kSameAsCached},
+       {"#div121", kNotMatched, kSameAsCached},
+       {"#div1211", kNotCached, kNotYetChecked},
+       {"#div122", kNotMatchedAndAllDescendantsOrNextSiblingsChecked,
+        kSameAsCached},
+       {"#div13", kNotMatchedAndAllDescendantsOrNextSiblingsChecked,
+        kSameAsCached},
+       {"#div2", kNotMatchedAndAllDescendantsOrNextSiblingsChecked,
+        kSameAsCached}},
+      /* expected_fast_reject_filter_cache_count */ 4);
+}
+
+TEST_F(CheckPseudoHasCacheScopeContextTest,
+       QuerySelectorAllCase3NonSubjectHas) {
+  // CheckPseudoHasArgumentTraversalScope::kOneNextSiblingSubtree
+
+  auto* document = HTMLDocument::CreateForTest();
+  document->write(R"HTML(
+    <!DOCTYPE html>
+    <main id=main>
+      <div id=div1>
+        <div id=div11 class=c></div>
+      </div>
+      <div id=div2 class=a>
+        <div id=div21>
+          <div id=div211 class=c></div>
+        </div>
+        <div id=div22 class=a>
+          <div id=div221 class=b></div>
+        </div>
+        <div id=div23>
+          <div id=div231 class=b></div>
+        </div>
+      </div>
+    </main>
+  )HTML");
+
+  TestMatches(document, "div11", ":has(+ .a .b) .c",
+              /* expected_match_result */ true,
+              /* expected_result_cache_count */ 3,
+              {{"main", kNotCached, kNotYetChecked},
+               {"#div1", kMatched, kSameAsCached},
+               {"#div11", kNotCached, kNotYetChecked},
+               {"#div2", kNotCached, kNotYetChecked},
+               {"#div21", kNotCached, kNotYetChecked},
+               {"#div211", kNotCached, kNotYetChecked},
+               {"#div22", kNotCached, kNotYetChecked},
+               {"#div221", kNotCached, kNotYetChecked},
+               {"#div23", kNotCheckedAndSomeChildrenChecked, kNotYetChecked},
+               {"#div231", kNotMatchedAndAllDescendantsOrNextSiblingsChecked,
+                kSameAsCached}},
+              /* expected_fast_reject_filter_cache_count */ 1);
+
+  TestMatches(document, "div211", ":has(+ .a .b) .c",
+              /* expected_match_result */ true,
+              /* expected_result_cache_count */ 3,
+              {{"main", kNotCached, kNotYetChecked},
+               {"#div1", kNotCached, kNotYetChecked},
+               {"#div11", kNotCached, kNotYetChecked},
+               {"#div2", kNotCached, kNotYetChecked},
+               {"#div21", kMatched, kSameAsCached},
+               {"#div211", kNotCached, kNotYetChecked},
+               {"#div22", kNotCheckedAndSomeChildrenChecked, kNotYetChecked},
+               {"#div221", kNotMatchedAndAllDescendantsOrNextSiblingsChecked,
+                kSameAsCached},
+               {"#div23", kNotCached, kNotYetChecked},
+               {"#div231", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 1);
+
+  TestQuerySelectorAll(
+      document, "main", ":has(+ .a .b) .c", {"div11", "div211"},
+      /* expected_result_cache_count */ 6,
+      {{"main", kNotCached, kNotYetChecked},
+       {"#div1", kMatched, kSameAsCached},
+       {"#div11", kNotCached, kNotYetChecked},
+       {"#div2", kNotCached, kNotYetChecked},
+       {"#div21", kMatched, kSameAsCached},
+       {"#div211", kNotCached, kNotYetChecked},
+       {"#div22", kNotCheckedAndSomeChildrenChecked, kNotYetChecked},
+       {"#div221", kNotMatchedAndAllDescendantsOrNextSiblingsChecked,
+        kSameAsCached},
+       {"#div23", kNotCheckedAndSomeChildrenChecked, kNotYetChecked},
+       {"#div231", kNotMatchedAndAllDescendantsOrNextSiblingsChecked,
+        kSameAsCached}},
+      /* expected_fast_reject_filter_cache_count */ 2);
+}
+
+TEST_F(CheckPseudoHasCacheScopeContextTest,
+       QuerySelectorAllCase4NonSubjectHas) {
+  // CheckPseudoHasArgumentTraversalScope::kAllNextSiblingSubtrees
+
+  auto* document = HTMLDocument::CreateForTest();
+  document->write(R"HTML(
+    <!DOCTYPE html>
+    <main id=main>
+      <div id=div1>
+        <div id=div11 class=c></div>
+      </div>
+      <div id=div2 class=a>
+        <div id=div21>
+          <div id=div211>
+            <div id=div2111 class=c></div>
+          </div>
+          <div id=div212 class=a>
+            <div id=div2121 class=b></div>
+          </div>
+        </div>
+        <div id=div22>
+          <div id=div221 class=b></div>
+        </div>
+      </div>
+    </main>
+  )HTML");
+
+  TestMatches(document, "div11", ":has(~ .a .b) .c",
+              /* expected_match_result */ true,
+              /* expected_result_cache_count */ 3,
+              {{"main", kNotCached, kNotYetChecked},
+               {"#div1", kMatched, kSameAsCached},
+               {"#div11", kNotCached, kNotYetChecked},
+               {"#div2", kNotCached, kNotYetChecked},
+               {"#div21", kNotCached, kNotYetChecked},
+               {"#div211", kNotCached, kNotYetChecked},
+               {"#div2111", kNotCached, kNotYetChecked},
+               {"#div212", kNotCached, kNotYetChecked},
+               {"#div2121", kNotCached, kNotYetChecked},
+               {"#div22", kNotCheckedAndSomeChildrenChecked, kNotYetChecked},
+               {"#div221", kNotMatchedAndAllDescendantsOrNextSiblingsChecked,
+                kSameAsCached}},
+              /* expected_fast_reject_filter_cache_count */ 1);
+
+  TestMatches(document, "div2111", ":has(~ .a .b) .c",
+              /* expected_match_result */ true,
+              /* expected_result_cache_count */ 3,
+              {{"main", kNotCached, kNotYetChecked},
+               {"#div1", kNotCached, kNotYetChecked},
+               {"#div11", kNotCached, kNotYetChecked},
+               {"#div2", kNotCached, kNotYetChecked},
+               {"#div21", kNotCheckedAndSomeChildrenChecked, kNotYetChecked},
+               {"#div211", kMatched, kSameAsCached},
+               {"#div2111", kNotCached, kNotYetChecked},
+               {"#div212", kNotMatchedAndAllDescendantsOrNextSiblingsChecked,
+                kSameAsCached},
+               {"#div2121", kNotCached, kAlreadyNotMatched},
+               {"#div22", kNotCached, kNotYetChecked},
+               {"#div221", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 1);
+
+  TestQuerySelectorAll(
+      document, "main", ":has(~ .a .b) .c", {"div11", "div2111"},
+      /* expected_result_cache_count */ 6,
+      {{"main", kNotCached, kNotYetChecked},
+       {"#div1", kMatched, kSameAsCached},
+       {"#div11", kNotCached, kNotYetChecked},
+       {"#div2", kNotCached, kNotYetChecked},
+       {"#div21", kNotCheckedAndSomeChildrenChecked, kNotYetChecked},
+       {"#div211", kMatched, kSameAsCached},
+       {"#div2111", kNotCached, kNotYetChecked},
+       {"#div212", kNotMatchedAndAllDescendantsOrNextSiblingsChecked,
+        kSameAsCached},
+       {"#div2121", kNotCached, kAlreadyNotMatched},
+       {"#div22", kNotCheckedAndSomeChildrenChecked, kNotYetChecked},
+       {"#div221", kNotMatchedAndAllDescendantsOrNextSiblingsChecked,
+        kSameAsCached}},
+      /* expected_fast_reject_filter_cache_count */ 2);
 }
 
 TEST_F(CheckPseudoHasCacheScopeContextTest,
@@ -1647,7 +1929,8 @@
                {"#div5", kNotCached, kAlreadyNotMatched},
                {"#div51", kNotCached, kAlreadyNotMatched},
                {"#div6", kNotCached, kAlreadyNotMatched},
-               {"#div61", kNotCached, kAlreadyNotMatched}});
+               {"#div61", kNotCached, kAlreadyNotMatched}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 
   TestMatches(document, "div11", ":has(+ .a ~ .b .c)",
               /* expected_match_result */ true,
@@ -1662,7 +1945,8 @@
                {"#div14", kNotMatchedAndAllDescendantsOrNextSiblingsChecked,
                 kSameAsCached},
                {"#div141", kNotCached, kAlreadyNotMatched},
-               {"#div15", kNotCached, kAlreadyNotMatched}});
+               {"#div15", kNotCached, kAlreadyNotMatched}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 
   TestMatches(document, "div12", ":has(+ .a ~ .b .c)",
               /* expected_match_result */ false,
@@ -1676,7 +1960,8 @@
                {"#div132", kNotCached, kAlreadyNotMatched},
                {"#div14", kNotCached, kAlreadyNotMatched},
                {"#div141", kNotCached, kAlreadyNotMatched},
-               {"#div15", kNotCached, kAlreadyNotMatched}});
+               {"#div15", kNotCached, kAlreadyNotMatched}},
+              /* expected_fast_reject_filter_cache_count */ 1);
 
   TestQuerySelectorAll(
       document, "main", ":has(+ .a ~ .b .c)", {"div11", "div4"},
@@ -1705,7 +1990,8 @@
        {"#div5", kNotCached, kAlreadyNotMatched},
        {"#div51", kNotCached, kAlreadyNotMatched},
        {"#div6", kNotCached, kAlreadyNotMatched},
-       {"#div61", kNotCached, kAlreadyNotMatched}});
+       {"#div61", kNotCached, kAlreadyNotMatched}},
+      /* expected_fast_reject_filter_cache_count */ 3);
 }
 
 TEST_F(CheckPseudoHasCacheScopeContextTest, QuerySelectorAllCase5) {
@@ -1748,7 +2034,8 @@
                {"#div3", kNotCached, kNotYetChecked},
                {"#div31", kNotCached, kNotYetChecked},
                {"#div32", kNotCached, kNotYetChecked},
-               {"#div33", kNotCached, kNotYetChecked}});
+               {"#div33", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 0);
 
   TestMatches(document, "div21", ":has(+ .a)",
               /* expected_match_result */ true,
@@ -1765,7 +2052,8 @@
                {"#div3", kNotCached, kNotYetChecked},
                {"#div31", kNotCached, kNotYetChecked},
                {"#div32", kNotCached, kNotYetChecked},
-               {"#div33", kNotCached, kNotYetChecked}});
+               {"#div33", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 0);
 
   TestQuerySelectorAll(document, "main", ":has(+ .a)", {"div2", "div21"},
                        /* expected_result_cache_count */ 0,
@@ -1781,7 +2069,8 @@
                         {"#div3", kNotCached, kNotYetChecked},
                         {"#div31", kNotCached, kNotYetChecked},
                         {"#div32", kNotCached, kNotYetChecked},
-                        {"#div33", kNotCached, kNotYetChecked}});
+                        {"#div33", kNotCached, kNotYetChecked}},
+                       /* expected_fast_reject_filter_cache_count */ 0);
 }
 
 TEST_F(CheckPseudoHasCacheScopeContextTest, QuerySelectorAllCase6) {
@@ -1825,7 +2114,8 @@
                {"#div12", kNotCached, kNotYetChecked},
                {"#div121", kNotCached, kNotYetChecked},
                {"#div122", kNotCached, kNotYetChecked},
-               {"#div123", kNotCached, kNotYetChecked}});
+               {"#div123", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 0);
 
   TestMatches(document, "div112", ":has(> .a)",
               /* expected_match_result */ true,
@@ -1842,7 +2132,8 @@
                {"#div12", kNotCached, kNotYetChecked},
                {"#div121", kNotCached, kNotYetChecked},
                {"#div122", kNotCached, kNotYetChecked},
-               {"#div123", kNotCached, kNotYetChecked}});
+               {"#div123", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 0);
 
   TestMatches(document, "div12", ":has(> .a)",
               /* expected_match_result */ true,
@@ -1859,7 +2150,8 @@
                {"#div12", kNotCached, kNotYetChecked},
                {"#div121", kNotCached, kNotYetChecked},
                {"#div122", kNotCached, kNotYetChecked},
-               {"#div123", kNotCached, kNotYetChecked}});
+               {"#div123", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 0);
 
   TestQuerySelectorAll(document, "main", ":has(> .a)",
                        {"div1", "div112", "div12"},
@@ -1876,7 +2168,8 @@
                         {"#div12", kNotCached, kNotYetChecked},
                         {"#div121", kNotCached, kNotYetChecked},
                         {"#div122", kNotCached, kNotYetChecked},
-                        {"#div123", kNotCached, kNotYetChecked}});
+                        {"#div123", kNotCached, kNotYetChecked}},
+                       /* expected_fast_reject_filter_cache_count */ 0);
 }
 
 TEST_F(CheckPseudoHasCacheScopeContextTest, QuerySelectorAllCase7) {
@@ -1917,7 +2210,8 @@
                {"#div23", kNotCached, kNotYetChecked},
                {"#div231", kNotCached, kNotYetChecked},
                {"#div232", kNotCached, kNotYetChecked},
-               {"#div233", kNotCached, kNotYetChecked}});
+               {"#div233", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 0);
 
   TestMatches(document, "div22", ":has(+ .a > .b)",
               /* expected_match_result */ true,
@@ -1933,7 +2227,8 @@
                {"#div23", kNotCached, kNotYetChecked},
                {"#div231", kNotCached, kNotYetChecked},
                {"#div232", kNotCached, kNotYetChecked},
-               {"#div233", kNotCached, kNotYetChecked}});
+               {"#div233", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 0);
 
   TestQuerySelectorAll(document, "main", ":has(+ .a > .b)", {"div1", "div22"},
                        /* expected_result_cache_count */ 0,
@@ -1948,7 +2243,8 @@
                         {"#div23", kNotCached, kNotYetChecked},
                         {"#div231", kNotCached, kNotYetChecked},
                         {"#div232", kNotCached, kNotYetChecked},
-                        {"#div233", kNotCached, kNotYetChecked}});
+                        {"#div233", kNotCached, kNotYetChecked}},
+                       /* expected_fast_reject_filter_cache_count */ 0);
 }
 
 TEST_F(CheckPseudoHasCacheScopeContextTest, QuerySelectorAllCase8) {
@@ -1998,7 +2294,8 @@
                {"#div3", kNotCached, kNotYetChecked},
                {"#div31", kNotCached, kNotYetChecked},
                {"#div32", kNotCached, kNotYetChecked},
-               {"#div33", kNotCached, kNotYetChecked}});
+               {"#div33", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 0);
 
   TestMatches(document, "div2", ":has(~ .a > .b)",
               /* expected_match_result */ true,
@@ -2018,7 +2315,8 @@
                {"#div3", kNotCached, kNotYetChecked},
                {"#div31", kNotCached, kNotYetChecked},
                {"#div32", kNotCached, kNotYetChecked},
-               {"#div33", kNotCached, kNotYetChecked}});
+               {"#div33", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 0);
 
   TestMatches(document, "div21", ":has(~ .a > .b)",
               /* expected_match_result */ true,
@@ -2038,7 +2336,8 @@
                {"#div3", kNotCached, kNotYetChecked},
                {"#div31", kNotCached, kNotYetChecked},
                {"#div32", kNotCached, kNotYetChecked},
-               {"#div33", kNotCached, kNotYetChecked}});
+               {"#div33", kNotCached, kNotYetChecked}},
+              /* expected_fast_reject_filter_cache_count */ 0);
 
   TestQuerySelectorAll(document, "main", ":has(~ .a > .b)",
                        {"div1", "div2", "div21"},
@@ -2058,7 +2357,8 @@
                         {"#div3", kNotCached, kNotYetChecked},
                         {"#div31", kNotCached, kNotYetChecked},
                         {"#div32", kNotCached, kNotYetChecked},
-                        {"#div33", kNotCached, kNotYetChecked}});
+                        {"#div33", kNotCached, kNotYetChecked}},
+                       /* expected_fast_reject_filter_cache_count */ 0);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/check_pseudo_has_fast_reject_filter.cc b/third_party/blink/renderer/core/css/check_pseudo_has_fast_reject_filter.cc
index ccd7d981..1a0b46f1 100644
--- a/third_party/blink/renderer/core/css/check_pseudo_has_fast_reject_filter.cc
+++ b/third_party/blink/renderer/core/css/check_pseudo_has_fast_reject_filter.cc
@@ -40,14 +40,15 @@
 
 void CheckPseudoHasFastRejectFilter::AddElementIdentifierHashes(
     const Element& element) {
-  filter_.Add(GetTagHash(element.LocalNameForSelectorMatching()));
+  DCHECK(filter_.get());
+  filter_->Add(GetTagHash(element.LocalNameForSelectorMatching()));
   if (element.HasID())
-    filter_.Add(GetIdHash(element.IdForStyleResolution()));
+    filter_->Add(GetIdHash(element.IdForStyleResolution()));
   if (element.HasClass()) {
     const SpaceSplitString& class_names = element.ClassNames();
     wtf_size_t count = class_names.size();
     for (wtf_size_t i = 0; i < count; ++i)
-      filter_.Add(GetClassHash(class_names[i]));
+      filter_->Add(GetClassHash(class_names[i]));
   }
   AttributeCollection attributes = element.AttributesWithoutUpdate();
   for (const auto& attribute_item : attributes) {
@@ -56,16 +57,17 @@
       continue;
     auto lower = attribute_name.IsLowerASCII() ? attribute_name
                                                : attribute_name.LowerASCII();
-    filter_.Add(GetAttributeHash(lower));
+    filter_->Add(GetAttributeHash(lower));
   }
 }
 
 bool CheckPseudoHasFastRejectFilter::FastReject(
     const Vector<unsigned>& pseudo_has_argument_hashes) const {
+  DCHECK(filter_.get());
   if (pseudo_has_argument_hashes.IsEmpty())
     return false;
   for (unsigned hash : pseudo_has_argument_hashes) {
-    if (!filter_.MayContain(hash))
+    if (!filter_->MayContain(hash))
       return true;
   }
   return false;
@@ -115,4 +117,10 @@
   }
 }
 
+void CheckPseudoHasFastRejectFilter::AllocateBloomFilter() {
+  if (filter_)
+    return;
+  filter_ = std::make_unique<FastRejectFilter>();
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/check_pseudo_has_fast_reject_filter.h b/third_party/blink/renderer/core/css/check_pseudo_has_fast_reject_filter.h
index aefe9ac0..3a7776a 100644
--- a/third_party/blink/renderer/core/css/check_pseudo_has_fast_reject_filter.h
+++ b/third_party/blink/renderer/core/css/check_pseudo_has_fast_reject_filter.h
@@ -50,8 +50,12 @@
 
   bool FastReject(const Vector<unsigned>& pseudo_has_argument_hashes) const;
 
+  void AllocateBloomFilter();
+  bool BloomFilterAllocated() const { return filter_.get(); }
+
  private:
-  WTF::BloomFilter<12> filter_;
+  using FastRejectFilter = WTF::BloomFilter<12>;
+  std::unique_ptr<FastRejectFilter> filter_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/check_pseudo_has_fast_reject_filter_test.cc b/third_party/blink/renderer/core/css/check_pseudo_has_fast_reject_filter_test.cc
index 4d56f8f..7cf82b87 100644
--- a/third_party/blink/renderer/core/css/check_pseudo_has_fast_reject_filter_test.cc
+++ b/third_party/blink/renderer/core/css/check_pseudo_has_fast_reject_filter_test.cc
@@ -58,6 +58,10 @@
 TEST_F(CheckPseudoHasFastRejectFilterTest, CheckFastReject) {
   CheckPseudoHasFastRejectFilter filter;
 
+  EXPECT_FALSE(filter.BloomFilterAllocated());
+  filter.AllocateBloomFilter();
+  EXPECT_TRUE(filter.BloomFilterAllocated());
+
   AddElementIdentifierHashes(
       filter, {{/* tag_name */ "div", /* id */ "d1", /* class_names */ "a",
                 /* attribute_name */ "attr1", /* attribute_value */ "val1"},
diff --git a/third_party/blink/renderer/core/css/container_query.cc b/third_party/blink/renderer/core/css/container_query.cc
index 7abef359..ca19253 100644
--- a/third_party/blink/renderer/core/css/container_query.cc
+++ b/third_party/blink/renderer/core/css/container_query.cc
@@ -20,6 +20,8 @@
     physical_axes_ |= kPhysicalAxisHorizontal;
   if (feature_flags & MediaQueryExpNode::kFeatureHeight)
     physical_axes_ |= kPhysicalAxisVertical;
+  if (feature_flags & MediaQueryExpNode::kFeatureStyle)
+    has_style_query_ = true;
 }
 
 unsigned ContainerSelector::Type(WritingMode writing_mode) const {
diff --git a/third_party/blink/renderer/core/css/container_query.h b/third_party/blink/renderer/core/css/container_query.h
index 7c548c6..fea83e6 100644
--- a/third_party/blink/renderer/core/css/container_query.h
+++ b/third_party/blink/renderer/core/css/container_query.h
@@ -34,10 +34,18 @@
   // for this selector to match.
   unsigned Type(WritingMode) const;
 
+  bool SelectsSizeContainers() const {
+    return physical_axes_ != kPhysicalAxisNone ||
+           logical_axes_ != kLogicalAxisNone;
+  }
+
+  bool SelectsStyleContainers() const { return has_style_query_; }
+
  private:
   AtomicString name_;
   PhysicalAxes physical_axes_{kPhysicalAxisNone};
   LogicalAxes logical_axes_{kLogicalAxisNone};
+  bool has_style_query_{false};
 };
 
 class CORE_EXPORT ContainerQuery final
diff --git a/third_party/blink/renderer/core/css/container_query_evaluator.cc b/third_party/blink/renderer/core/css/container_query_evaluator.cc
index a18728d..d4ef72f 100644
--- a/third_party/blink/renderer/core/css/container_query_evaluator.cc
+++ b/third_party/blink/renderer/core/css/container_query_evaluator.cc
@@ -52,13 +52,13 @@
 
 // static
 Element* ContainerQueryEvaluator::FindContainer(
-    Element* context_element,
+    Element* starting_element,
     const ContainerSelector& container_selector) {
   // TODO(crbug.com/1213888): Cache results.
-  for (Element* element = context_element; element;
+  for (Element* element = starting_element; element;
        element = element->ParentOrShadowHostElement()) {
     if (const ComputedStyle* style = element->GetComputedStyle()) {
-      if (style->IsContainerForSizeContainerQueries() &&
+      if (style->StyleType() == kPseudoIdNone &&
           Matches(*style, container_selector)) {
         return element;
       }
@@ -68,15 +68,30 @@
   return nullptr;
 }
 
-bool ContainerQueryEvaluator::EvalAndAdd(const StyleRecalcContext& context,
+bool ContainerQueryEvaluator::EvalAndAdd(const Element& matching_element,
+                                         const StyleRecalcContext& context,
                                          const ContainerQuery& query,
                                          MatchResult& match_result) {
-  Element* container = FindContainer(context.container, query.Selector());
+  const ContainerSelector& selector = query.Selector();
+  Element* starting_element =
+      selector.SelectsSizeContainers()
+          ? context.container
+          : matching_element.ParentOrShadowHostElement();
+  Element* container = FindContainer(starting_element, selector);
   if (!container)
     return false;
   ContainerQueryEvaluator* evaluator = container->GetContainerQueryEvaluator();
-  if (!evaluator)
-    return false;
+  if (!evaluator) {
+    if (selector.SelectsSizeContainers() ||
+        !selector.SelectsStyleContainers()) {
+      return false;
+    }
+    evaluator = &container->EnsureContainerQueryEvaluator();
+    evaluator->SetData(container->GetDocument(), *container, PhysicalSize(),
+                       kPhysicalAxisNone);
+  }
+  // TODO(crbug.com/1302630): style() queries should not compare with
+  // context.container.
   Change change = (context.container == container)
                       ? Change::kNearestContainer
                       : Change::kDescendantContainers;
diff --git a/third_party/blink/renderer/core/css/container_query_evaluator.h b/third_party/blink/renderer/core/css/container_query_evaluator.h
index 7356ac0..8905b08 100644
--- a/third_party/blink/renderer/core/css/container_query_evaluator.h
+++ b/third_party/blink/renderer/core/css/container_query_evaluator.h
@@ -27,11 +27,12 @@
 class CORE_EXPORT ContainerQueryEvaluator final
     : public GarbageCollected<ContainerQueryEvaluator> {
  public:
-  // Look for a container query container in the inclusive ancestor
-  // chain of `context_element`.
-  static Element* FindContainer(Element* context_element,
+  // Look for a container query container in the shadow-including inclusive
+  // ancestor chain of 'starting_element'.
+  static Element* FindContainer(Element* starting_element,
                                 const ContainerSelector&);
-  static bool EvalAndAdd(const StyleRecalcContext&,
+  static bool EvalAndAdd(const Element& matching_element,
+                         const StyleRecalcContext&,
                          const ContainerQuery&,
                          MatchResult&);
 
diff --git a/third_party/blink/renderer/core/css/container_query_evaluator_test.cc b/third_party/blink/renderer/core/css/container_query_evaluator_test.cc
index 434e3ee1..c4031871 100644
--- a/third_party/blink/renderer/core/css/container_query_evaluator_test.cc
+++ b/third_party/blink/renderer/core/css/container_query_evaluator_test.cc
@@ -20,6 +20,7 @@
 #include "third_party/blink/renderer/core/dom/dom_token_list.h"
 #include "third_party/blink/renderer/core/dom/element.h"
 #include "third_party/blink/renderer/core/dom/node_computed_style.h"
+#include "third_party/blink/renderer/core/dom/parent_node.h"
 #include "third_party/blink/renderer/core/execution_context/security_context.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/core/html/html_div_element.h"
@@ -626,4 +627,42 @@
   EXPECT_TRUE(Eval("style(--my-prop: 10px )", "--my-prop", "10px "));
 }
 
+TEST_F(ContainerQueryEvaluatorTest, FindContainer) {
+  SetBodyInnerHTML(R"HTML(
+    <div style="container-name:outer;container-type:size">
+      <div style="container-name:outer">
+        <div style="container-type: size">
+          <div>
+            <div></div>
+          </div>
+        </div>
+      </div>
+    </div>
+  )HTML");
+
+  UpdateAllLifecyclePhasesForTest();
+
+  Element* outer_size = ParentNode::firstElementChild(*GetDocument().body());
+  Element* outer = ParentNode::firstElementChild(*outer_size);
+  Element* inner_size = ParentNode::firstElementChild(*outer);
+  Element* inner = ParentNode::firstElementChild(*inner_size);
+
+  EXPECT_EQ(ContainerQueryEvaluator::FindContainer(
+                inner, ParseContainer("style(--foo: bar)")->Selector()),
+            inner);
+  EXPECT_EQ(
+      ContainerQueryEvaluator::FindContainer(
+          inner,
+          ParseContainer("(width > 100px) and style(--foo: bar)")->Selector()),
+      inner_size);
+  EXPECT_EQ(ContainerQueryEvaluator::FindContainer(
+                inner, ParseContainer("outer style(--foo: bar)")->Selector()),
+            outer);
+  EXPECT_EQ(
+      ContainerQueryEvaluator::FindContainer(
+          inner, ParseContainer("outer (width > 100px) and style(--foo: bar)")
+                     ->Selector()),
+      outer_size);
+}
+
 }  // namespace blink
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 23c2d843..72fbe5e 100644
--- a/third_party/blink/renderer/core/css/css_gradient_value.cc
+++ b/third_party/blink/renderer/core/css/css_gradient_value.cc
@@ -265,19 +265,30 @@
     }
 
     GradientStop new_stops[9];
-    // Position the new color stops.
+    // Position the new color stops. These must be in the range
+    // [offset_left, offset_right], and in non-decreasing order, even in the
+    // face of floating-point rounding.
     if (left_dist > right_dist) {
       for (size_t y = 0; y < 7; ++y)
-        new_stops[y].offset = offset_left + left_dist * (7 + y) / 13;
-      new_stops[7].offset = offset + right_dist / 3;
-      new_stops[8].offset = offset + right_dist * 2 / 3;
+        new_stops[y].offset = offset_left + left_dist * ((7.0f + y) / 13.0f);
+      new_stops[7].offset = offset + right_dist * (1.0f / 3.0f);
+      new_stops[8].offset = offset + right_dist * (2.0f / 3.0f);
     } else {
-      new_stops[0].offset = offset_left + left_dist / 3;
-      new_stops[1].offset = offset_left + left_dist * 2 / 3;
+      new_stops[0].offset = offset_left + left_dist * (1.0f / 3.0f);
+      new_stops[1].offset = offset_left + left_dist * (2.0f / 3.0f);
       for (size_t y = 0; y < 7; ++y)
-        new_stops[y + 2].offset = offset + right_dist * y / 13;
+        new_stops[y + 2].offset = offset + right_dist * (y / 13.0f);
     }
 
+#if DCHECK_IS_ON()
+    // Verify that offset_left <= x_0 <= x_1 <= ... <= x_8 <= offset_right.
+    DCHECK_GE(new_stops[0].offset, offset_left);
+    for (int i = 1; i < 8; ++i) {
+      DCHECK_GE(new_stops[i].offset, new_stops[i - 1].offset);
+    }
+    DCHECK_GE(offset_right, new_stops[8].offset);
+#endif  // DCHECK_IS_ON()
+
     // calculate colors for the new color hints.
     // The color weighting for the new color stops will be
     // pointRelativeOffset^(ln(0.5)/ln(hintRelativeOffset)).
diff --git a/third_party/blink/renderer/core/css/element_rule_collector.cc b/third_party/blink/renderer/core/css/element_rule_collector.cc
index 3fcc632b..76c93902d 100644
--- a/third_party/blink/renderer/core/css/element_rule_collector.cc
+++ b/third_party/blink/renderer/core/css/element_rule_collector.cc
@@ -81,13 +81,14 @@
 }
 
 bool EvaluateAndAddContainerQueries(
+    const Element& matching_element,
     const ContainerQuery& container_query,
     const StyleRecalcContext& style_recalc_context,
     MatchResult& result) {
   for (const ContainerQuery* current = &container_query; current;
        current = current->Parent()) {
-    if (!ContainerQueryEvaluator::EvalAndAdd(style_recalc_context, *current,
-                                             result)) {
+    if (!ContainerQueryEvaluator::EvalAndAdd(
+            matching_element, style_recalc_context, *current, result)) {
       return false;
     }
   }
@@ -415,7 +416,8 @@
       // elements when they depend on the originating element.
       if (pseudo_style_request_.pseudo_id != kPseudoIdNone ||
           result.dynamic_pseudo == kPseudoIdNone) {
-        if (!EvaluateAndAddContainerQueries(*container_query,
+        if (!EvaluateAndAddContainerQueries(context_.GetElement(),
+                                            *container_query,
                                             style_recalc_context_, result_)) {
           if (AffectsAnimations(rule_data))
             result_.SetConditionallyAffectsAnimations();
diff --git a/third_party/blink/renderer/core/css/layout_upgrade.cc b/third_party/blink/renderer/core/css/layout_upgrade.cc
index 0b635a6..217c50a6 100644
--- a/third_party/blink/renderer/core/css/layout_upgrade.cc
+++ b/third_party/blink/renderer/core/css/layout_upgrade.cc
@@ -20,7 +20,9 @@
 }
 
 bool ParentLayoutUpgrade::ShouldUpgrade() {
-  return document_.GetStyleEngine().HasViewportDependentMediaQueries() ||
+  StyleEngine& style_engine = document_.GetStyleEngine();
+  return style_engine.HasViewportDependentMediaQueries() ||
+         style_engine.HasViewportDependentPropertyRegistrations() ||
          NodeLayoutUpgrade(owner_).ShouldUpgrade();
 }
 
diff --git a/third_party/blink/renderer/core/css/media_query_exp.cc b/third_party/blink/renderer/core/css/media_query_exp.cc
index 87a5081..f5c33a5 100644
--- a/third_party/blink/renderer/core/css/media_query_exp.cc
+++ b/third_party/blink/renderer/core/css/media_query_exp.cc
@@ -694,6 +694,14 @@
   builder.Append(")");
 }
 
+MediaQueryExpNode::FeatureFlags MediaQueryFunctionExpNode::CollectFeatureFlags()
+    const {
+  FeatureFlags flags = MediaQueryUnaryExpNode::CollectFeatureFlags();
+  if (name_ == AtomicString("style"))
+    flags |= kFeatureStyle;
+  return flags;
+}
+
 void MediaQueryNotExpNode::SerializeTo(StringBuilder& builder) const {
   builder.Append("not ");
   Operand().SerializeTo(builder);
diff --git a/third_party/blink/renderer/core/css/media_query_exp.h b/third_party/blink/renderer/core/css/media_query_exp.h
index cb6cb3bb..0f6b131 100644
--- a/third_party/blink/renderer/core/css/media_query_exp.h
+++ b/third_party/blink/renderer/core/css/media_query_exp.h
@@ -316,6 +316,7 @@
     kFeatureHeight = 1 << 3,
     kFeatureInlineSize = 1 << 4,
     kFeatureBlockSize = 1 << 5,
+    kFeatureStyle = 1 << 6,
   };
 
   using FeatureFlags = unsigned;
@@ -398,6 +399,7 @@
 
   Type GetType() const override { return Type::kFunction; }
   void SerializeTo(StringBuilder&) const override;
+  FeatureFlags CollectFeatureFlags() const override;
 
  private:
   AtomicString name_;
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 4132424..662abec3 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
@@ -2123,9 +2123,18 @@
 const CSSValue& StyleBuilderConverter::ConvertRegisteredPropertyInitialValue(
     const Document& document,
     const CSSValue& value) {
+  CSSToLengthConversionData::FontSizes font_sizes;
+  CSSToLengthConversionData::ViewportSize viewport_size(
+      document.GetLayoutView());
+  CSSToLengthConversionData::ContainerSizes container_sizes;
+  CSSToLengthConversionData conversion_data(
+      /* style */ nullptr, WritingMode::kHorizontalTb, font_sizes,
+      viewport_size, container_sizes,
+      /* zoom */ 1.0f);
+
   return ComputeRegisteredPropertyValue(
-      document, nullptr /* state */, CSSToLengthConversionData(), value,
-      document.BaseURL(), document.Encoding());
+      document, nullptr /* state */, conversion_data, value, document.BaseURL(),
+      document.Encoding());
 }
 
 const CSSValue& StyleBuilderConverter::ConvertRegisteredPropertyValue(
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver.cc b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
index e11dacb..61412fa 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
@@ -1583,9 +1583,9 @@
 Element* StyleResolver::FindContainerForElement(
     Element* element,
     const ContainerSelector& container_selector) {
-  auto context = StyleRecalcContext::FromAncestors(*element);
-  return ContainerQueryEvaluator::FindContainer(context.container,
-                                                container_selector);
+  DCHECK(element);
+  return ContainerQueryEvaluator::FindContainer(
+      element->ParentOrShadowHostElement(), container_selector);
 }
 
 RuleIndexList* StyleResolver::PseudoCSSRulesForElement(
diff --git a/third_party/blink/renderer/core/css/selector_checker.cc b/third_party/blink/renderer/core/css/selector_checker.cc
index de18ddd..be8e581 100644
--- a/third_party/blink/renderer/core/css/selector_checker.cc
+++ b/third_party/blink/renderer/core/css/selector_checker.cc
@@ -947,6 +947,43 @@
   return false;
 }
 
+void AddElementIdentifierHashesInTraversalScopeAndSetAffectedByHasFlags(
+    CheckPseudoHasFastRejectFilter& fast_reject_filter,
+    Element& has_anchor_element,
+    CheckPseudoHasArgumentContext& argument_context,
+    bool update_affected_by_has_flags) {
+  for (CheckPseudoHasArgumentTraversalIterator iterator(has_anchor_element,
+                                                        argument_context);
+       !iterator.AtEnd(); ++iterator) {
+    fast_reject_filter.AddElementIdentifierHashes(*iterator.CurrentElement());
+    if (update_affected_by_has_flags) {
+      SetAffectedByHasFlagsForElementAtDepth(
+          argument_context, iterator.CurrentElement(), iterator.CurrentDepth());
+    }
+  }
+}
+
+void SetAllElementsInTraversalScopeAsChecked(
+    Element* has_anchor_element,
+    CheckPseudoHasArgumentContext& argument_context,
+    CheckPseudoHasCacheScope::Context& cache_scope_context) {
+  // Find last element and last depth of the argument traversal iterator.
+  Element* last_element = has_anchor_element;
+  int last_depth = 0;
+  if (argument_context.AdjacentDistanceLimit() > 0)
+    last_element = ElementTraversal::NextSibling(*last_element);
+  if (last_element) {
+    if (argument_context.DepthLimit() > 0) {
+      last_element = ElementTraversal::FirstChild(*last_element);
+      last_depth = 1;
+    }
+  }
+  if (!last_element)
+    return;
+  cache_scope_context.SetAllTraversedElementsAsChecked(last_element,
+                                                       last_depth);
+}
+
 enum EarlyBreakOnHasArgumentChecking {
   kBreakEarlyAndReturnAsMatched,
   kBreakEarlyAndMoveToNextArgument,
@@ -958,7 +995,7 @@
     Element* has_anchor_element,
     CheckPseudoHasArgumentContext& argument_context,
     CheckPseudoHasCacheScope::Context& cache_scope_context,
-    bool update_affected_by_has_flags) {
+    bool& update_affected_by_has_flags) {
   if (!cache_scope_context.CacheAllowed())
     return kNoEarlyBreak;
 
@@ -980,6 +1017,48 @@
                                       : kBreakEarlyAndMoveToNextArgument;
   }
 
+  // Check fast reject filter to reject :has() argument checking early.
+
+  bool is_new_entry;
+  CheckPseudoHasFastRejectFilter& fast_reject_filter =
+      cache_scope_context.EnsureFastRejectFilter(has_anchor_element,
+                                                 is_new_entry);
+
+  // Filter is not actually created on the first check to avoid unnecessary
+  // filter creation overhead. If the :has() anchor element has the
+  // AffectedByMultipleHas flag set, use fast reject filter even if on the first
+  // check since there can be more checks on the anchor element.
+  if (is_new_entry && !has_anchor_element->AffectedByMultipleHas())
+    return kNoEarlyBreak;
+
+  // The bloom filter in the fast reject filter is allocated and initialized on
+  // the second check. We can check fast rejection with the filter after the
+  // allocation and initialization.
+  if (!fast_reject_filter.BloomFilterAllocated()) {
+    if (update_affected_by_has_flags) {
+      // Mark the :has() anchor element as affected by multiple :has() pseudo
+      // classes so that we can always use fast reject filter for the anchor
+      // element.
+      has_anchor_element->SetAffectedByMultipleHas();
+    }
+
+    fast_reject_filter.AllocateBloomFilter();
+    AddElementIdentifierHashesInTraversalScopeAndSetAffectedByHasFlags(
+        fast_reject_filter, *has_anchor_element, argument_context,
+        update_affected_by_has_flags);
+  }
+
+  // affected-by-has flags were already set while adding element identifier
+  // hashes (AddElementIdentifierHashesInTraversalScopeAndSetAffectedByHasFlags)
+  update_affected_by_has_flags = false;
+
+  if (fast_reject_filter.FastReject(
+          argument_context.GetPseudoHasArgumentHashes())) {
+    SetAllElementsInTraversalScopeAsChecked(
+        has_anchor_element, argument_context, cache_scope_context);
+    return kBreakEarlyAndMoveToNextArgument;
+  }
+
   return kNoEarlyBreak;
 }
 
diff --git a/third_party/blink/renderer/core/css/style_engine.cc b/third_party/blink/renderer/core/css/style_engine.cc
index 46ff36eb..c339b99 100644
--- a/third_party/blink/renderer/core/css/style_engine.cc
+++ b/third_party/blink/renderer/core/css/style_engine.cc
@@ -882,9 +882,19 @@
 void StyleEngine::InvalidateViewportUnitStylesIfNeeded() {
   if (!viewport_unit_dirty_flags_)
     return;
-  SetNeedsStyleRecalcForViewportUnits(GetDocument(),
-                                      viewport_unit_dirty_flags_);
-  viewport_unit_dirty_flags_ = 0;
+  unsigned dirty_flags = 0;
+  std::swap(viewport_unit_dirty_flags_, dirty_flags);
+
+  // If there are registered custom properties which depend on the invalidated
+  // viewport units, it can potentially affect every element.
+  if (initial_data_ && (initial_data_->GetViewportUnitFlags() & dirty_flags)) {
+    InvalidateInitialData();
+    MarkAllElementsForStyleRecalc(StyleChangeReasonForTracing::Create(
+        style_change_reason::kViewportUnits));
+    return;
+  }
+
+  SetNeedsStyleRecalcForViewportUnits(GetDocument(), dirty_flags);
 }
 
 void StyleEngine::InvalidateStyleAndLayoutForFontUpdates() {
@@ -1704,6 +1714,12 @@
   }
 }
 
+bool StyleEngine::HasViewportDependentPropertyRegistrations() {
+  UpdateActiveStyle();
+  const PropertyRegistry* registry = GetDocument().GetPropertyRegistry();
+  return registry && registry->GetViewportUnitFlags();
+}
+
 void StyleEngine::ScheduleInvalidationsForRuleSets(
     TreeScope& tree_scope,
     const HeapHashSet<Member<RuleSet>>& rule_sets,
diff --git a/third_party/blink/renderer/core/css/style_engine.h b/third_party/blink/renderer/core/css/style_engine.h
index aa871c3..d69d367f 100644
--- a/third_party/blink/renderer/core/css/style_engine.h
+++ b/third_party/blink/renderer/core/css/style_engine.h
@@ -307,6 +307,7 @@
     return global_rule_set_->GetRuleFeatureSet()
         .HasViewportDependentMediaQueries();
   }
+  bool HasViewportDependentPropertyRegistrations();
 
   class InApplyAnimationUpdateScope {
     STACK_ALLOCATED();
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
index f15da2c..b3621a2a 100644
--- a/third_party/blink/renderer/core/dom/element.cc
+++ b/third_party/blink/renderer/core/dom/element.cc
@@ -6143,6 +6143,14 @@
   EnsureElementRareData().SetAffectedByLogicalCombinationsInHas();
 }
 
+bool Element::AffectedByMultipleHas() const {
+  return HasRareData() ? GetElementRareData()->AffectedByMultipleHas() : false;
+}
+
+void Element::SetAffectedByMultipleHas() {
+  EnsureElementRareData().SetAffectedByMultipleHas();
+}
+
 bool Element::UpdateForceLegacyLayout(const ComputedStyle& new_style,
                                       const ComputedStyle* old_style) {
   // ::first-letter may cause structure discrepancies between DOM and layout
@@ -6561,6 +6569,16 @@
   return nullptr;
 }
 
+ContainerQueryEvaluator& Element::EnsureContainerQueryEvaluator() {
+  ContainerQueryData& data = EnsureElementRareData().EnsureContainerQueryData();
+  ContainerQueryEvaluator* evaluator = data.GetContainerQueryEvaluator();
+  if (!evaluator) {
+    evaluator = MakeGarbageCollected<ContainerQueryEvaluator>();
+    data.SetContainerQueryEvaluator(evaluator);
+  }
+  return *evaluator;
+}
+
 bool Element::SkippedContainerStyleRecalc() const {
   if (!RuntimeEnabledFeatures::CSSContainerSkipStyleRecalcEnabled())
     return false;
diff --git a/third_party/blink/renderer/core/dom/element.h b/third_party/blink/renderer/core/dom/element.h
index 1be5ed6..76c77410 100644
--- a/third_party/blink/renderer/core/dom/element.h
+++ b/third_party/blink/renderer/core/dom/element.h
@@ -1138,6 +1138,7 @@
 
   ContainerQueryData* GetContainerQueryData() const;
   ContainerQueryEvaluator* GetContainerQueryEvaluator() const;
+  ContainerQueryEvaluator& EnsureContainerQueryEvaluator();
   bool SkippedContainerStyleRecalc() const;
 
   virtual void SetActive(bool active);
@@ -1177,6 +1178,8 @@
   void SetAncestorsOrSiblingsAffectedByFocusVisibleInHas();
   bool AffectedByLogicalCombinationsInHas() const;
   void SetAffectedByLogicalCombinationsInHas();
+  bool AffectedByMultipleHas() const;
+  void SetAffectedByMultipleHas();
 
   void SaveIntrinsicSize(ResizeObserverSize* size);
   const ResizeObserverSize* LastIntrinsicSize() const;
diff --git a/third_party/blink/renderer/core/dom/element_rare_data.h b/third_party/blink/renderer/core/dom/element_rare_data.h
index a00cf935..612ce90 100644
--- a/third_party/blink/renderer/core/dom/element_rare_data.h
+++ b/third_party/blink/renderer/core/dom/element_rare_data.h
@@ -244,6 +244,12 @@
   void SetAffectedByLogicalCombinationsInHas() {
     has_invalidation_flags_.affected_by_logical_combinations_in_has = true;
   }
+  bool AffectedByMultipleHas() const {
+    return has_invalidation_flags_.affected_by_multiple_has;
+  }
+  void SetAffectedByMultipleHas() {
+    has_invalidation_flags_.affected_by_multiple_has = true;
+  }
 
   void Trace(blink::Visitor*) const;
 
@@ -514,6 +520,12 @@
   void SetAffectedByLogicalCombinationsInHas() {
     EnsureSuperRareData().SetAffectedByLogicalCombinationsInHas();
   }
+  bool AffectedByMultipleHas() const {
+    return super_rare_data_ ? super_rare_data_->AffectedByMultipleHas() : false;
+  }
+  void SetAffectedByMultipleHas() {
+    EnsureSuperRareData().SetAffectedByMultipleHas();
+  }
 
   AccessibleNode* GetAccessibleNode() const {
     if (super_rare_data_)
diff --git a/third_party/blink/renderer/core/dom/has_invalidation_flags.h b/third_party/blink/renderer/core/dom/has_invalidation_flags.h
index b29e4ce..26c29d6 100644
--- a/third_party/blink/renderer/core/dom/has_invalidation_flags.h
+++ b/third_party/blink/renderer/core/dom/has_invalidation_flags.h
@@ -25,6 +25,21 @@
 //        change mutation, if an element doesn't have the flag set, the element
 //        will not be invalidated or scheduled on even if the element has the
 //        AffectedBySubjectHas or AffectedByNonSubjectHas flag set.
+//    - AffectedByMultipleHas
+//        Indicate that this element can be affected by multiple :has() pseudo
+//        classes.
+//        SelectorChecker uses CheckPseudoHasFastRejectFilter to preemtively
+//        skip non-matching :has() pseudo class checks only if there are
+//        multiple :has() to check on the same anchor element. SelectorChecker
+//        would not use the reject filter for a single :has() because it would
+//        have worse performance caused by the bloom filter memory allocation
+//        and the tree traversal for collecting element identifier hashes.
+//        To avoid the unnecessary overhead, bloom filter creation and element
+//        identifier hash collection are performed on the second check, and at
+//        this time AffectedByMultipleHas flag is set.
+//        This flag is used to determine whether SelectorChecker can use the
+//        reject filter even if on the first check since the flag indicates that
+//        there can be additional checks on the same anchor element.
 //
 //    SelectorChecker::CheckPseudoClass() set the flags on an element when it
 //    checks a :has() pseudo class on the element.
@@ -189,6 +204,8 @@
   unsigned ancestors_or_siblings_affected_by_focus_visible_in_has : 1;
   unsigned affected_by_logical_combinations_in_has : 1;
 
+  unsigned affected_by_multiple_has : 1;
+
   HasInvalidationFlags()
       : affected_by_subject_has(false),
         affected_by_non_subject_has(false),
@@ -198,7 +215,8 @@
         ancestors_or_siblings_affected_by_hover_in_has(false),
         ancestors_or_siblings_affected_by_active_in_has(false),
         ancestors_or_siblings_affected_by_focus_in_has(false),
-        ancestors_or_siblings_affected_by_focus_visible_in_has(false) {}
+        ancestors_or_siblings_affected_by_focus_visible_in_has(false),
+        affected_by_multiple_has(false) {}
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/forms/html_form_element.cc b/third_party/blink/renderer/core/html/forms/html_form_element.cc
index 66709078..a8756801 100644
--- a/third_party/blink/renderer/core/html/forms/html_form_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_form_element.cc
@@ -360,10 +360,8 @@
   if (should_submit) {
     // If this form already made a request to navigate another frame which is
     // still pending, then we should cancel that one.
-    if (cancel_last_submission_ &&
-        RuntimeEnabledFeatures::CancelFormSubmissionInDefaultHandlerEnabled()) {
+    if (cancel_last_submission_)
       std::move(cancel_last_submission_).Run();
-    }
     ScheduleFormSubmission(event, submit_button);
   }
 }
diff --git a/third_party/blink/renderer/core/html/html_frame_owner_element.cc b/third_party/blink/renderer/core/html/html_frame_owner_element.cc
index 31254cc0..3889d24 100644
--- a/third_party/blink/renderer/core/html/html_frame_owner_element.cc
+++ b/third_party/blink/renderer/core/html/html_frame_owner_element.cc
@@ -53,6 +53,7 @@
 #include "third_party/blink/renderer/core/loader/document_loader.h"
 #include "third_party/blink/renderer/core/loader/frame_load_request.h"
 #include "third_party/blink/renderer/core/loader/frame_loader.h"
+#include "third_party/blink/renderer/core/loader/url_matcher.h"
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/core/page/scrolling/root_scroller_controller.h"
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
@@ -231,43 +232,6 @@
   return false;
 }
 
-using AllowedListForLazyLoading =
-    WTF::Vector<std::pair<scoped_refptr<const SecurityOrigin>, WTF::String>>;
-
-// The format of params from Finch experiment is like below:
-// "https://www.exampole.com(:443)|/embed,https://www.exampole.com|embed=true"
-// Expecting the param is a comma separated string, and each string is
-// separated by the vertical bar. The left side of the vertical bar is a
-// host name, and the right side is a part of path or search params.
-// Both are the indicators of html embeds.
-AllowedListForLazyLoading
-ParseFieldParamForAutomaticLazyFrameLoadingToEmbeds() {
-  AllowedListForLazyLoading allowed_list;
-  WTF::Vector<WTF::String> parsed_strings;
-  WTF::String(
-      base::GetFieldTrialParamValueByFeature(
-          features::kAutomaticLazyFrameLoadingToEmbedUrls, "allowed_websites")
-          .c_str())
-      .Split(",", /*allow_empty_entries=*/false, parsed_strings);
-  WTF::Vector<WTF::String> site_info;
-  for (const auto& it : parsed_strings) {
-    it.Split("|", /*allow_empty_entries=*/false, site_info);
-    DCHECK_EQ(site_info.size(), 2u)
-        << "Got unexpected AutomaticLazyFrameLoadingToEmbeds entry: " << it;
-
-    allowed_list.push_back(std::make_pair(
-        SecurityOrigin::CreateFromString(site_info[0]), site_info[1]));
-  }
-
-  return allowed_list;
-}
-
-const AllowedListForLazyLoading& AllowedWebsitesForLazyLoading() {
-  DEFINE_STATIC_LOCAL(AllowedListForLazyLoading, allowed_websites,
-                      (ParseFieldParamForAutomaticLazyFrameLoadingToEmbeds()));
-  return allowed_websites;
-}
-
 // Checks if the passed url is the same origin with the document.
 // This is called in order to limit LazyEmbeds/Ads to apply only cross-origin
 // frames.
@@ -296,24 +260,12 @@
     return false;
   }
 
-  scoped_refptr<const SecurityOrigin> origin = SecurityOrigin::Create(url);
-  for (const auto& it : AllowedWebsitesForLazyLoading()) {
-    // TODO(sisidovski): IsSameOriginWith is more strict but we skip the port
-    // number check in order to avoid hardcoding port numbers to corresponding
-    // WPT test suites. To check port numbers, we need to set them to the
-    // allowlist which is passed by Chrome launch flag or Finch params. But,
-    // WPT server could have multiple ports, and it's difficult to expect which
-    // ports are available and set to the feature params before starting the
-    // test. That will affect the test reliability.
-    if ((origin.get()->Protocol() == it.first->Protocol() &&
-         origin.get()->Host() == it.first->Host()) &&
-        (url.GetPath().Contains(it.second) ||
-         url.Query().Contains(it.second))) {
-      return true;
-    }
-  }
+  DEFINE_STATIC_LOCAL(UrlMatcher, url_matcher,
+                      (UrlMatcher(base::GetFieldTrialParamValueByFeature(
+                          features::kAutomaticLazyFrameLoadingToEmbedUrls,
+                          "allowed_websites"))));
 
-  return false;
+  return url_matcher.Match(url);
 }
 
 const base::TimeDelta GetLazyEmbedsTimeoutMs() {
diff --git a/third_party/blink/renderer/core/layout/build.gni b/third_party/blink/renderer/core/layout/build.gni
index 5d4205d..5956d7925 100644
--- a/third_party/blink/renderer/core/layout/build.gni
+++ b/third_party/blink/renderer/core/layout/build.gni
@@ -573,6 +573,7 @@
   "ng/ng_length_utils.cc",
   "ng/ng_length_utils.h",
   "ng/ng_link.h",
+  "ng/ng_logical_link.h",
   "ng/ng_out_of_flow_layout_part.cc",
   "ng/ng_out_of_flow_layout_part.h",
   "ng/ng_out_of_flow_positioned_node.cc",
diff --git a/third_party/blink/renderer/core/layout/geometry/logical_rect.h b/third_party/blink/renderer/core/layout/geometry/logical_rect.h
index f6c95d07..e7b10ad 100644
--- a/third_party/blink/renderer/core/layout/geometry/logical_rect.h
+++ b/third_party/blink/renderer/core/layout/geometry/logical_rect.h
@@ -66,7 +66,7 @@
     return other.offset == offset && other.size == size;
   }
 
-  LogicalRect operator+(const LogicalOffset&) const {
+  LogicalRect operator+(const LogicalOffset& offset) const {
     return {this->offset + offset, size};
   }
 
diff --git a/third_party/blink/renderer/core/layout/geometry/logical_rect_test.cc b/third_party/blink/renderer/core/layout/geometry/logical_rect_test.cc
index e82fdd6..646b56e 100644
--- a/third_party/blink/renderer/core/layout/geometry/logical_rect_test.cc
+++ b/third_party/blink/renderer/core/layout/geometry/logical_rect_test.cc
@@ -11,6 +11,11 @@
 
 namespace {
 
+TEST(LogicalRectTest, AddOffset) {
+  EXPECT_EQ(LogicalRect(1, 2, 3, 4) + LogicalOffset(5, 6),
+            LogicalRect(6, 8, 3, 4));
+}
+
 struct LogicalRectUniteTestData {
   const char* test_case;
   LogicalRect a;
diff --git a/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.cc
index fe9c4658..c684ea10 100644
--- a/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.cc
@@ -349,6 +349,19 @@
          DoesItemStretch(child);
 }
 
+NGConstraintSpace NGFlexLayoutAlgorithm::BuildSpaceForIntrinsicInlineSize(
+    const NGBlockNode& child) const {
+  NGMinMaxConstraintSpaceBuilder builder(ConstraintSpace(), Style(), child,
+                                         /* is_new_fc */ true);
+  builder.SetAvailableBlockSize(ChildAvailableSize().block_size);
+  builder.SetPercentageResolutionBlockSize(child_percentage_size_.block_size);
+  builder.SetReplacedPercentageResolutionBlockSize(
+      child_percentage_size_.block_size);
+  if (!is_column_ && WillChildCrossSizeBeContainerCrossSize(child))
+    builder.SetBlockAutoBehavior(NGAutoBehavior::kStretchExplicit);
+  return builder.ToConstraintSpace();
+}
+
 NGConstraintSpace NGFlexLayoutAlgorithm::BuildSpaceForIntrinsicBlockSize(
     const NGBlockNode& flex_item) const {
   const ComputedStyle& child_style = flex_item.Style();
@@ -1863,7 +1876,7 @@
     return;
   }
   DCHECK_EQ(children.size(), 1u);
-  const NGContainerFragmentBuilder::ChildWithOffset& child = children[0];
+  const NGLogicalLink& child = children[0];
   DCHECK(!child.fragment->IsLineBox());
   const NGConstraintSpace& space = ConstraintSpace();
   NGBoxFragment fragment(space.GetWritingDirection(),
@@ -2129,16 +2142,8 @@
   FlexFractionParts max_content_largest_fraction(Style());
   for (const FlexItem& item : algorithm_.all_items_) {
     const NGBlockNode& child = item.ng_input_node_;
-    NGMinMaxConstraintSpaceBuilder builder(ConstraintSpace(), Style(), child,
-                                           /* is_new_fc */ true);
-    builder.SetAvailableBlockSize(ChildAvailableSize().block_size);
-    builder.SetPercentageResolutionBlockSize(child_percentage_size_.block_size);
-    builder.SetReplacedPercentageResolutionBlockSize(
-        child_percentage_size_.block_size);
-    if (!is_column_ && WillChildCrossSizeBeContainerCrossSize(child))
-      builder.SetBlockAutoBehavior(NGAutoBehavior::kStretchExplicit);
-    const NGConstraintSpace space = builder.ToConstraintSpace();
 
+    const NGConstraintSpace space = BuildSpaceForIntrinsicInlineSize(child);
     const MinMaxSizesResult min_max_content_contributions =
         ComputeItemContributions(space, item);
     depends_on_block_constraints |=
@@ -2157,6 +2162,54 @@
 }
 
 MinMaxSizesResult
+NGFlexLayoutAlgorithm::ComputeMinMaxSizeOfMultilineColumnContainer() {
+  NGFlexChildIterator iterator(Node());
+  MinMaxSizes largest_inline_size_contributions;
+  for (NGBlockNode child = iterator.NextChild(); child;
+       child = iterator.NextChild()) {
+    if (child.IsOutOfFlowPositioned())
+      continue;
+
+    auto space = BuildSpaceForIntrinsicInlineSize(child);
+    MinMaxSizesResult child_contributions =
+        ComputeMinAndMaxContentContribution(Style(), child, space);
+    NGBoxStrut child_margins =
+        ComputeMarginsFor(space, child.Style(), ConstraintSpace());
+    child_contributions.sizes += child_margins.InlineSum();
+
+    largest_inline_size_contributions.Encompass(child_contributions.sizes);
+  }
+
+  // The algorithm for determining the max-content width of a column-wrap
+  // container is simply: Run layout on the container but give the items an
+  // overridden available size, equal to the largest max-content width of any
+  // item, when they are laid out. The container's max-content width is then
+  // the farthest outer inline-end point of all the items.
+  item_inline_available_size_override_ =
+      largest_inline_size_contributions.max_size;
+  HeapVector<NGFlexLine> flex_line_outputs;
+  HeapVector<Member<LayoutBox>> dummy_oof_children;
+  PlaceFlexItems(&flex_line_outputs, &dummy_oof_children);
+  if (!flex_line_outputs.IsEmpty()) {
+    largest_inline_size_contributions.max_size =
+        flex_line_outputs.back().line_cross_size +
+        flex_line_outputs.back().cross_axis_offset -
+        flex_line_outputs.front().cross_axis_offset;
+  }
+
+  DCHECK_GE(largest_inline_size_contributions.min_size, 0);
+  DCHECK_LE(largest_inline_size_contributions.min_size,
+            largest_inline_size_contributions.max_size);
+
+  largest_inline_size_contributions += BorderScrollbarPadding().InlineSum();
+
+  // This always depends on block constraints because if block constraints
+  // change, this flexbox could get a different number of columns.
+  return {largest_inline_size_contributions,
+          /* depends_on_block_constraints */ true};
+}
+
+MinMaxSizesResult
 NGFlexLayoutAlgorithm::ComputeMinMaxSizeOfSingleLineRowContainer() {
   ConstructAndAppendFlexItems(/*is_computing_intrinsic_size*/ true);
   MinMaxSizes container_sizes;
@@ -2220,7 +2273,7 @@
     // TODO(crbug.com/240765): Implement all the cases here.
     if (is_column_) {
       if (algorithm_.IsMultiline()) {
-        // multiline column flexbox
+        return ComputeMinMaxSizeOfMultilineColumnContainer();
       } else {
         // singleline column flexbox
       }
@@ -2242,16 +2295,7 @@
       continue;
     number_of_items++;
 
-    NGMinMaxConstraintSpaceBuilder builder(ConstraintSpace(), Style(), child,
-                                           /* is_new_fc */ true);
-    builder.SetAvailableBlockSize(ChildAvailableSize().block_size);
-    builder.SetPercentageResolutionBlockSize(child_percentage_size_.block_size);
-    builder.SetReplacedPercentageResolutionBlockSize(
-        child_percentage_size_.block_size);
-    if (!is_column_ && WillChildCrossSizeBeContainerCrossSize(child))
-      builder.SetBlockAutoBehavior(NGAutoBehavior::kStretchExplicit);
-    const auto space = builder.ToConstraintSpace();
-
+    const NGConstraintSpace space = BuildSpaceForIntrinsicInlineSize(child);
     MinMaxSizesResult child_result =
         ComputeMinAndMaxContentContribution(Style(), child, space);
     NGBoxStrut child_margins =
@@ -2550,4 +2594,13 @@
 }
 #endif
 
+LogicalSize NGFlexLayoutAlgorithm::ChildAvailableSize() const {
+  LogicalSize available_size = NGLayoutAlgorithm::ChildAvailableSize();
+  if (UNLIKELY(item_inline_available_size_override_.has_value())) {
+    DCHECK_EQ(container_builder_.InlineSize(), kIndefiniteSize);
+    available_size.inline_size = *item_inline_available_size_override_;
+  }
+  return available_size;
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.h b/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.h
index abc4629..7bd621f 100644
--- a/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.h
+++ b/third_party/blink/renderer/core/layout/ng/flex/ng_flex_layout_algorithm.h
@@ -63,6 +63,8 @@
   bool IsColumnContainerMainSizeDefinite() const;
   bool IsContainerCrossSizeDefinite() const;
 
+  NGConstraintSpace BuildSpaceForIntrinsicInlineSize(
+      const NGBlockNode& flex_item) const;
   NGConstraintSpace BuildSpaceForFlexBasis(const NGBlockNode& flex_item) const;
   NGConstraintSpace BuildSpaceForIntrinsicBlockSize(
       const NGBlockNode& flex_item) const;
@@ -116,7 +118,9 @@
              NGFlexLayoutAlgorithm::FlexFractionParts,
              bool>
   FindLargestFractions() const;
+
   MinMaxSizesResult ComputeMinMaxSizeOfSingleLineRowContainer();
+  MinMaxSizesResult ComputeMinMaxSizeOfMultilineColumnContainer();
   // This implements 9.9.3. Flex Item Intrinsic Size Contributions, from
   // https://drafts.csswg.org/css-flexbox/#intrinsic-item-contributions.
   MinMaxSizesResult ComputeItemContributions(const NGConstraintSpace& space,
@@ -183,6 +187,13 @@
   void CheckFlexLines(HeapVector<NGFlexLine>& flex_line_outputs) const;
 #endif
 
+  // These are used when determining the max-content width of a column-wrap flex
+  // container. Note: |item_inline_available_size_override_| has to be
+  // initialized before is_cross_size_definite_, and probably before some other
+  // data members too.
+  LogicalSize ChildAvailableSize() const;
+  absl::optional<LayoutUnit> item_inline_available_size_override_;
+
   const bool is_column_;
   const bool is_horizontal_flow_;
   const bool is_cross_size_definite_;
diff --git a/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc b/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc
index 1279ab3..266ed62 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_box_fragment_builder.cc
@@ -302,7 +302,7 @@
   DCHECK(object.Parent()->IsFlexibleBox());
   DCHECK(object.Parent()->IsOutOfFlowPositioned());
   for (wtf_size_t idx = 0; idx < children_.size(); idx++) {
-    const ChildWithOffset& child = children_[idx];
+    const NGLogicalLink& child = children_[idx];
     if (child.fragment->GetLayoutObject() == &object) {
       children_.EraseAt(idx);
       return;
@@ -502,7 +502,7 @@
     WritingMode block_or_line_writing_mode) {
 #if DCHECK_IS_ON()
   if (ItemsBuilder()) {
-    for (const ChildWithOffset& child : Children()) {
+    for (const NGLogicalLink& child : Children()) {
       DCHECK(child.fragment);
       const NGPhysicalFragment& fragment = *child.fragment;
       DCHECK(fragment.IsLineBox() ||
diff --git a/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.cc
index c65cbb6..905aa2d 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm.cc
@@ -845,8 +845,8 @@
       NGOutOfFlowLayoutPart::ColumnBalancingInfo column_balancing_info;
       for (wtf_size_t i = 0; i < new_columns.size(); i++) {
         auto& new_column = new_columns[i];
-        column_balancing_info.columns.emplace_back(new_column.offset,
-                                                   &new_column.Fragment());
+        column_balancing_info.columns.push_back(
+            NGLogicalLink{&new_column.Fragment(), new_column.offset});
 
         // Because the current set of columns haven't been added to the builder
         // yet, any OOF descendants won't have been propagated up yet. Instead,
diff --git a/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.cc b/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.cc
index 2a2df24..3378991 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.cc
@@ -26,17 +26,12 @@
 
 }  // namespace
 
-void NGContainerFragmentBuilder::ChildWithOffset::Trace(
-    Visitor* visitor) const {
-  visitor->Trace(fragment);
-}
-
 void NGContainerFragmentBuilder::ReplaceChild(
     wtf_size_t index,
     const NGPhysicalFragment& new_child,
     const LogicalOffset offset) {
   DCHECK_LT(index, children_.size());
-  children_[index] = ChildWithOffset(offset, std::move(&new_child));
+  children_[index] = NGLogicalLink{std::move(&new_child), offset};
 }
 
 // Propagate data in |child| to this fragment. The |child| will then be added as
@@ -178,7 +173,7 @@
   // In order to know where list-markers are within the children list (for the
   // |NGSimplifiedLayoutAlgorithm|) we always place them as the first child.
   if (child->IsListMarker()) {
-    children_.push_front(ChildWithOffset(child_offset, std::move(child)));
+    children_.push_front(NGLogicalLink{std::move(child), child_offset});
     return;
   }
 
@@ -187,13 +182,12 @@
     // ::placeholder earlier.
     const wtf_size_t size = children_.size();
     if (size > 0) {
-      children_.insert(size - 1,
-                       ChildWithOffset(child_offset, std::move(child)));
+      children_.insert(size - 1, NGLogicalLink{std::move(child), child_offset});
       return;
     }
   }
 
-  children_.emplace_back(child_offset, std::move(child));
+  children_.push_back(NGLogicalLink{std::move(child), child_offset});
 }
 
 void NGContainerFragmentBuilder::AddOutOfFlowChildCandidate(
diff --git a/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h b/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h
index 9cd0334..b5acd33 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h
@@ -21,6 +21,7 @@
 #include "third_party/blink/renderer/core/layout/ng/ng_fragment_builder.h"
 #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h"
 #include "third_party/blink/renderer/core/layout/ng/ng_link.h"
+#include "third_party/blink/renderer/core/layout/ng/ng_logical_link.h"
 #include "third_party/blink/renderer/core/layout/ng/ng_out_of_flow_positioned_node.h"
 #include "third_party/blink/renderer/core/style/computed_style.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h"
@@ -48,20 +49,7 @@
     child_break_tokens_.clear();
   }
 
-  struct ChildWithOffset {
-    DISALLOW_NEW();
-    ChildWithOffset(LogicalOffset offset, const NGPhysicalFragment* fragment)
-        : offset(offset), fragment(std::move(fragment)) {}
-
-    void Trace(Visitor*) const;
-
-    // We store logical offsets (instead of the final physical), as we can't
-    // convert into the physical coordinate space until we know our final size.
-    LogicalOffset offset;
-    Member<const NGPhysicalFragment> fragment;
-  };
-
-  using ChildrenVector = HeapVector<ChildWithOffset, 4>;
+  using ChildrenVector = HeapVector<NGLogicalLink, 4>;
   using MulticolCollection =
       HeapHashMap<Member<LayoutBox>,
                   Member<NGMulticolWithPendingOOFs<LogicalOffset>>>;
@@ -476,7 +464,4 @@
 
 }  // namespace blink
 
-WTF_ALLOW_MOVE_INIT_AND_COMPARE_WITH_MEM_FUNCTIONS(
-    blink::NGContainerFragmentBuilder::ChildWithOffset)
-
 #endif  // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_CONTAINER_FRAGMENT_BUILDER_H_
diff --git a/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc b/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc
index 0487048..edd83e1 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_length_utils.cc
@@ -1471,7 +1471,9 @@
   LayoutUnit default_block_size = CalculateDefaultBlockSize(
       constraint_space, node, break_token, border_scrollbar_padding);
   absl::optional<LayoutUnit> inline_size;
-  if (!is_intrinsic) {
+  if (!is_intrinsic &&
+      (!InlineLengthUnresolvable(constraint_space, style.LogicalWidth()) ||
+       constraint_space.IsFixedInlineSize())) {
     inline_size =
         ComputeInlineSizeForFragment(constraint_space, node, border_padding);
 
diff --git a/third_party/blink/renderer/core/layout/ng/ng_link.h b/third_party/blink/renderer/core/layout/ng/ng_link.h
index ac108e0d..1062345e 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_link.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_link.h
@@ -26,7 +26,7 @@
   PhysicalOffset Offset() const { return offset; }
   const NGPhysicalFragment* get() const { return fragment; }
 
-  operator bool() const { return fragment; }
+  explicit operator bool() const { return fragment; }
   const NGPhysicalFragment& operator*() const { return *fragment; }
   const NGPhysicalFragment* operator->() const { return fragment; }
 
diff --git a/third_party/blink/renderer/core/layout/ng/ng_logical_link.h b/third_party/blink/renderer/core/layout/ng/ng_logical_link.h
new file mode 100644
index 0000000..3f0812c
--- /dev/null
+++ b/third_party/blink/renderer/core/layout/ng/ng_logical_link.h
@@ -0,0 +1,37 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_LOGICAL_LINK_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_LOGICAL_LINK_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/layout/geometry/logical_offset.h"
+
+namespace blink {
+
+class NGPhysicalFragment;
+
+// Similar to |NGLink| but with |LogicalOffset| instead of |PhysicalOffset|.
+struct CORE_EXPORT NGLogicalLink {
+  DISALLOW_NEW();
+
+ public:
+  const LogicalOffset& Offset() const { return offset; }
+  const NGPhysicalFragment* get() const { return fragment; }
+
+  explicit operator bool() const { return fragment; }
+  const NGPhysicalFragment& operator*() const { return *fragment; }
+  const NGPhysicalFragment* operator->() const { return fragment; }
+
+  void Trace(Visitor* visitor) const { visitor->Trace(fragment); }
+
+  Member<const NGPhysicalFragment> fragment;
+  LogicalOffset offset;
+};
+
+}  // namespace blink
+
+WTF_ALLOW_MOVE_INIT_AND_COMPARE_WITH_MEM_FUNCTIONS(blink::NGLogicalLink)
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_NG_LOGICAL_LINK_H_
diff --git a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
index b00e30c..1fc26ec0 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_out_of_flow_layout_part.cc
@@ -1042,7 +1042,7 @@
     LayoutUnit additional_column_block_size;
     // Then append the new fragmentainers.
     for (wtf_size_t i = old_fragment_count; i < new_fragment_count; i++) {
-      NGContainerFragmentBuilder::ChildWithOffset child =
+      const NGLogicalLink& child =
           limited_multicol_container_builder.Children()[i];
       algorithm.AppendNewChildFragment(*child.fragment, child.offset);
       additional_column_block_size +=
@@ -2160,7 +2160,7 @@
     // We're currently laying out |containing_block|, and it's a multicol
     // container. Search inside fragmentainer children in the builder.
     auto& children = FragmentationContextChildren();
-    for (const NGContainerFragmentBuilder::ChildWithOffset& child : children) {
+    for (const NGLogicalLink& child : children) {
       if (ReplaceFragmentainerChild(*child.fragment))
         return;
     }
diff --git a/third_party/blink/renderer/core/loader/build.gni b/third_party/blink/renderer/core/loader/build.gni
index 1d056ee5..208e3c0 100644
--- a/third_party/blink/renderer/core/loader/build.gni
+++ b/third_party/blink/renderer/core/loader/build.gni
@@ -140,6 +140,8 @@
   "threadable_loader_client.h",
   "threaded_icon_loader.cc",
   "threaded_icon_loader.h",
+  "url_matcher.cc",
+  "url_matcher.h",
   "web_associated_url_loader_impl.cc",
   "web_associated_url_loader_impl.h",
   "web_bundle/script_web_bundle.cc",
@@ -196,6 +198,7 @@
   "resource_load_observer_for_frame_test.cc",
   "threadable_loader_test.cc",
   "threaded_icon_loader_test.cc",
+  "url_matcher_test.cc",
   "web_associated_url_loader_impl_test.cc",
   "web_bundle/script_web_bundle_rule_test.cc",
 ]
diff --git a/third_party/blink/renderer/core/loader/url_matcher.cc b/third_party/blink/renderer/core/loader/url_matcher.cc
new file mode 100644
index 0000000..131de95
--- /dev/null
+++ b/third_party/blink/renderer/core/loader/url_matcher.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 "third_party/blink/renderer/core/loader/url_matcher.h"
+
+#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
+
+namespace blink {
+
+UrlMatcher::UrlMatcher(const base::StringPiece& encoded_url_list_string) {
+  ParseFieldTrialParam(encoded_url_list_string);
+}
+
+UrlMatcher::~UrlMatcher() = default;
+
+bool UrlMatcher::Match(const KURL& url) const {
+  scoped_refptr<const SecurityOrigin> origin = SecurityOrigin::Create(url);
+  for (const auto& it : url_list_) {
+    // TODO(sisidovski): IsSameOriginWith is more strict but we skip the port
+    // number check in order to avoid hardcoding port numbers to corresponding
+    // WPT test suites. To check port numbers, we need to set them to the
+    // allowlist which is passed by Chrome launch flag or Finch params. But,
+    // WPT server could have multiple ports, and it's difficult to expect which
+    // ports are available and set to the feature params before starting the
+    // test. That will affect the test reliability.
+    if ((origin.get()->Protocol() == it.first->Protocol() &&
+         origin.get()->Host() == it.first->Host())) {
+      // AllowList could only have domain info. In that case the matcher neither
+      // cares path nor query strings.
+      if (!it.second.has_value())
+        return true;
+      // Otherwise check if the path or query contains the string.
+      if (url.GetPath().Contains(it.second.value()) ||
+          url.Query().Contains(it.second.value()))
+        return true;
+    }
+  }
+
+  return false;
+}
+
+void UrlMatcher::ParseFieldTrialParam(
+    const base::StringPiece& encoded_url_list_string) {
+  Vector<String> parsed_strings;
+  String::FromUTF8(encoded_url_list_string)
+      .Split(",", /*allow_empty_entries=*/false, parsed_strings);
+  Vector<String> site_info;
+  for (const auto& it : parsed_strings) {
+    it.Split("|", /*allow_empty_entries=*/false, site_info);
+    DCHECK_LE(site_info.size(), 2u)
+        << "Got unexpected format that UrlMatcher cannot handle: " << it;
+    absl::optional<String> match_string;
+    if (site_info.size() == 2u)
+      match_string = site_info[1];
+    url_list_.push_back(std::make_pair(
+        SecurityOrigin::CreateFromString(site_info[0]), match_string));
+  }
+}
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/loader/url_matcher.h b/third_party/blink/renderer/core/loader/url_matcher.h
new file mode 100644
index 0000000..4a1d3026
--- /dev/null
+++ b/third_party/blink/renderer/core/loader/url_matcher.h
@@ -0,0 +1,53 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_URL_MATCHER_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_URL_MATCHER_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/platform/weborigin/kurl.h"
+#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
+
+namespace blink {
+
+// UrlMatcher is a class to manage the list of URLs stored in the field trial
+// param. As the original data from the field trial params is delivered as a
+// special format string, this class parses and formats it, and stores the list.
+//
+// The expected param format is a comma separated string, and each string is
+// separated by the vertical bar. The left side of the vertical bar is a
+// host name, and the right side is a part of path or search params.
+//
+// The string is something like
+// "https://test.exmaple|/foo,http://another.test.example|?foo=bar,https:://yet.another.test.example"
+// Then the UrlMatcher will parse it to the formatted list like
+// [
+//  ["https://test.example", "/foo"],
+//  ["http://another.test.example", "foo=bar"],
+//  ["https:://yet.another.test.example", ""]
+// ]
+// Based on the above list, UrlMatcher::Match() checks 1) if the given url is a
+// same origin or not, 2) if it's a same origin, check the second value in the
+// list item. If it's an empty string, that means origin-level url matching. If
+// it has a string, check the path string and query string in the given url
+// contain it or not.
+class CORE_EXPORT UrlMatcher final {
+ public:
+  explicit UrlMatcher(const base::StringPiece& encoded_url_list_string);
+  ~UrlMatcher();
+
+  bool Match(const KURL& url) const;
+
+ private:
+  using UrlList = Vector<
+      std::pair<scoped_refptr<const SecurityOrigin>, absl::optional<String>>>;
+  UrlList url_list_;
+
+  void ParseFieldTrialParam(const base::StringPiece& encoded_url_list_string);
+};
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_URL_MATCHER_H_
diff --git a/third_party/blink/renderer/core/loader/url_matcher_test.cc b/third_party/blink/renderer/core/loader/url_matcher_test.cc
new file mode 100644
index 0000000..bb0fed8
--- /dev/null
+++ b/third_party/blink/renderer/core/loader/url_matcher_test.cc
@@ -0,0 +1,38 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/core/loader/url_matcher.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace blink {
+
+TEST(UrlMatcherTest, SingleDomain) {
+  UrlMatcher matcher("https://test.com");
+  EXPECT_TRUE(matcher.Match(KURL("https://test.com/script.js")));
+  EXPECT_FALSE(matcher.Match(KURL("http://test.com/script.js")));
+  EXPECT_FALSE(matcher.Match(KURL("http://another.test.com/script.js")));
+}
+
+TEST(UrlMatcherTest, MultipleDomains) {
+  UrlMatcher matcher("https://test.com,https://another.test.com");
+  KURL url = KURL("https://test.com/script.js");
+  EXPECT_TRUE(matcher.Match(url));
+}
+
+TEST(UrlMatcherTest, WithSeparatorForPathStrings) {
+  UrlMatcher matcher("https://test.com|/foo");
+  EXPECT_TRUE(matcher.Match(KURL("https://test.com/foo")));
+  EXPECT_FALSE(matcher.Match(KURL("https://test.com/bar")));
+  EXPECT_FALSE(matcher.Match(KURL("https://test.com?foo")));
+}
+
+TEST(UrlMatcherTest, WithSeparatorForQueryParams) {
+  UrlMatcher matcher("https://test.com|foo=bar");
+  EXPECT_FALSE(matcher.Match(KURL("https://test.com/foo")));
+  EXPECT_FALSE(matcher.Match(KURL("https://test.com/foo/bar")));
+  EXPECT_TRUE(matcher.Match(KURL("https://test.com?foo=bar")));
+  EXPECT_TRUE(matcher.Match(KURL("https://test.com?a=b&foo=bar")));
+}
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/streams/readable_stream.cc b/third_party/blink/renderer/core/streams/readable_stream.cc
index 091f561..91f1b5eb 100644
--- a/third_party/blink/renderer/core/streams/readable_stream.cc
+++ b/third_party/blink/renderer/core/streams/readable_stream.cc
@@ -1324,17 +1324,20 @@
 // static
 ReadableStream* ReadableStream::CreateByteStream(
     ScriptState* script_state,
-    UnderlyingByteSourceBase* underlying_byte_source,
-    ExceptionState& exception_state) {
+    UnderlyingByteSourceBase* underlying_byte_source) {
   auto* pull_algorithm =
       MakeGarbageCollected<PullAlgorithm>(underlying_byte_source);
   auto* cancel_algorithm =
       MakeGarbageCollected<CancelAlgorithm>(underlying_byte_source);
+
+  // Construction of the byte stream cannot fail because the trivial start
+  // algorithm will not throw.
+  NonThrowableExceptionState exception_state;
   auto* stream =
       CreateByteStream(script_state, CreateTrivialStartAlgorithm(),
                        pull_algorithm, cancel_algorithm, exception_state);
   DCHECK(stream);
-  DCHECK(!exception_state.HadException());
+
   ReadableStreamController* controller = stream->readable_stream_controller_;
   pull_algorithm->SetController(To<ReadableByteStreamController>(controller));
   return stream;
diff --git a/third_party/blink/renderer/core/streams/readable_stream.h b/third_party/blink/renderer/core/streams/readable_stream.h
index e70125e..a3fb5b5 100644
--- a/third_party/blink/renderer/core/streams/readable_stream.h
+++ b/third_party/blink/renderer/core/streams/readable_stream.h
@@ -116,8 +116,7 @@
   // Entry point to create a ReadableByteStream from other C++ APIs.
   static ReadableStream* CreateByteStream(
       ScriptState*,
-      UnderlyingByteSourceBase* underlying_byte_source,
-      ExceptionState&);
+      UnderlyingByteSourceBase* underlying_byte_source);
 
   // CreateReadableByteStream():
   // https://streams.spec.whatwg.org/#abstract-opdef-createreadablebytestream
diff --git a/third_party/blink/renderer/core/streams/readable_stream_test.cc b/third_party/blink/renderer/core/streams/readable_stream_test.cc
index 9744aef2..427ecce 100644
--- a/third_party/blink/renderer/core/streams/readable_stream_test.cc
+++ b/third_party/blink/renderer/core/streams/readable_stream_test.cc
@@ -595,10 +595,9 @@
   ReadableStream* Stream() const { return stream_; }
 
   void Init(ScriptState* script_state,
-            UnderlyingByteSourceBase* underlying_byte_source,
-            ExceptionState& exception_state) {
-    stream_ = ReadableStream::CreateByteStream(
-        script_state, underlying_byte_source, exception_state);
+            UnderlyingByteSourceBase* underlying_byte_source) {
+    stream_ =
+        ReadableStream::CreateByteStream(script_state, underlying_byte_source);
   }
 
   // This takes the |stream| property of ReadableStream and copies it onto the
@@ -684,8 +683,7 @@
 TEST_F(ReadableByteStreamTest, Construct) {
   V8TestingScope scope;
   Init(scope.GetScriptState(),
-       MakeGarbageCollected<TestUnderlyingByteSource>(scope.GetScriptState()),
-       ASSERT_NO_EXCEPTION);
+       MakeGarbageCollected<TestUnderlyingByteSource>(scope.GetScriptState()));
   EXPECT_TRUE(Stream());
 }
 
@@ -693,7 +691,7 @@
   V8TestingScope scope;
   auto* mock =
       MakeGarbageCollected<MockUnderlyingByteSource>(scope.GetScriptState());
-  Init(scope.GetScriptState(), mock, ASSERT_NO_EXCEPTION);
+  Init(scope.GetScriptState(), mock);
   // Need to run microtasks so the startAlgorithm promise resolves.
   v8::MicrotasksScope::PerformCheckpoint(scope.GetIsolate());
   CopyStreamToGlobal(scope);
@@ -713,7 +711,7 @@
   V8TestingScope scope;
   auto* mock =
       MakeGarbageCollected<MockUnderlyingByteSource>(scope.GetScriptState());
-  Init(scope.GetScriptState(), mock, ASSERT_NO_EXCEPTION);
+  Init(scope.GetScriptState(), mock);
   // Need to run microtasks so the startAlgorithm promise resolves.
   v8::MicrotasksScope::PerformCheckpoint(scope.GetIsolate());
   CopyStreamToGlobal(scope);
@@ -770,8 +768,7 @@
   V8TestingScope scope;
   auto* script_state = scope.GetScriptState();
   Init(script_state,
-       MakeGarbageCollected<ThrowFromPullUnderlyingByteSource>(script_state),
-       ASSERT_NO_EXCEPTION);
+       MakeGarbageCollected<ThrowFromPullUnderlyingByteSource>(script_state));
 
   auto* reader =
       Stream()->GetBYOBReaderForTesting(script_state, ASSERT_NO_EXCEPTION);
@@ -801,8 +798,7 @@
   V8TestingScope scope;
   auto* script_state = scope.GetScriptState();
   Init(script_state,
-       MakeGarbageCollected<ThrowFromCancelUnderlyingByteSource>(script_state),
-       ASSERT_NO_EXCEPTION);
+       MakeGarbageCollected<ThrowFromCancelUnderlyingByteSource>(script_state));
 
   auto* reader =
       Stream()->GetBYOBReaderForTesting(script_state, ASSERT_NO_EXCEPTION);
diff --git a/third_party/blink/renderer/core/style/style_initial_data.cc b/third_party/blink/renderer/core/style/style_initial_data.cc
index 96abc76..077b3d4 100644
--- a/third_party/blink/renderer/core/style/style_initial_data.cc
+++ b/third_party/blink/renderer/core/style/style_initial_data.cc
@@ -26,6 +26,9 @@
     variables_.SetData(entry.key, std::move(computed_initial_data));
     variables_.SetValue(entry.key, computed_initial_value);
   }
+
+  viewport_unit_flags_ = registry.GetViewportUnitFlags();
+  document.AddViewportUnitFlags(viewport_unit_flags_);
 }
 
 bool StyleInitialData::operator==(const StyleInitialData& other) const {
diff --git a/third_party/blink/renderer/core/style/style_initial_data.h b/third_party/blink/renderer/core/style/style_initial_data.h
index fcbd9c37..664d3134 100644
--- a/third_party/blink/renderer/core/style/style_initial_data.h
+++ b/third_party/blink/renderer/core/style/style_initial_data.h
@@ -49,6 +49,8 @@
     return variables_.GetValue(name).value_or(nullptr);
   }
 
+  unsigned GetViewportUnitFlags() const { return viewport_unit_flags_; }
+
  private:
   StyleInitialData(Document&, const PropertyRegistry&);
 
@@ -56,6 +58,19 @@
   // the initial style, and then shared with all other styles that directly or
   // indirectly inherit from that.
   StyleVariables variables_;
+  // This is equal to `PropertyRegistry::GetViewportUnitFlags()` at the time the
+  // `StyleInitialData` was created.
+  //
+  // Since StyleInitialData is only (re)created during style resolution, this
+  // tells us whether ComputedStyles from that process depend on viewport units
+  // or not, which in turns tells us if we need to recalculate any styles when
+  // we resize.
+  //
+  // PropertyRegistry::GetViewportUnitFlags on the other hand, can change
+  // immediately via JavaScript, and is also affected by active style updates.
+  // Hence this is not useful for understanding whether or not any current
+  // ComputedStyles need to be invalidated by a resize.
+  unsigned viewport_unit_flags_ = 0;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
index 61ea6e5..d57636d 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_node_object.cc
@@ -4587,8 +4587,10 @@
   // objects may have been deferred by display-locking.
   Document* document = GetDocument();
   Node* node = GetNode();
-  if (document && node)
-    document->UpdateStyleAndLayoutTreeForNode(node);
+  if (!document || !node)
+    return false;
+
+  document->UpdateStyleAndLayoutTreeForNode(node);
 
   if (!CanSetFocusAttribute())
     return false;
@@ -5378,7 +5380,8 @@
     const AXObject* datetime_ancestor = DatetimeAncestor();
     ax::mojom::blink::NameFrom datetime_ancestor_name_from;
     datetime_ancestor->GetName(datetime_ancestor_name_from, nullptr);
-    description_objects->clear();
+    if (description_objects)
+      description_objects->clear();
     String ancestor_description = DatetimeAncestor()->Description(
         datetime_ancestor_name_from, description_from, description_objects);
     if (!result.IsEmpty() && !ancestor_description.IsEmpty())
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.cc b/third_party/blink/renderer/modules/accessibility/ax_object.cc
index e2ca1e9..b48987fe 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.cc
@@ -509,7 +509,7 @@
     uint32_t max_len = kMaxStringAttributeLength) {
   if (value.IsEmpty())
     return;
-  std::string value_utf8 = value.Utf8();
+  std::string value_utf8 = value.Utf8(kStrictUTF8Conversion);
   if (value_utf8.size() > max_len) {
     std::string truncated;
     base::TruncateUTF8ToByteSize(value_utf8, max_len, &truncated);
@@ -2960,6 +2960,9 @@
 
   while (object && !object->IsAXNodeObject())
     object = object->ParentObject();
+
+  DCHECK(object);
+
   Node* node = object->GetNode();
   auto* element = DynamicTo<Element>(node);
   if (!element)
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
index b664207..c764bc70 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
@@ -931,7 +931,7 @@
       // Layout object is irrelevant, but node object can still be relevant.
       if (!node_id) {
         DCHECK(layout_id);  // One of of node_id, layout_id is non-zero.
-        Invalidate(layout_object->GetDocument(), layout_id);
+        Invalidate(node->GetDocument(), layout_id);
       } else {
         layout_object = nullptr;
         layout_id = 0;
@@ -943,7 +943,7 @@
     // Change from AXLayoutObject -> AXNodeObject.
     // The node is in a display locked subtree, but we've previously put it in
     // the cache with its layout object.
-    Invalidate(layout_object->GetDocument(), layout_id);
+    Invalidate(node->GetDocument(), layout_id);
   } else if (layout_object && node_id && !layout_id && !IsDisplayLocked(node)) {
     // Change from AXNodeObject -> AXLayoutObject.
     // Has a layout object but no layout_id, meaning that when the AXObject was
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_track_impl.cc b/third_party/blink/renderer/modules/mediastream/media_stream_track_impl.cc
index 42fb8106..d1d098a8 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_track_impl.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_track_impl.cc
@@ -954,7 +954,7 @@
           message.Utf8().c_str(), kind().Utf8().c_str(), id().Utf8().c_str(),
           label().Utf8().c_str(), enabled() ? "true" : "false",
           muted() ? "true" : "false", readyState().Utf8().c_str(),
-          component_->Source()->Remote() ? "true" : "false")
+          component_->Remote() ? "true" : "false")
           .Utf8());
 }
 
diff --git a/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_compositor.cc b/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_compositor.cc
index efe69f0..146613e 100644
--- a/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_compositor.cc
+++ b/third_party/blink/renderer/modules/mediastream/webmediaplayer_ms_compositor.cc
@@ -214,7 +214,7 @@
     video_components = media_stream_descriptor->VideoComponents();
 
   const bool remote_video =
-      video_components.size() && video_components[0]->Source()->Remote();
+      video_components.size() && video_components[0]->Remote();
 
   if (remote_video && Platform::Current()->RTCSmoothnessAlgorithmEnabled()) {
     base::AutoLock auto_lock(current_frame_lock_);
diff --git a/third_party/blink/renderer/platform/heap/test/incremental_marking_test.cc b/third_party/blink/renderer/platform/heap/test/incremental_marking_test.cc
index f39f776f..82db5bf7 100644
--- a/third_party/blink/renderer/platform/heap/test/incremental_marking_test.cc
+++ b/third_party/blink/renderer/platform/heap/test/incremental_marking_test.cc
@@ -6,6 +6,7 @@
 
 #include "base/bind.h"
 #include "base/test/scoped_feature_list.h"
+#include "build/buildflag.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h"
@@ -1338,7 +1339,14 @@
 size_t DestructedAndTraced::n_destructed = 0;
 size_t DestructedAndTraced::n_traced = 0;
 
-TEST_F(IncrementalMarkingTest, ConservativeGCOfWeakContainer) {
+// Flaky <https://crbug.com/1351511>.
+#if BUILDFLAG(IS_LINUX)
+#define MAYBE_ConservativeGCOfWeakContainer \
+  DISABLED_ConservativeGCOfWeakContainer
+#else
+#define MAYBE_ConservativeGCOfWeakContainer ConservativeGCOfWeakContainer
+#endif
+TEST_F(IncrementalMarkingTest, MAYBE_ConservativeGCOfWeakContainer) {
   // Regression test: https://crbug.com/1108676
   //
   // Test ensures that on-stack references to weak containers (e.g. iterators)
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_component.h b/third_party/blink/renderer/platform/mediastream/media_stream_component.h
index 87b05e6..2bbb7af 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_component.h
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_component.h
@@ -64,6 +64,7 @@
   virtual String Id() const = 0;
   // Uniquely identifies this component.
   virtual int UniqueId() const = 0;
+  virtual bool Remote() const = 0;
   virtual bool Enabled() const = 0;
   virtual void SetEnabled(bool enabled) = 0;
   virtual WebMediaStreamTrack::ContentHintType ContentHint() = 0;
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_component_impl.h b/third_party/blink/renderer/platform/mediastream/media_stream_component_impl.h
index 1c141dd9..1e3d99a 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_component_impl.h
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_component_impl.h
@@ -42,6 +42,7 @@
 #include "third_party/blink/renderer/platform/heap/prefinalizer.h"
 #include "third_party/blink/renderer/platform/mediastream/media_constraints.h"
 #include "third_party/blink/renderer/platform/mediastream/media_stream_component.h"
+#include "third_party/blink/renderer/platform/mediastream/media_stream_source.h"
 #include "third_party/blink/renderer/platform/mediastream/media_stream_track_platform.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
@@ -49,7 +50,6 @@
 
 namespace blink {
 
-class MediaStreamSource;
 class WebLocalFrame;
 
 class PLATFORM_EXPORT MediaStreamComponentImpl final
@@ -82,6 +82,7 @@
 
   String Id() const override { return id_; }
   int UniqueId() const override { return unique_id_; }
+  bool Remote() const override { return Source()->Remote(); }
   bool Enabled() const override { return enabled_; }
   void SetEnabled(bool enabled) override { enabled_ = enabled; }
   WebMediaStreamTrack::ContentHintType ContentHint() override {
diff --git a/third_party/blink/renderer/platform/mediastream/transferred_media_stream_component.cc b/third_party/blink/renderer/platform/mediastream/transferred_media_stream_component.cc
index 623b779e9..0adc6d5c 100644
--- a/third_party/blink/renderer/platform/mediastream/transferred_media_stream_component.cc
+++ b/third_party/blink/renderer/platform/mediastream/transferred_media_stream_component.cc
@@ -45,6 +45,14 @@
   return 0;
 }
 
+bool TransferredMediaStreamComponent::Remote() const {
+  if (component_) {
+    return component_->Remote();
+  }
+  // TODO(crbug.com/1288839): Return the transferred value
+  return false;
+}
+
 bool TransferredMediaStreamComponent::Enabled() const {
   if (component_) {
     return component_->Enabled();
diff --git a/third_party/blink/renderer/platform/mediastream/transferred_media_stream_component.h b/third_party/blink/renderer/platform/mediastream/transferred_media_stream_component.h
index b09dda6..370ed50 100644
--- a/third_party/blink/renderer/platform/mediastream/transferred_media_stream_component.h
+++ b/third_party/blink/renderer/platform/mediastream/transferred_media_stream_component.h
@@ -39,6 +39,7 @@
 
   String Id() const override;
   int UniqueId() const override;
+  bool Remote() const override;
   bool Enabled() const override;
   void SetEnabled(bool enabled) override;
   WebMediaStreamTrack::ContentHintType ContentHint() override;
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 54908d9..ebd68bd 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -308,10 +308,6 @@
       status: "experimental",
     },
     {
-      name: "CancelFormSubmissionInDefaultHandler",
-      status: "stable",
-    },
-    {
       name: "Canvas2dImageChromium",
     },
     {
@@ -584,7 +580,7 @@
     },
     {
       name: "CSSIcUnit",
-      status: "experimental",
+      status: "stable",
     },
     {
       name: "CSSIndependentTransformProperties",
diff --git a/third_party/blink/web_tests/FlagExpectations/disable-site-isolation-trials b/third_party/blink/web_tests/FlagExpectations/disable-site-isolation-trials
index 891eb45..2d039a5 100644
--- a/third_party/blink/web_tests/FlagExpectations/disable-site-isolation-trials
+++ b/third_party/blink/web_tests/FlagExpectations/disable-site-isolation-trials
@@ -49,6 +49,7 @@
 crbug.com/1209223 external/wpt/html/browsers/browsing-the-web/navigating-across-documents/javascript-url-security-check-same-origin-domain.sub.html [ Failure ]
 
 # ====== New tests from wpt-importer added here ======
+crbug.com/626703 external/wpt/custom-elements/form-associated/ElementInternals-target-element-is-held-strongly.html [ Timeout ]
 crbug.com/626703 external/wpt/custom-elements/throw-on-dynamic-markup-insertion-counter-construct-xml-parser.xhtml [ Crash ]
 crbug.com/626703 external/wpt/custom-elements/throw-on-dynamic-markup-insertion-counter-reactions-xml-parser.xhtml [ Crash ]
 crbug.com/626703 external/wpt/css/css-sizing/aspect-ratio/grid-aspect-ratio-040.html [ Crash ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index bac317db..48b566f 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -1478,6 +1478,14 @@
 crbug.com/240765 external/wpt/css/css-flexbox/intrinsic-size/row-005.html [ Failure ]
 crbug.com/240765 external/wpt/css/css-flexbox/intrinsic-size/row-007.html [ Failure ]
 crbug.com/240765 external/wpt/css/css-flexbox/intrinsic-size/row-008.html [ Failure ]
+crbug.com/240765 external/wpt/css/css-flexbox/intrinsic-size/col-wrap-001.html [ Failure ]
+crbug.com/240765 external/wpt/css/css-flexbox/intrinsic-size/col-wrap-002.html [ Failure ]
+crbug.com/240765 external/wpt/css/css-flexbox/intrinsic-size/col-wrap-003.html [ Failure ]
+crbug.com/240765 external/wpt/css/css-flexbox/intrinsic-size/col-wrap-005.html [ Failure ]
+crbug.com/240765 external/wpt/css/css-flexbox/intrinsic-size/col-wrap-006.html [ Failure ]
+crbug.com/240765 external/wpt/css/css-flexbox/intrinsic-size/col-wrap-007.html [ Failure ]
+crbug.com/240765 external/wpt/css/css-flexbox/intrinsic-size/col-wrap-008.html [ Failure ]
+crbug.com/240765 external/wpt/css/css-flexbox/intrinsic-size/col-wrap-009.html [ Failure ]
 
 # These require css-align-3 positional keywords that Blink doesn't yet support for flexbox.
 crbug.com/1011718 external/wpt/css/css-flexbox/flexbox-safe-overflow-position-001.html [ Failure ]
@@ -2395,7 +2403,7 @@
 crbug.com/922618 external/wpt/html/semantics/document-metadata/the-link-element/link-multiple-load-events.html [ Timeout ]
 
 # crbug.com/1095379: These are flaky-slow, but are already present in SlowTests
-crbug.com/73609 http/tests/media/video-play-stall.html [ Pass Timeout Failure ]
+crbug.com/73609 http/tests/media/video-play-stall.html [ Failure Pass Timeout ]
 crbug.com/518987 http/tests/xmlhttprequest/navigation-abort-detaches-frame.html [ Pass Timeout ]
 crbug.com/538717 [ Win ] http/tests/permissions/chromium/test-request-worker.html [ Pass Timeout ]
 crbug.com/829740 fast/history/history-back-twice-with-subframes-assert.html [ Pass Timeout ]
@@ -3365,8 +3373,8 @@
 crbug.com/626703 [ Win ] virtual/partitioned-cookies/http/tests/inspector-protocol/network/disabled-cache-navigation.js [ Failure ]
 
 # ====== New tests from wpt-importer added here ======
-crbug.com/626703 [ Win10.20h2 ] external/wpt/custom-elements/form-associated/ElementInternals-target-element-is-held-strongly.html [ Timeout ]
-crbug.com/626703 [ Mac11-arm64 ] external/wpt/html/browsers/browsing-the-web/remote-context-helper-tests/navigation-same-document.window.html [ Timeout Failure ]
+crbug.com/626703 external/wpt/custom-elements/form-associated/ElementInternals-target-element-is-held-strongly.html [ Timeout ]
+crbug.com/626703 [ Mac11-arm64 ] external/wpt/html/browsers/browsing-the-web/remote-context-helper-tests/navigation-same-document.window.html [ Failure Timeout ]
 crbug.com/626703 external/wpt/custom-elements/throw-on-dynamic-markup-insertion-counter-construct-xml-parser.xhtml [ Crash ]
 crbug.com/626703 [ Linux ] external/wpt/custom-elements/throw-on-dynamic-markup-insertion-counter-reactions-xml-parser.xhtml [ Crash ]
 crbug.com/626703 [ Mac10.15 ] external/wpt/custom-elements/throw-on-dynamic-markup-insertion-counter-reactions-xml-parser.xhtml [ Crash ]
diff --git a/third_party/blink/web_tests/android/WebLayerWPTOverrideExpectations b/third_party/blink/web_tests/android/WebLayerWPTOverrideExpectations
index f0c44ac9..c8fd002c 100644
--- a/third_party/blink/web_tests/android/WebLayerWPTOverrideExpectations
+++ b/third_party/blink/web_tests/android/WebLayerWPTOverrideExpectations
@@ -1261,7 +1261,6 @@
 crbug.com/1050754 external/wpt/paint-timing/with-first-paint/first-contentful-paint.html [ Pass ]
 crbug.com/1050754 external/wpt/paint-timing/with-first-paint/mask-image.html [ Pass ]
 crbug.com/1050754 external/wpt/paint-timing/with-first-paint/sibling-painting-first-image.html [ Failure ]
-crbug.com/1050754 external/wpt/payment-handler/can-make-payment-event.https.html [ Failure Timeout ]
 crbug.com/1050754 external/wpt/payment-handler/respond-with-minimal-ui.https.html [ Failure Timeout ]
 crbug.com/1050754 external/wpt/payment-request/constructor_convert_method_data.https.html [ Pass Timeout ]
 crbug.com/1050754 external/wpt/payment-request/payment-request-hasenrolledinstrument-method-protection.tentative.https.html [ Pass Timeout ]
diff --git a/third_party/blink/web_tests/android/WebviewWPTExpectations b/third_party/blink/web_tests/android/WebviewWPTExpectations
index cca22cf..3d6f265 100644
--- a/third_party/blink/web_tests/android/WebviewWPTExpectations
+++ b/third_party/blink/web_tests/android/WebviewWPTExpectations
@@ -3694,7 +3694,6 @@
 crbug.com/1050754 external/wpt/paint-timing/fcp-only/fcp-text-input.html [ Failure ]
 crbug.com/1050754 external/wpt/paint-timing/fcp-only/fcp-video-frame.html [ Failure ]
 crbug.com/1050754 external/wpt/paint-timing/fcp-only/fcp-video-poster.html [ Failure ]
-crbug.com/1050754 external/wpt/payment-handler/can-make-payment-event.https.html [ Failure Pass ]
 crbug.com/1050754 external/wpt/payment-handler/idlharness.https.any.serviceworker.html [ Failure Pass ]
 crbug.com/1050754 external/wpt/payment-handler/idlharness.https.any.sharedworker.html [ Failure ]
 crbug.com/1050754 external/wpt/payment-handler/idlharness.https.any.worker.html [ Failure Pass ]
diff --git a/third_party/blink/web_tests/external/Version b/third_party/blink/web_tests/external/Version
index d035e34..206a814 100644
--- a/third_party/blink/web_tests/external/Version
+++ b/third_party/blink/web_tests/external/Version
@@ -1 +1 @@
-Version: c4ad1de47c85a65efde93aae9362359475fac31a
+Version: c1783313cda795ef118c6974015ee47de58a0f80
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 7111446b..f48ee71 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
@@ -113553,6 +113553,97 @@
       ]
      ],
      "intrinsic-size": {
+      "col-wrap-001.html": [
+       "45da5123896a7b6ebd430b1ea0b67f1965771ea7",
+       [
+        null,
+        [
+         [
+          "/css/reference/ref-filled-green-100px-square.xht",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
+      "col-wrap-002.html": [
+       "0e61cf7846213a7237e574a248965fcd40695889",
+       [
+        null,
+        [
+         [
+          "/css/reference/ref-filled-green-100px-square.xht",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
+      "col-wrap-003.html": [
+       "518bf9f65844e00998b7154f074b14d93e71df40",
+       [
+        null,
+        [
+         [
+          "/css/reference/ref-filled-green-100px-square.xht",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
+      "col-wrap-006.html": [
+       "52560057c8327cabd99fa7c7813ec630db42e7ec",
+       [
+        null,
+        [
+         [
+          "/css/reference/ref-filled-green-100px-square.xht",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
+      "col-wrap-007.html": [
+       "7f50f504569fa7e0a40e95fb881970f8145a6489",
+       [
+        null,
+        [
+         [
+          "/css/reference/ref-filled-green-100px-square.xht",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
+      "col-wrap-008.html": [
+       "5edde9495d1382e774cd295cabf81766b0b41a7b",
+       [
+        null,
+        [
+         [
+          "/css/reference/ref-filled-green-100px-square.xht",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
+      "col-wrap-009.html": [
+       "62514dbcffea07f87bf01930dd45e00d2e6a6af1",
+       [
+        null,
+        [
+         [
+          "/css/reference/ref-filled-green-100px-square.xht",
+          "=="
+         ]
+        ],
+        {}
+       ]
+      ],
       "row-001.html": [
        "a681d07d58a15559cc6594fb045ff9c42c6981b8",
        [
@@ -292948,7 +293039,7 @@
       []
      ],
      "compute-kind-widget-no-fallback-ref.html": [
-      "241ecf93751bc3db73c4c586a6d695e0cdc51e7a",
+      "c0e56dc051000b907d5c7b66f9a014470c14c685",
       []
      ],
      "input-security-auto-sensitive-text-input-ref.html": [
@@ -300126,7 +300217,7 @@
      "non-cancelable-when-passive": {
       "resources": {
        "scrolling.js": [
-        "1e96a7f49715db0822867727a1fcb14a30fdad48",
+        "43c693857928da167be4d0b163abde5acf2ede69",
         []
        ],
        "touching.js": [
@@ -322272,6 +322363,14 @@
      "largest-contentful-paint-helpers.js": [
       "5012faf3b1be3388b9e342af5ee25265e4c3de22",
       []
+     ],
+     "lcp-sw-from-cache.js": [
+      "c650a0b7471bf499a00c2afdbd596b9688284dd1",
+      []
+     ],
+     "lcp-sw.https.html": [
+      "069a50eae9a90bce2f8fb1022fed5bd72b5a5d17",
+      []
      ]
     }
    },
@@ -322344,7 +322443,7 @@
     ]
    },
    "lint.ignore": [
-    "6c93ccafb44b1fe2e58e69e4dd2aac166df9de64",
+    "1f6f823f704c809133a31d321f67ea82b2cf9fb4",
     []
    ],
    "loading": {
@@ -328086,11 +328185,11 @@
       []
      ],
      "generate-report-once.py": [
-      "076d5008751f5083353214362506bdf123a22919",
+      "163846a4b962c156442f4375dfec3168ee705c74",
       []
      ],
      "generate-report.https.sub.html": [
-      "ee40c46e3870db95c679c82263c8ed79bceff09b",
+      "f1f4e96300653af6c169ed41e42d3aaff144ebae",
       []
      ],
      "middle-frame.https.sub.html": [
@@ -379785,6 +379884,20 @@
       ]
      ],
      "intrinsic-size": {
+      "col-wrap-004.html": [
+       "00cfa520690374562242cbca8fa19c0e1ca44520",
+       [
+        null,
+        {}
+       ]
+      ],
+      "col-wrap-005.html": [
+       "a753619c62f51b85fa0b51f0cb058bec589707c5",
+       [
+        null,
+        {}
+       ]
+      ],
       "row-005.html": [
        "0f34c6fbdb400c35af723c8be56c22bbe1612e96",
        [
@@ -390729,6 +390842,13 @@
         {}
        ]
       ],
+      "contain-intrinsic-size-030.html": [
+       "8acfabbf618290d83f6399db825d0710749173f4",
+       [
+        null,
+        {}
+       ]
+      ],
       "contain-intrinsic-size-logical-003.html": [
        "59ae8328f2dfaba25094bdf90cf6c0bc1abbdee0",
        [
@@ -492637,6 +492757,13 @@
       {}
      ]
     ],
+    "image-sw-same-origin.https.html": [
+     "3f375008c535a68f339f750eff2cc91bfc07ddcb",
+     [
+      null,
+      {}
+     ]
+    ],
     "image-upscaling.html": [
      "a4f7d8079d3b9a75f79b896743cbc018cc81c9ce",
      [
@@ -505592,10 +505719,12 @@
      ]
     ],
     "can-make-payment-event.https.html": [
-     "b2016a05ec34fd8148a1b43a102ca3f1b51b6526",
+     "28c001c654e1445dfa00193269fabadbf17fc2f0",
      [
       null,
-      {}
+      {
+       "testdriver": true
+      }
      ]
     ],
     "idlharness.https.any.js": [
@@ -508543,12 +508672,11 @@
      ]
     ],
     "pointerevent_touch-action-inherit_parent-none_touch.html": [
-     "e8c205a042f283296c39d0d249739a2b0d23afe0",
+     "cf9e2f7e4e5c8d9128be453f47e2ef98e1bfa445",
      [
       null,
       {
-       "testdriver": true,
-       "timeout": "long"
+       "testdriver": true
       }
      ]
     ],
@@ -508563,7 +508691,7 @@
      ]
     ],
     "pointerevent_touch-action-modified_touch.html": [
-     "c53264d103da3c7a19664a9053184c43f93930bd",
+     "a91022504a7ebf8dee21a368b5911285ef614e1c",
      [
       null,
       {
@@ -521947,7 +522075,7 @@
      ]
     ],
     "document-reporting-default-endpoint.https.sub.html": [
-     "24c1216afaa99917c0afd84f6e3e09ac4ab63089",
+     "f1951e3469b90ec7040b7c2e6a9d2c0178d275b2",
      [
       null,
       {
@@ -521984,7 +522112,7 @@
      ]
     ],
     "document-reporting-path-absolute.https.sub.html": [
-     "e23dd85e7c4c5efa1aae87cdd85a5fb1ed143680",
+     "48be010a00be6db990a118f58543a900cb1da259",
      [
       null,
       {
@@ -552670,7 +552798,7 @@
      ]
     ],
     "audioDecoder-codec-specific.https.any.js": [
-     "994aaf1d17b73428eaf6b316aa73fd76e6e29b40",
+     "b53965c529d475c6fdc8df8061936f14391013ce",
      [
       "webcodecs/audioDecoder-codec-specific.https.any.html?adts_aac",
       {
@@ -554750,7 +554878,7 @@
      ]
     ],
     "videoDecoder-codec-specific.https.any.js": [
-     "f94bee82d18de1da1fa9d685005baac5327402d8",
+     "fab0b24220dc9be9f34186c851dc20fd21ee1c57",
      [
       "webcodecs/videoDecoder-codec-specific.https.any.html?av1",
       {
@@ -591812,6 +591940,13 @@
        null,
        {}
       ]
+     ],
+     "nested-multicol-with-spanner-and-oof-crash-006.html": [
+      "b969f925d3fa132a332fca462917f1f893973f05",
+      [
+       null,
+       {}
+      ]
      ]
     },
     "css-counter-styles": {
diff --git a/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/custom-property-style-queries.html b/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/custom-property-style-queries.html
new file mode 100644
index 0000000..aec3f9a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/custom-property-style-queries.html
@@ -0,0 +1,96 @@
+<!doctype html>
+<title>CSS Container Queries Test: custom property style queries</title>
+<link rel="help" href="https://drafts.csswg.org/css-contain-3/#style-container">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/cq-testcommon.js"></script>
+<style>
+  #outer {
+    container-name: outer;
+    --inner: false;
+    --outer: true;
+    --inner-no-space:false;
+    --outer-no-space:true;
+    --inner-space-after:false ;
+    --outer-space-after:true ;
+  }
+  #inner {
+    --inner: true;
+    --outer: false;
+    --inner-no-space:true;
+    --outer-no-space:false;
+    --inner-space-after:true ;
+    --outer-space-after:false ;
+  }
+</style>
+<div id="outer">
+  <div id="inner">
+    <div id="target"></div>
+  </div>
+</div>
+<script>
+  function test_evaluation(query, expected) {
+    test((t) => {
+      let style_node = document.createElement('style');
+      t.add_cleanup(() => {
+        style_node.remove();
+      });
+      style_node.innerText = `@container ${query} { #target { --applied:true; } }`;
+
+      assert_equals(getComputedStyle(target).getPropertyValue('--applied'), '');
+      document.head.append(style_node);
+      assert_equals(getComputedStyle(target).getPropertyValue('--applied'), expected ? 'true' : '');
+    }, `${query}`);
+  }
+
+  setup(() => assert_implements_container_queries());
+
+  test_evaluation('style(--inner: true)', true);
+  test_evaluation('style(--inner:true)', true);
+  test_evaluation('style(--inner:true )', true);
+  test_evaluation('style(--inner: true )', true);
+  test_evaluation('style(--inner-no-space: true)', true);
+  test_evaluation('style(--inner-no-space:true)', true);
+  test_evaluation('style(--inner-no-space:true )', true);
+  test_evaluation('style(--inner-no-space: true )', true);
+  test_evaluation('style(--inner-space-after: true)', true);
+  test_evaluation('style(--inner-space-after:true)', true);
+  test_evaluation('style(--inner-space-after:true )', true);
+  test_evaluation('style(--inner-space-after: true )', true);
+  test_evaluation('style(--outer: true)', false);
+  test_evaluation('style(--outer:true)', false);
+  test_evaluation('style(--outer:true )', false);
+  test_evaluation('style(--outer: true )', false);
+  test_evaluation('style(--outer-no-space: true)', false);
+  test_evaluation('style(--outer-no-space:true)', false);
+  test_evaluation('style(--outer-no-space:true )', false);
+  test_evaluation('style(--outer-no-space: true )', false);
+  test_evaluation('style(--outer-space-after: true)', false);
+  test_evaluation('style(--outer-space-after:true)', false);
+  test_evaluation('style(--outer-space-after:true )', false);
+  test_evaluation('style(--outer-space-after: true )', false);
+  test_evaluation('outer style(--inner: true)', false);
+  test_evaluation('outer style(--inner:true)', false);
+  test_evaluation('outer style(--inner:true )', false);
+  test_evaluation('outer style(--inner: true )', false);
+  test_evaluation('outer style(--inner-no-space: true)', false);
+  test_evaluation('outer style(--inner-no-space:true)', false);
+  test_evaluation('outer style(--inner-no-space:true )', false);
+  test_evaluation('outer style(--inner-no-space: true )', false);
+  test_evaluation('outer style(--inner-space-after: true)', false);
+  test_evaluation('outer style(--inner-space-after:true)', false);
+  test_evaluation('outer style(--inner-space-after:true )', false);
+  test_evaluation('outer style(--inner-space-after: true )', false);
+  test_evaluation('outer style(--outer: true)', true);
+  test_evaluation('outer style(--outer:true)', true);
+  test_evaluation('outer style(--outer:true )', true);
+  test_evaluation('outer style(--outer: true )', true);
+  test_evaluation('outer style(--outer-no-space: true)', true);
+  test_evaluation('outer style(--outer-no-space:true)', true);
+  test_evaluation('outer style(--outer-no-space:true )', true);
+  test_evaluation('outer style(--outer-no-space: true )', true);
+  test_evaluation('outer style(--outer-space-after: true)', true);
+  test_evaluation('outer style(--outer-space-after:true)', true);
+  test_evaluation('outer style(--outer-space-after:true )', true);
+  test_evaluation('outer style(--outer-space-after: true )', true);
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/intrinsic-size/col-wrap-001.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/intrinsic-size/col-wrap-001.html
new file mode 100644
index 0000000..45da512
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/intrinsic-size/col-wrap-001.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<link rel="author" title="David Grogan" href="mailto:dgrogan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#intrinsic-sizes">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<meta name="assert"
+  content="column-wrap container's max-content width is big enough that items don't overflow when the container has fixed height and items have fixed width and height. Old algorithm gave max-content width of 50px." />
+
+<style>
+  #reference-overlapped-red {
+    position: absolute;
+    background-color: red;
+    width: 100px;
+    height: 100px;
+    z-index: -1;
+  }
+
+  .item {
+    /* Remove min-height so we don't have to think about it. */
+    min-height: 0px;
+    width: 50px;
+    flex: 0 0 100px
+  }
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.
+</p>
+
+<div id=reference-overlapped-red></div>
+
+<div
+  style="display: flex; flex-flow: column wrap; height: 100px; width: max-content; background: green;"
+  class=flex>
+  <div class="item"></div>
+  <div class="item"></div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/intrinsic-size/col-wrap-002.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/intrinsic-size/col-wrap-002.html
new file mode 100644
index 0000000..0e61cf78
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/intrinsic-size/col-wrap-002.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<link rel="author" title="David Grogan" href="mailto:dgrogan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#intrinsic-sizes">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<meta name="assert"
+  content="column-wrap container's max-content width is big enough that items don't overflow when the container has indefinite height but fixed max-height and items have fixed width and height. Old algorithm gave max-content width of 50px." />
+
+<style>
+  #reference-overlapped-red {
+    position: absolute;
+    background-color: red;
+    width: 100px;
+    height: 100px;
+    z-index: -1;
+  }
+
+  .item {
+    /* Remove min-height so we don't have to think about it. */
+    min-height: 0px;
+    width: 50px;
+    flex: 0 0 100px
+  }
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.
+</p>
+
+<div id=reference-overlapped-red></div>
+
+<div
+  style="display: flex; flex-flow: column wrap; max-height: 100px; width: max-content; background: green;"
+  class=flex>
+  <div class="item"></div>
+  <div class="item"></div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/intrinsic-size/col-wrap-003.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/intrinsic-size/col-wrap-003.html
new file mode 100644
index 0000000..518bf9f6
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/intrinsic-size/col-wrap-003.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<link rel="author" title="David Grogan" href="mailto:dgrogan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#intrinsic-sizes">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<meta name="assert"
+  content="Cross-axis borders are accounted for when determining inline content sizes of column-wrap flexboxes." />
+
+<style>
+  #reference-overlapped-red {
+    position: absolute;
+    background-color: red;
+    width: 100px;
+    height: 100px;
+    z-index: -1;
+  }
+
+  .item {
+    /* Remove min-height so we don't have to think about it. */
+    min-height: 0px;
+    width: 40px;
+    flex: 0 0 100px;
+  }
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.
+</p>
+
+<div id=reference-overlapped-red></div>
+
+<div
+  style="display: flex; flex-flow: column wrap; max-height: 100px; width: max-content; background: green; padding-left: 5px; border-left: 9px solid green; border-right: 6px solid green;">
+  <div class="item"></div>
+  <div class="item"></div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/intrinsic-size/col-wrap-004.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/intrinsic-size/col-wrap-004.html
new file mode 100644
index 0000000..00cfa52
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/intrinsic-size/col-wrap-004.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<link rel="author" title="David Grogan" href="mailto:dgrogan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#intrinsic-sizes">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<meta name="assert"
+  content="Item with width:100% doesn't contribute to max-content size but does get 100px width after final layout. The item also overflows the container." />
+
+<style>
+  .item {
+    /* Remove min-height so we don't have to think about it. */
+    min-height: 0px;
+    flex: 0 0 100px;
+    outline: 1px solid;
+  }
+</style>
+
+<div
+  style="display: flex; flex-flow: column wrap; width: max-content; height: 100px; background: green; position: relative;"
+  data-expected-width="100">
+  <div class="item" style="width: 100px;"></div>
+  <div class="item" style="width: 100%;" data-expected-width="100"
+    data-offset-x="100"></div>
+</div>
+
+<script>
+  checkLayout('body > div');
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/intrinsic-size/col-wrap-005.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/intrinsic-size/col-wrap-005.html
new file mode 100644
index 0000000..a753619
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/intrinsic-size/col-wrap-005.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<link rel="author" title="David Grogan" href="mailto:dgrogan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#intrinsic-sizes">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<meta name="assert"
+  content="The item still has sufficient available size at the time fit-content is resolved. (A bug in the code could cause the right-most item to have a width of 75px.)">
+
+<style>
+  .item {
+    /* Remove min-height so we don't have to think about it. */
+    min-height: 0px;
+    flex: 0 0 100px;
+    outline: 1px solid;
+  }
+
+  .grandchild {
+    width: 75px;
+    float: left;
+  }
+</style>
+
+<div
+  style="display: flex; flex-flow: column wrap; width: max-content; height: 100px; background: green; position: relative;"
+  data-expected-width="250">
+  <div class="item" style="width: 100px;"></div>
+  <div class="item" style="width: fit-content;" data-expected-width="150"
+    data-offset-x="100">
+    <!-- This item has min-content=75 and max-content=150. -->
+    <div class="grandchild"></div>
+    <div class="grandchild"></div>
+  </div>
+</div>
+
+<script>
+  checkLayout('body > div');
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/intrinsic-size/col-wrap-006.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/intrinsic-size/col-wrap-006.html
new file mode 100644
index 0000000..52560057
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/intrinsic-size/col-wrap-006.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<link rel="author" title="David Grogan" href="mailto:dgrogan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#intrinsic-sizes">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<meta name="assert"
+  content="flex item order is accounted for when determining inline content sizes of column-wrap flexboxes.">
+
+<style>
+  #reference-overlapped-red {
+    position: absolute;
+    background-color: red;
+    width: 100px;
+    height: 100px;
+    z-index: -1;
+  }
+
+  .item {
+    /* Remove min-height so we don't have to think about it. */
+    min-height: 0px;
+    width: 50px;
+    flex: 0 0 auto;
+  }
+
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.
+</p>
+
+<div id=reference-overlapped-red></div>
+
+<!--  An implementation that ignored order could make the max-content size of this flexbox 150px. -->
+<div
+  style="display: flex; flex-flow: column wrap; width: max-content; height: 100px; background: green;">
+  <div class="item" style="height: 90px; order: 0;"></div>
+  <div class="item" style="height: 100px; order: 2;"></div>
+  <div class="item" style="height: 10px; order: 1;"></div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/intrinsic-size/col-wrap-007.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/intrinsic-size/col-wrap-007.html
new file mode 100644
index 0000000..7f50f50
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/intrinsic-size/col-wrap-007.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<link rel="author" title="David Grogan" href="mailto:dgrogan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#intrinsic-sizes">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<meta name="assert"
+  content="column wrap max-content width calculation works for wrap-reverse containers" />
+
+<style>
+  #reference-overlapped-red {
+    position: absolute;
+    background-color: red;
+    width: 100px;
+    height: 100px;
+    z-index: -1;
+  }
+
+  .item {
+    /* Remove min-height so we don't have to think about it. */
+    min-height: 0px;
+    flex: 0 0 100px;
+  }
+
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.
+</p>
+
+<div id=reference-overlapped-red></div>
+
+<div
+  style="display: flex; flex-flow: column wrap-reverse; height: 100px; width: max-content; background: green;">
+  <div class="item" style="width: 30px;"></div>
+  <div class="item" style="width: 70px;"></div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/intrinsic-size/col-wrap-008.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/intrinsic-size/col-wrap-008.html
new file mode 100644
index 0000000..5edde949
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/intrinsic-size/col-wrap-008.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<link rel="author" title="David Grogan" href="mailto:dgrogan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#intrinsic-sizes">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<meta name="assert"
+  content="column wrap max-content width calculation works for align-content:space-around containers" />
+
+<style>
+  #reference-overlapped-red {
+    position: absolute;
+    background-color: red;
+    width: 100px;
+    height: 100px;
+    z-index: -1;
+  }
+
+  .item {
+    /* Remove min-height so we don't have to think about it. */
+    min-height: 0px;
+    flex: 0 0 100px;
+  }
+
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.
+</p>
+
+<div id=reference-overlapped-red></div>
+
+<div
+  style="display: flex; flex-flow: column wrap; height: 100px; width: max-content; align-content: space-around; background: green;">
+  <div class="item" style="width: 30px;"></div>
+  <div class="item" style="width: 70px;"></div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-flexbox/intrinsic-size/col-wrap-009.html b/third_party/blink/web_tests/external/wpt/css/css-flexbox/intrinsic-size/col-wrap-009.html
new file mode 100644
index 0000000..62514dbc
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-flexbox/intrinsic-size/col-wrap-009.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<link rel="author" title="David Grogan" href="mailto:dgrogan@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-flexbox/#intrinsic-sizes">
+<link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
+<meta name="assert"
+  content="column wrap container's intrinsic width changes when its height changes." />
+
+<style>
+  #reference-overlapped-red {
+    position: absolute;
+    background-color: red;
+    width: 100px;
+    height: 100px;
+    z-index: -1;
+  }
+  span {
+    width: 50px;
+    height: 100px;
+  }
+</style>
+
+<p>Test passes if there is a filled green square and <strong>no red</strong>.
+<div id=reference-overlapped-red></div>
+
+<div style="display: flex; height: 200px;" id="target">
+  <div
+    style="display: flex; flex-flow: column wrap; width: max-content; background: green;">
+    <span></span>
+    <span></span>
+  </div>
+</div>
+
+<script>
+  target.offsetLeft;
+  // With original height of 200px, both items fit in one flex line.
+  // With height 100px, there are two lines and the items are side-by-side.
+  target.style.height = "100px";
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-images/radial-gradient-transition-hint-crash.html b/third_party/blink/web_tests/external/wpt/css/css-images/radial-gradient-transition-hint-crash.html
new file mode 100644
index 0000000..51519a19
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-images/radial-gradient-transition-hint-crash.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<title>Color transition hint between values that are far apart</title>
+<link rel="help" href="https://drafts.csswg.org/css-images-4/#radial-gradients">
+<link rel="help" href="https://drafts.csswg.org/css-images-4/#color-transition-hint">
+<div style="width:400px;height:400px;"></div>
+<style>
+div {
+  background-image: radial-gradient(green -1540359700%, 0px, darkgrey 2%);
+}
+</style>
+PASS if no crash
diff --git a/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/at-property-viewport-units-dynamic.html b/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/at-property-viewport-units-dynamic.html
new file mode 100644
index 0000000..68adff6
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/at-property-viewport-units-dynamic.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<title>@property: viewport units in initial value (dynamic)</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1/#initial-value-descriptor" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  iframe {
+    width: 400px;
+    height: 200px;
+  }
+</style>
+<iframe id=iframe srcdoc="
+  <style>
+    @property --10vw { syntax: '<length>'; inherits: true; initial-value: 10vw}
+    @property --10vh { syntax: '<length>'; inherits: true; initial-value: 10vh}
+    div {
+      background: green;
+      width: var(--10vw);
+      height: var(--10vh);
+    }
+  </style>
+  <div style='width:10vw'></div>
+"></iframe>
+<script>
+  iframe.offsetTop;
+
+  function waitForLoad(w) {
+    return new Promise(resolve => w.addEventListener('load', resolve));
+  }
+
+  promise_test(async (t) => {
+    await waitForLoad(window);
+    let element = iframe.contentDocument.querySelector('div');
+    assert_equals(getComputedStyle(element).getPropertyValue('--10vw'), '40px');
+    assert_equals(getComputedStyle(element).getPropertyValue('--10vh'), '20px');
+
+    iframe.style.width = '100px';
+    assert_equals(getComputedStyle(element).getPropertyValue('--10vw'), '10px');
+    assert_equals(getComputedStyle(element).getPropertyValue('--10vh'), '20px');
+  });
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/at-property-viewport-units.html b/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/at-property-viewport-units.html
new file mode 100644
index 0000000..51520c2a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/at-property-viewport-units.html
@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<title>@property: viewport units in initial value</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1/#initial-value-descriptor" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  iframe {
+    width: 400px;
+    height: 200px;
+  }
+</style>
+<iframe id=iframe srcdoc="
+  <style>
+    @property --10vw { syntax: '<length>'; inherits: true; initial-value: 10vw}
+    @property --10vh { syntax: '<length>'; inherits: true; initial-value: 10vh}
+    @property --10vi { syntax: '<length>'; inherits: true; initial-value: 10vi}
+    @property --10vb { syntax: '<length>'; inherits: true; initial-value: 10vb}
+    @property --10vmin { syntax: '<length>'; inherits: true; initial-value: 10vmin}
+    @property --10vmax { syntax: '<length>'; inherits: true; initial-value: 10vmax}
+
+    @property --10svw { syntax: '<length>'; inherits: true; initial-value: 10svw}
+    @property --10svh { syntax: '<length>'; inherits: true; initial-value: 10svh}
+    @property --10svi { syntax: '<length>'; inherits: true; initial-value: 10svi}
+    @property --10svb { syntax: '<length>'; inherits: true; initial-value: 10svb}
+    @property --10svmin { syntax: '<length>'; inherits: true; initial-value: 10svmin}
+    @property --10svmax { syntax: '<length>'; inherits: true; initial-value: 10svmax}
+
+    @property --10lvw { syntax: '<length>'; inherits: true; initial-value: 10lvw}
+    @property --10lvh { syntax: '<length>'; inherits: true; initial-value: 10lvh}
+    @property --10lvi { syntax: '<length>'; inherits: true; initial-value: 10lvi}
+    @property --10lvb { syntax: '<length>'; inherits: true; initial-value: 10lvb}
+    @property --10lvmin { syntax: '<length>'; inherits: true; initial-value: 10lvmin}
+    @property --10lvmax { syntax: '<length>'; inherits: true; initial-value: 10lvmax}
+
+    @property --10dvw { syntax: '<length>'; inherits: true; initial-value: 10dvw}
+    @property --10dvh { syntax: '<length>'; inherits: true; initial-value: 10dvh}
+    @property --10dvi { syntax: '<length>'; inherits: true; initial-value: 10dvi}
+    @property --10dvb { syntax: '<length>'; inherits: true; initial-value: 10dvb}
+    @property --10dvmin { syntax: '<length>'; inherits: true; initial-value: 10dvmin}
+    @property --10dvmax { syntax: '<length>'; inherits: true; initial-value: 10dvmax}
+  </style>
+  <div></div>
+"></iframe>
+<script>
+  iframe.offsetTop;
+
+  function waitForLoad(w) {
+    return new Promise(resolve => {
+      if (w.document.readyState == 'complete')
+        resolve();
+      else
+        w.addEventListener('load', resolve)
+    });
+  }
+
+  function test_unit(element, actual, expected) {
+    promise_test(async (t) => {
+      await waitForLoad(window);
+      let element = iframe.contentDocument.querySelector('div');
+      assert_equals(getComputedStyle(element).getPropertyValue(`--${actual}`), expected);
+    },`${actual} is ${expected}`);
+  }
+
+  test_unit(iframe, '10vw', '40px');
+  test_unit(iframe, '10vh', '20px');
+  test_unit(iframe, '10vi', '40px');
+  test_unit(iframe, '10vb', '20px');
+  test_unit(iframe, '10vmin', '20px');
+  test_unit(iframe, '10vmax', '40px');
+
+  test_unit(iframe, '10svw', '40px');
+  test_unit(iframe, '10svh', '20px');
+  test_unit(iframe, '10svi', '40px');
+  test_unit(iframe, '10svb', '20px');
+  test_unit(iframe, '10svmin', '20px');
+  test_unit(iframe, '10svmax', '40px');
+
+  test_unit(iframe, '10lvw', '40px');
+  test_unit(iframe, '10lvh', '20px');
+  test_unit(iframe, '10lvi', '40px');
+  test_unit(iframe, '10lvb', '20px');
+  test_unit(iframe, '10lvmin', '20px');
+  test_unit(iframe, '10lvmax', '40px');
+
+  test_unit(iframe, '10dvw', '40px');
+  test_unit(iframe, '10dvh', '20px');
+  test_unit(iframe, '10dvi', '40px');
+  test_unit(iframe, '10dvb', '20px');
+  test_unit(iframe, '10dvmin', '20px');
+  test_unit(iframe, '10dvmax', '40px');
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-sizing/contain-intrinsic-size/contain-intrinsic-size-030.html b/third_party/blink/web_tests/external/wpt/css/css-sizing/contain-intrinsic-size/contain-intrinsic-size-030.html
new file mode 100644
index 0000000..8acfabb
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-sizing/contain-intrinsic-size/contain-intrinsic-size-030.html
@@ -0,0 +1,135 @@
+<!DOCTYPE html>
+<meta charset="utf8">
+<title>CSS contain-intrinsic-size: scroll containers</title>
+<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-sizing-4/#intrinsic-size-override">
+<link rel="help" href="https://drafts.csswg.org/css-overflow-3/#scroll-container">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<style>
+.test {
+  contain: size;
+  display: inline-block;
+  padding: 0;
+  border: 5px solid;
+}
+.test::before {
+  content: '';
+  display: block;
+  width: 40px;
+  height: 20px;
+}
+.big-contents::before {
+  width: 400px;
+  height: 200px;
+}
+.overflow-auto {
+  overflow: auto;
+  scrollbar-gutter: stable;
+}
+.overflow-scroll {
+  overflow: scroll;
+}
+.overflow-hidden {
+  overflow: hidden;
+}
+.cis-none {
+  contain-intrinsic-size: none none;
+}
+.cis-height {
+  contain-intrinsic-size: none 50px;
+}
+.cis-width {
+  contain-intrinsic-size: 100px none;
+}
+.cis-both {
+  contain-intrinsic-size: 100px 50px;
+}
+</style>
+
+<div id="log"></div>
+
+<div class="test small-contents overflow-auto cis-none"
+     data-expected-client-width="0" data-expected-client-height="0"
+     data-expected-scroll-width="40" data-expected-scroll-height="20"></div>
+<div class="test small-contents overflow-auto cis-height"
+     data-expected-client-width="0" data-expected-client-height="50"
+     data-expected-scroll-width="40" data-expected-scroll-height="50"></div>
+<div class="test small-contents overflow-auto cis-width"
+     data-expected-client-width="100" data-expected-client-height="0"
+     data-expected-scroll-width="100" data-expected-scroll-height="20"></div>
+<div class="test small-contents overflow-auto cis-both"
+     data-expected-client-width="100" data-expected-client-height="50"
+     data-expected-scroll-width="100" data-expected-scroll-height="50"></div>
+
+<div class="test small-contents overflow-scroll cis-none"
+     data-expected-client-width="0" data-expected-client-height="0"
+     data-expected-scroll-width="40" data-expected-scroll-height="20"></div>
+<div class="test small-contents overflow-scroll cis-height"
+     data-expected-client-width="0" data-expected-client-height="50"
+     data-expected-scroll-width="40" data-expected-scroll-height="50"></div>
+<div class="test small-contents overflow-scroll cis-width"
+     data-expected-client-width="100" data-expected-client-height="0"
+     data-expected-scroll-width="100" data-expected-scroll-height="20"></div>
+<div class="test small-contents overflow-scroll cis-both"
+     data-expected-client-width="100" data-expected-client-height="50"
+     data-expected-scroll-width="100" data-expected-scroll-height="50"></div>
+
+<div class="test small-contents overflow-hidden cis-none"
+     data-expected-client-width="0" data-expected-client-height="0"
+     data-expected-scroll-width="40" data-expected-scroll-height="20"></div>
+<div class="test small-contents overflow-hidden cis-height"
+     data-expected-client-width="0" data-expected-client-height="50"
+     data-expected-scroll-width="40" data-expected-scroll-height="50"></div>
+<div class="test small-contents overflow-hidden cis-width"
+     data-expected-client-width="100" data-expected-client-height="0"
+     data-expected-scroll-width="100" data-expected-scroll-height="20"></div>
+<div class="test small-contents overflow-hidden cis-both"
+     data-expected-client-width="100" data-expected-client-height="50"
+     data-expected-scroll-width="100" data-expected-scroll-height="50"></div>
+
+
+<div class="test big-contents overflow-auto cis-none"
+     data-expected-client-width="0" data-expected-client-height="0"
+     data-expected-scroll-width="400" data-expected-scroll-height="200"></div>
+<div class="test big-contents overflow-auto cis-height"
+     data-expected-client-width="0" data-expected-client-height="50"
+     data-expected-scroll-width="400" data-expected-scroll-height="200"></div>
+<div class="test big-contents overflow-auto cis-width"
+     data-expected-client-width="100" data-expected-client-height="0"
+     data-expected-scroll-width="400" data-expected-scroll-height="200"></div>
+<div class="test big-contents overflow-auto cis-both"
+     data-expected-client-width="100" data-expected-client-height="50"
+     data-expected-scroll-width="400" data-expected-scroll-height="200"></div>
+
+<div class="test big-contents overflow-scroll cis-none"
+     data-expected-client-width="0" data-expected-client-height="0"
+     data-expected-scroll-width="400" data-expected-scroll-height="200"></div>
+<div class="test big-contents overflow-scroll cis-height"
+     data-expected-client-width="0" data-expected-client-height="50"
+     data-expected-scroll-width="400" data-expected-scroll-height="200"></div>
+<div class="test big-contents overflow-scroll cis-width"
+     data-expected-client-width="100" data-expected-client-height="0"
+     data-expected-scroll-width="400" data-expected-scroll-height="200"></div>
+<div class="test big-contents overflow-scroll cis-both"
+     data-expected-client-width="100" data-expected-client-height="50"
+     data-expected-scroll-width="400" data-expected-scroll-height="200"></div>
+
+<div class="test big-contents overflow-hidden cis-none"
+     data-expected-client-width="0" data-expected-client-height="0"
+     data-expected-scroll-width="400" data-expected-scroll-height="200"></div>
+<div class="test big-contents overflow-hidden cis-height"
+     data-expected-client-width="0" data-expected-client-height="50"
+     data-expected-scroll-width="400" data-expected-scroll-height="200"></div>
+<div class="test big-contents overflow-hidden cis-width"
+     data-expected-client-width="100" data-expected-client-height="0"
+     data-expected-scroll-width="400" data-expected-scroll-height="200"></div>
+<div class="test big-contents overflow-hidden cis-both"
+     data-expected-client-width="100" data-expected-client-height="50"
+     data-expected-scroll-width="400" data-expected-scroll-height="200"></div>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+<script>
+checkLayout(".test");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-no-fallback-ref.html b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-no-fallback-ref.html
index 241ecf9..c0e56dc0 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-no-fallback-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-ui/compute-kind-widget-no-fallback-ref.html
@@ -3,6 +3,8 @@
 <title>Reference: Compute kind of widget - no fallback</title>
 <style>
     #container { width: 500px; }
+    #container > #search-text-input { appearance: textfield; }
+    #container > #select-menulist-button { appearance: none; appearance: menulist-button; }
 </style>
 <div id="container">
     <a>a</a>
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/target-pseudo-in-has.html b/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/target-pseudo-in-has.html
index 3f0ea16..629a6a8 100644
--- a/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/target-pseudo-in-has.html
+++ b/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/target-pseudo-in-has.html
@@ -70,13 +70,8 @@
 
     parent2.append(fragment2);
 
-    // Wait for :target to be detected.
-    await new Promise(r => requestAnimationFrame(r));
-    // Wait again to avoid flaky test result. (https://crbug.com/1334635)
-    await new Promise(r => requestAnimationFrame(r));
-
-    assert_true(fragment2.matches(":target"));
-    assert_equals(getComputedStyle(parent2).color, GREEN, "parent2 should be green after re-appending :target child");
+    // Skip to check parent2 color since there is nothing in the spec mentioning DOM mutations affecting the target element.
+    // - https://html.spec.whatwg.org/multipage/browsing-the-web.html#scroll-to-fragid
 
     fragmentLink("fragment3").click();
 
diff --git a/third_party/blink/web_tests/external/wpt/lint.ignore b/third_party/blink/web_tests/external/wpt/lint.ignore
index 6c93cca..1f6f823 100644
--- a/third_party/blink/web_tests/external/wpt/lint.ignore
+++ b/third_party/blink/web_tests/external/wpt/lint.ignore
@@ -836,6 +836,7 @@
 SET TIMEOUT: editing/crashtests/backcolor-in-nested-editing-host-td-from-DOMAttrModified.html
 SET TIMEOUT: editing/crashtests/contenteditable-will-be-blurred-by-focus-event-listener.html
 SET TIMEOUT: editing/crashtests/designMode-document-will-be-blurred-by-focus-event-listener.html
+SET TIMEOUT: editing/crashtests/indent-outdent-after-closing-editable-dialog-element.html
 SET TIMEOUT: editing/crashtests/inserthtml-after-temporarily-removing-document-element.html
 SET TIMEOUT: editing/crashtests/inserthtml-in-text-adopted-to-other-document.html
 SET TIMEOUT: editing/crashtests/insertorderedlist-in-text-adopted-to-other-document.html
diff --git a/third_party/blink/web_tests/external/wpt/payment-handler/can-make-payment-event.https.html b/third_party/blink/web_tests/external/wpt/payment-handler/can-make-payment-event.https.html
index b2016a05..28c001c6 100644
--- a/third_party/blink/web_tests/external/wpt/payment-handler/can-make-payment-event.https.html
+++ b/third_party/blink/web_tests/external/wpt/payment-handler/can-make-payment-event.https.html
@@ -5,6 +5,8 @@
 <link rel="manifest" href="manifest.json">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
 <script>
 const instrumentKey = 'instrument-key';
 
@@ -121,6 +123,8 @@
     paymentRequestCanMakePaymentResult,
     'canMakePayment() must return false.',
   );
+
+  await test_driver.bless('PaymentRequest.show() requires user activation');
   await promise_rejects_dom(t, 'NotSupportedError', request.show());
 }, 'If a payment handler is not installed, then the payment method is not supported.');
 
@@ -143,6 +147,8 @@
     paymentRequestCanMakePaymentResult,
     'canMakePayment() must return false.',
   );
+
+  await test_driver.bless('PaymentRequest.show() requires user activation');
   await promise_rejects_dom(t, 'NotSupportedError', request.show());
 }, 'If CanMakePaymentEvent.respondWith(false) is called, then the payment method is not supported.');
 
@@ -165,6 +171,8 @@
     paymentRequestCanMakePaymentResult,
     'canMakePayment() must return false.',
   );
+
+  await test_driver.bless('PaymentRequest.show() requires user activation');
   await promise_rejects_dom(t, 'NotSupportedError', request.show());
 }, 'If CanMakePaymentEvent.respondWith(Promise.resolve(false)) is called, then the payment method is not supported.');
 
@@ -187,6 +195,8 @@
     paymentRequestCanMakePaymentResult,
     'canMakePayment() must return true.',
   );
+
+  await test_driver.bless('PaymentRequest.show() requires user activation');
   const acceptPromise = request.show();
   await request.abort();
   await promise_rejects_dom(t, 'AbortError', acceptPromise);
@@ -211,6 +221,8 @@
     paymentRequestCanMakePaymentResult,
     'canMakePayment() must return true.',
   );
+
+  await test_driver.bless('PaymentRequest.show() requires user activation');
   const acceptPromise = request.show();
   await request.abort();
   await promise_rejects_dom(t, 'AbortError', acceptPromise);
@@ -235,6 +247,8 @@
     paymentRequestCanMakePaymentResult,
     'canMakePayment() must return false.',
   );
+
+  await test_driver.bless('PaymentRequest.show() requires user activation');
   await promise_rejects_dom(t, 'NotSupportedError', request.show());
 }, 'If CanMakePaymentEvent.respondWith(Promise.reject(error)) is called, then the payment method is not supported.');
 </script>
diff --git a/third_party/blink/web_tests/platform/generic/external/wpt/css/css-contain/container-queries/custom-property-style-queries-expected.txt b/third_party/blink/web_tests/platform/generic/external/wpt/css/css-contain/container-queries/custom-property-style-queries-expected.txt
new file mode 100644
index 0000000..c2a1b04
--- /dev/null
+++ b/third_party/blink/web_tests/platform/generic/external/wpt/css/css-contain/container-queries/custom-property-style-queries-expected.txt
@@ -0,0 +1,51 @@
+This is a testharness.js-based test.
+FAIL style(--inner: true) assert_equals: expected "true" but got ""
+FAIL style(--inner:true) assert_equals: expected "true" but got ""
+FAIL style(--inner:true ) assert_equals: expected "true" but got ""
+FAIL style(--inner: true ) assert_equals: expected "true" but got ""
+PASS style(--inner-no-space: true)
+PASS style(--inner-no-space:true)
+FAIL style(--inner-no-space:true ) assert_equals: expected "true" but got ""
+FAIL style(--inner-no-space: true ) assert_equals: expected "true" but got ""
+FAIL style(--inner-space-after: true) assert_equals: expected "true" but got ""
+FAIL style(--inner-space-after:true) assert_equals: expected "true" but got ""
+PASS style(--inner-space-after:true )
+PASS style(--inner-space-after: true )
+PASS style(--outer: true)
+PASS style(--outer:true)
+PASS style(--outer:true )
+PASS style(--outer: true )
+PASS style(--outer-no-space: true)
+PASS style(--outer-no-space:true)
+PASS style(--outer-no-space:true )
+PASS style(--outer-no-space: true )
+PASS style(--outer-space-after: true)
+PASS style(--outer-space-after:true)
+PASS style(--outer-space-after:true )
+PASS style(--outer-space-after: true )
+PASS outer style(--inner: true)
+PASS outer style(--inner:true)
+PASS outer style(--inner:true )
+PASS outer style(--inner: true )
+PASS outer style(--inner-no-space: true)
+PASS outer style(--inner-no-space:true)
+PASS outer style(--inner-no-space:true )
+PASS outer style(--inner-no-space: true )
+PASS outer style(--inner-space-after: true)
+PASS outer style(--inner-space-after:true)
+PASS outer style(--inner-space-after:true )
+PASS outer style(--inner-space-after: true )
+FAIL outer style(--outer: true) assert_equals: expected "true" but got ""
+FAIL outer style(--outer:true) assert_equals: expected "true" but got ""
+FAIL outer style(--outer:true ) assert_equals: expected "true" but got ""
+FAIL outer style(--outer: true ) assert_equals: expected "true" but got ""
+PASS outer style(--outer-no-space: true)
+PASS outer style(--outer-no-space:true)
+FAIL outer style(--outer-no-space:true ) assert_equals: expected "true" but got ""
+FAIL outer style(--outer-no-space: true ) assert_equals: expected "true" but got ""
+FAIL outer style(--outer-space-after: true) assert_equals: expected "true" but got ""
+FAIL outer style(--outer-space-after:true) assert_equals: expected "true" but got ""
+PASS outer style(--outer-space-after:true )
+PASS outer style(--outer-space-after: true )
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac/fast/gradients/unprefixed-linear-gradients-color-hints-expected.png b/third_party/blink/web_tests/platform/mac/fast/gradients/unprefixed-linear-gradients-color-hints-expected.png
index 648ab38..16723db 100644
--- a/third_party/blink/web_tests/platform/mac/fast/gradients/unprefixed-linear-gradients-color-hints-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/gradients/unprefixed-linear-gradients-color-hints-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/gradients/unprefixed-repeating-gradient-color-hint-expected.png b/third_party/blink/web_tests/platform/mac/fast/gradients/unprefixed-repeating-gradient-color-hint-expected.png
index 1f9a4e4..7b28397 100644
--- a/third_party/blink/web_tests/platform/mac/fast/gradients/unprefixed-repeating-gradient-color-hint-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/gradients/unprefixed-repeating-gradient-color-hint-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/gradients/unprefixed-linear-gradients-color-hints-expected.png b/third_party/blink/web_tests/platform/win/fast/gradients/unprefixed-linear-gradients-color-hints-expected.png
index eaa1842..7c43d88 100644
--- a/third_party/blink/web_tests/platform/win/fast/gradients/unprefixed-linear-gradients-color-hints-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/gradients/unprefixed-linear-gradients-color-hints-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/gradients/unprefixed-repeating-gradient-color-hint-expected.png b/third_party/blink/web_tests/platform/win/fast/gradients/unprefixed-repeating-gradient-color-hint-expected.png
index ad44d768..ac1ae2e 100644
--- a/third_party/blink/web_tests/platform/win/fast/gradients/unprefixed-repeating-gradient-color-hint-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/gradients/unprefixed-repeating-gradient-color-hint-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/wpt_internal/fenced_frame/embedder-coop-coep-blocked.https.html b/third_party/blink/web_tests/wpt_internal/fenced_frame/embedder-coop-coep-blocked.https.html
index bb75a8f..6d0ebf4 100644
--- a/third_party/blink/web_tests/wpt_internal/fenced_frame/embedder-coop-coep-blocked.https.html
+++ b/third_party/blink/web_tests/wpt_internal/fenced_frame/embedder-coop-coep-blocked.https.html
@@ -10,9 +10,12 @@
 <script>
 promise_test(async(t) => {
   const fencedframe = attachFencedFrameContext();
-  t.step_timeout(() => t.done(), 1000);
-  await fencedframe.execute(() => {});
-  assert_unreached("fenced frame should not be loaded.");
+  const fencedframe_loaded = fencedframe.execute(() => {});
+  const fencedframe_blocked = new Promise(r => t.step_timeout(r, 1000));
+  assert_equals("blocked", await Promise.any([
+    fencedframe_blocked.then(() => "blocked"),
+    fencedframe_loaded.then(() => "loaded"),
+  ]), "fenced frame should not be loaded.");
 }, 'Create fencedframe with COOP:same-origin and COEP:require-corp');
 </script>
-</body>
\ No newline at end of file
+</body>
diff --git a/third_party/blink/web_tests/wpt_internal/html/semantics/embedded-content/the-iframe-element/iframe-parent-changes-baseurl.https.window.js b/third_party/blink/web_tests/wpt_internal/html/semantics/embedded-content/the-iframe-element/iframe-parent-changes-baseurl.https.window.js
new file mode 100644
index 0000000..ad32b7b
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/html/semantics/embedded-content/the-iframe-element/iframe-parent-changes-baseurl.https.window.js
@@ -0,0 +1,129 @@
+// This test loads a variety of different iframe configurations. It then
+// verifies that when the parent changes its baseURI, the child frames do not
+// see the change.
+
+// Helper to load the iframes once they have been created and configured.
+async function load_iframe(the_iframe) {
+  const the_iframe_load = new Promise(resolve => {
+    the_iframe.onload = resolve;
+  });
+  document.body.appendChild(the_iframe);
+  await the_iframe_load;
+}
+
+async function check_results(the_iframe, expected_base_uri) {
+  // Send a postMessage to trigger sending results from `the_iframe`. The
+  // following promise resolves with the value of the next message received via
+  // postMessage from `the_iframe`.
+  const child_base_uri = new Promise(r => onmessage = e => r(e.data));
+  the_iframe.contentWindow.postMessage("base url", "*");
+  const result = await child_base_uri;
+  assert_true(result.links_unchanged);
+  assert_equals(expected_base_uri, result.base_uri);
+}
+
+onload = () => {
+  promise_test(async test => {
+    const html_src =
+        await fetch('./resources/baseurl-test.html').then(r => r.text());
+
+    // Create some links to verify parent behavior in baseURI change.
+    const link_rel1 = document.createElement("a");
+    link_rel1.href = "resources/baseurl-test.html";
+    link_rel1.id = 'link_rel1';
+    document.body.appendChild(link_rel1);
+    const link_rel2 = document.createElement("a");
+    // Note: link_rel2 below is relative to the origin (it starts with a /),
+    // and not the path, of the main document, and so will be different from
+    // link_rel1.
+    link_rel2.href = "/resources/baseurl-test.html";
+    link_rel2.id = 'link_rel2';
+    document.body.appendChild(link_rel2);
+
+    // Verify the link urls are what we expect.
+    const base_url = new URL(document.baseURI);
+    assert_equals(
+        base_url.origin + '/resources/baseurl-test.html', link_rel2.href);
+    const last_slash_index = base_url.pathname.lastIndexOf('/');
+    const test_page_url = base_url.origin +
+        base_url.pathname.substr(0, last_slash_index) +
+        '/resources/baseurl-test.html';
+    assert_equals(test_page_url, link_rel1.href);
+
+    // Create sandboxed srcdoc iframe for test.
+    const iframe1 = document.createElement("iframe");
+    iframe1.sandbox = "allow-scripts";
+    iframe1.srcdoc = html_src;
+    await load_iframe(iframe1);
+
+    // Create regular srcdoc iframe for test.
+    const iframe2 = document.createElement("iframe");
+    iframe2.srcdoc = html_src;
+    await load_iframe(iframe2);
+
+    // Create data src iframe for test.
+    let data_src = 'data:text/html,' + html_src;
+    const iframe3 = document.createElement("iframe");
+    iframe3.src = data_src;
+    await load_iframe(iframe3);
+    // Need to do a read-back here as '%0A' in original gets transcribed to '\n'
+    // during the load, and will be represented as such in the child's baseURI.
+    data_src = iframe3.src;
+
+    // Create regular src iframe for test.
+    const iframe4 = document.createElement("iframe");
+    iframe4.src = test_page_url;
+    await load_iframe(iframe4);
+
+    // Create src-as-srcdoc frame to test.
+    const iframe5 = document.createElement("iframe");
+    iframe5.src = 'about:srcdoc';
+    await load_iframe(iframe5);
+    iframe5.contentDocument.write(html_src);
+    await test.step_wait(() => iframe5.contentWindow.script_loaded);
+
+    // Create about:blank frame to test.
+    const iframe6 = document.createElement("iframe");
+    iframe6.src = 'about:blank';
+    await load_iframe(iframe6);
+    iframe6.contentDocument.write(html_src);
+    await test.step_wait(() => iframe6.contentWindow.script_loaded);
+
+    // Trigger the test scenario by changing the parent's baseURI, then querying
+    // the child.
+    const old_base_uri = document.baseURI;
+    const base_element = document.createElement('base');
+    const new_base_uri = "https://foo.com/";
+    base_element.href = new_base_uri;
+    document.head.appendChild(base_element);
+    assert_equals(new_base_uri, document.baseURI);
+
+    // Verify the parent link urls change as expected.
+    const new_test_url = new URL(new_base_uri);
+    assert_equals(
+        new_test_url.origin + '/resources/baseurl-test.html', link_rel2.href);
+    const new_last_slash_index = new_test_url.pathname.lastIndexOf('/');
+    const new_test_page_url = new_test_url.origin +
+        new_test_url.pathname.substr(0, new_last_slash_index) +
+        '/resources/baseurl-test.html';
+    assert_equals(new_test_page_url, link_rel1.href);
+
+    // sandboxed srcdoc iframe
+    await check_results(iframe1, old_base_uri);
+
+    // regular srcdoc iframe
+    await check_results(iframe2, old_base_uri);
+
+    // data iframe
+    await check_results(iframe3, data_src);
+
+    // regular same-site iframe
+    await check_results(iframe4, test_page_url);
+
+    // about:srcdoc iframe
+    await check_results(iframe5, old_base_uri);
+
+    // about:blank iframe
+    await check_results(iframe6, old_base_uri);
+  }, 'iframe doesn\'t see change in parent baseURI');
+}
diff --git a/third_party/blink/web_tests/wpt_internal/html/semantics/embedded-content/the-iframe-element/resources/baseurl-test.html b/third_party/blink/web_tests/wpt_internal/html/semantics/embedded-content/the-iframe-element/resources/baseurl-test.html
new file mode 100644
index 0000000..be75ed9
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/html/semantics/embedded-content/the-iframe-element/resources/baseurl-test.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>baseurl test page</title>
+<body>
+  <a href='foo.html' id='link_rel1'>txt</a>
+  <!-- Note: link_rel2 below is relative to the origin (it starts with a /), and
+       not the path, of the document, and so will be different from link_rel1.
+    -->
+  <a href='/foo.html' id='link_rel2'>txt</a>
+  <script>
+    let original_link_rel1 = link_rel1.href;
+    let original_link_rel2 = link_rel2.href;
+    window.addEventListener('message', (event) => {
+      // Verify the original link values haven't changed after the parent
+      // changes its baseURI.
+      const links_unchanged =
+          original_link_rel1 === link_rel1.href &&
+          original_link_rel2 === link_rel2.href;
+      event.source.postMessage(
+            {'base_uri': document.baseURI,
+             'links_unchanged': links_unchanged },
+            event.origin);
+    }, false);
+
+    // When the content is loaded with document.write, the following value is
+    // used to verify when the content has finished loading.
+    window.script_loaded = true;
+  </script>
+</body>
diff --git a/third_party/nearby/README.chromium b/third_party/nearby/README.chromium
index 21288f5..5872ed4f 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: 0a8f1f1c39af06dff550d8ca96c6e087994155b7
+Version: 55f20775d209871190580f0ef461ae1c816b1870
 License: Apache 2.0
 License File: LICENSE
 Security Critical: yes
diff --git a/tools/code_coverage/merge_js_source_maps/OWNERS b/tools/code_coverage/merge_js_source_maps/OWNERS
new file mode 100644
index 0000000..7d4c1180
--- /dev/null
+++ b/tools/code_coverage/merge_js_source_maps/OWNERS
@@ -0,0 +1,2 @@
+benreich@chromium.org
+tiborg@chromium.org
diff --git a/tools/code_coverage/merge_js_source_maps/merge_js_source_maps.gni b/tools/code_coverage/merge_js_source_maps/merge_js_source_maps.gni
new file mode 100644
index 0000000..1463400
--- /dev/null
+++ b/tools/code_coverage/merge_js_source_maps/merge_js_source_maps.gni
@@ -0,0 +1,31 @@
+# Copyright 2022 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//ui/webui/webui_features.gni")
+
+template("merge_js_source_maps") {
+  assert(enable_webui_inline_sourcemaps)
+
+  action(target_name) {
+    forward_variables_from(invoker,
+                           [
+                             "sources",
+                             "outputs",
+                             "deps",
+                           ])
+    script =
+        "//tools/code_coverage/merge_js_source_maps/merge_js_source_maps.py"
+    args = [ "--manifest-files" ] +
+           rebase_path(invoker.manifest_files, root_out_dir) + [ "--sources" ] +
+           rebase_path(invoker.sources, root_out_dir) + [ "--outputs" ] +
+           rebase_path(invoker.outputs, root_out_dir)
+    inputs =
+        [ "//tools/code_coverage/merge_js_source_maps/merge_js_source_maps.js" ]
+    foreach(manifest, invoker.manifest_files) {
+      outputs += [ get_path_info(manifest, "dir") + "/" +
+                   get_path_info(manifest, "name") + "__processed." +
+                   get_path_info(manifest, "extension") ]
+    }
+  }
+}
diff --git a/tools/code_coverage/merge_js_source_maps/merge_js_source_maps.js b/tools/code_coverage/merge_js_source_maps/merge_js_source_maps.js
new file mode 100644
index 0000000..46b2774
--- /dev/null
+++ b/tools/code_coverage/merge_js_source_maps/merge_js_source_maps.js
@@ -0,0 +1,172 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview Merges 2 inline sourcemaps for a input file and writes a new
+ * file to an output directory.
+ */
+
+import fs from 'fs';
+import path from 'path';
+
+import {ArgumentParser} from '../../../third_party/js_code_coverage/node_modules/argparse/argparse.js';
+import {SourceMapConsumer, SourceMapGenerator} from '../../../third_party/js_code_coverage/node_modules/source-map/source-map.js';
+
+/**
+ * The prefix comment that indicates a data URL containing the sourcemap.
+ */
+const SOURCEMAPPING_DATA_URL_PREFIX =
+    '//# sourceMappingURL=data:application/json;base64,';
+
+/**
+ * Decode a base64 encoded string representing a sourcemap to its utf-8
+ * equivalent.
+ * @param {string} contents Base64 encoded string.
+ * @returns Decoded utf-8 string of the sourcemap.
+ */
+function decodeBase64SourceMap(contents) {
+  const removedLeadingComment =
+      contents.replace(SOURCEMAPPING_DATA_URL_PREFIX, '');
+  const buf = Buffer.from(removedLeadingComment, 'base64');
+  return buf.toString('utf-8');
+}
+
+/**
+ * Helper to identify if a supplied line is an inline sourcemap.
+ * @param {string} lineContents Contents of an individual line.
+ * @returns True if line is an inline sourcemap, null otherwise.
+ */
+function isSourceMapComment(lineContents) {
+  return lineContents && lineContents.startsWith(SOURCEMAPPING_DATA_URL_PREFIX);
+}
+
+/**
+ * Convert `contents` into a valid dataURL sourceMappingURL comment.
+ * @param {string} contents A string representation of the sourcemap
+ * @returns A base64 encoded dataURL with the `SOURCEMAPPING_DATA_URL_PREFIX`
+ *     prepended.
+ */
+function encodeBase64SourceMap(contents) {
+  const buf = Buffer.from(contents, 'utf-8');
+  return SOURCEMAPPING_DATA_URL_PREFIX + buf.toString('base64');
+}
+
+/**
+ * Merge multiple sourcemaps to a single file.
+ * @param {!Array<string>} sourceMaps An array of stringified sourcemaps.
+ * @returns Returns a single sourcemap as a string.
+ */
+async function mergeSourcemaps(sourceMaps) {
+  let generator = null;
+  let originalSource = null;
+  for (const sourcemap of sourceMaps) {
+    const parsedMap = JSON.parse(sourcemap);
+    if (!originalSource) {
+      originalSource = parsedMap.sources[0];
+    }
+    const consumer = await new SourceMapConsumer(parsedMap);
+    if (generator) {
+      generator.applySourceMap(consumer, originalSource);
+    } else {
+      generator = await SourceMapGenerator.fromSourceMap(consumer);
+    }
+    consumer.destroy();
+  }
+  return generator.toString();
+}
+
+/**
+ * Processes all input files for multiple inlined sourcemaps and merges them.
+ * @param {!Array<string>} inputFiles The list of TS / JS files to extract
+ *     sourcemaps from.
+ */
+async function processFiles(inputFiles, outputFiles) {
+  for (let i = 0; i < inputFiles.length; i++) {
+    const inputFile = inputFiles[i];
+    const outputFile = outputFiles[i];
+    const fileContents = fs.readFileSync(inputFile, 'utf-8');
+    const inputLines = fileContents.split('\n');
+
+    // Skip any trailing blank lines to find the last non-null line.
+    let lastNonNullLine = inputLines.length - 1;
+    while (inputLines[lastNonNullLine].trim().length === 0 &&
+           lastNonNullLine > 0) {
+      lastNonNullLine--;
+    }
+
+    // If the last non-null line identified is not a sourcemap, ignore this file
+    // as it may have erroneously been marked for sourcemap merge.
+    if (!isSourceMapComment(inputLines[lastNonNullLine])) {
+      console.warn('Supplied file has no inline sourcemap', inputFile);
+      fs.copyFileSync(inputFile, outputFile);
+      continue;
+    }
+
+    // Extract out all the inline sourcemaps and decode them to their string
+    // equivalent.
+    const sourceMaps = [decodeBase64SourceMap(inputLines[lastNonNullLine])];
+    let sourceMapLineIdx = lastNonNullLine - 1;
+    while (isSourceMapComment(inputLines[sourceMapLineIdx]) &&
+           sourceMapLineIdx > 0) {
+      const sourceMap = decodeBase64SourceMap(inputLines[sourceMapLineIdx]);
+      sourceMaps.push(sourceMap);
+      sourceMapLineIdx--;
+    }
+
+    let mergedSourceMap = null;
+    try {
+      mergedSourceMap = await mergeSourcemaps(sourceMaps);
+    } catch (e) {
+      console.error(`Failed to merge inlined sourcemaps for ${inputFile}:`, e);
+      fs.copyFileSync(inputFile, outputFile);
+      continue;
+    }
+
+    // Drop off the lines that were previously identified as inline sourcemap
+    // comments and replace them with the merged sourcemap.
+    let finalFileContent =
+        inputLines.slice(0, sourceMapLineIdx + 1).join('\n') + '\n';
+    if (mergedSourceMap) {
+      finalFileContent += encodeBase64SourceMap(mergedSourceMap);
+    }
+    fs.writeFileSync(outputFile, finalFileContent);
+  }
+}
+
+async function main() {
+  const parser =
+      new ArgumentParser({description: 'Merge multiple inlined sourcemaps'});
+
+  parser.add_argument('--sources', {help: 'Input files', nargs: '*'});
+  parser.add_argument('--outputs', {help: 'Output files', nargs: '*'});
+  parser.add_argument(
+      '--manifest-files', {help: 'Output files', nargs: '*', required: false});
+
+  const argv = parser.parse_args();
+  await processFiles(argv.sources, argv.outputs);
+
+  if (argv.manifest_files) {
+    // TODO(crbug/1337530): Currently we just remove the final directory of the
+    // `base_dir` key. This is definitely brittle and also subject to changes
+    // made to the output directory. Consider updating this to be more robust.
+    for (const manifestFile of argv.manifest_files) {
+      try {
+        const manifestFileContents =
+            fs.readFileSync(manifestFile).toString('utf-8');
+        const manifest = JSON.parse(manifestFileContents);
+        manifest.base_dir = path.parse(manifest.base_dir).dir;
+        const parsedPath = path.parse(manifestFile);
+        fs.writeFileSync(
+            path.join(
+                parsedPath.dir,
+                (parsedPath.name + '__processed' + parsedPath.ext)),
+            JSON.stringify(manifest));
+      } catch (e) {
+        console.log(e);
+      }
+    }
+  }
+}
+
+(async () => main())();
\ No newline at end of file
diff --git a/tools/code_coverage/merge_js_source_maps/merge_js_source_maps.py b/tools/code_coverage/merge_js_source_maps/merge_js_source_maps.py
new file mode 100755
index 0000000..493724b
--- /dev/null
+++ b/tools/code_coverage/merge_js_source_maps/merge_js_source_maps.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env vpython3
+# Copyright 2022 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import argparse
+import sys
+from pathlib import Path
+
+_HERE_DIR = Path(__file__).parent
+_SOURCE_MAP_MERGER = (_HERE_DIR / 'merge_js_source_maps.js').resolve()
+
+_NODE_PATH = (_HERE_DIR.parent.parent.parent / 'third_party' / 'node').resolve()
+sys.path.append(str(_NODE_PATH))
+import node
+
+
+def main(argv):
+  parser = argparse.ArgumentParser()
+  parser.add_argument('--sources', required=True, nargs="*")
+  parser.add_argument('--outputs', required=True, nargs="*")
+  parser.add_argument('--manifest-files', required=True, nargs="*")
+  args = parser.parse_args(argv)
+
+  node.RunNode([
+      str(_SOURCE_MAP_MERGER), '--manifest-files', *args.manifest_files,
+      '--sources', *args.sources, '--outputs', *args.outputs
+  ])
+
+
+if __name__ == '__main__':
+  main(sys.argv[1:])
diff --git a/tools/code_coverage/merge_js_source_maps/package.json b/tools/code_coverage/merge_js_source_maps/package.json
new file mode 100644
index 0000000..36aa154f
--- /dev/null
+++ b/tools/code_coverage/merge_js_source_maps/package.json
@@ -0,0 +1,9 @@
+{
+  "name": "merge_js_source_maps",
+  "version": "0.1",
+  "description": "Merge 2 inline sourcemaps",
+  "main": "merge_js_source_maps.js",
+  "files": [ "merge_js_source_maps.js" ],
+  "license": "SEE LICENSE IN ../../../LICENSE",
+  "type" : "module"
+}
diff --git a/tools/code_coverage/merge_js_source_maps/test/generated_file_pre_merge.js b/tools/code_coverage/merge_js_source_maps/test/generated_file_pre_merge.js
new file mode 100644
index 0000000..706556f
--- /dev/null
+++ b/tools/code_coverage/merge_js_source_maps/test/generated_file_pre_merge.js
@@ -0,0 +1,32 @@
+'use strict';
+// 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.
+// clang-format off
+// /*grit-removed-lines:4*/
+// Enums aren't natively available in JS, this will ensure a rewritten TS
+// sourcemap.
+var ExampleEnum;
+(function (ExampleEnum) {
+    ExampleEnum[ExampleEnum["SOME_EXAMPLE"] = 0] = "SOME_EXAMPLE";
+    ExampleEnum[ExampleEnum["OTHER_EXAMPLE"] = 1] = "OTHER_EXAMPLE";
+})(ExampleEnum || (ExampleEnum = {}));
+class SpecialTypeScriptProperties {
+    constructor() {
+        /* Protected variables are typescript only specifiers */
+        this.protectedValue = 0;
+    }
+}
+class Derived extends SpecialTypeScriptProperties {
+    constructor() {
+        super(...arguments);
+        /* So are private variables */
+        this.privateValue = 0;
+    }
+    method() {
+        console.log(this.privateValue);
+        return this.protectedValue;
+    }
+}
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm9yaWdpbmFsX2ZpbGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBS0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyMiBUaGUgQ2hyb21pdW0gQXV0aG9ycy4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cbi8vIFVzZSBvZiB0aGlzIHNvdXJjZSBjb2RlIGlzIGdvdmVybmVkIGJ5IGEgQlNELXN0eWxlIGxpY2Vuc2UgdGhhdCBjYW4gYmVcbi8vIGZvdW5kIGluIHRoZSBMSUNFTlNFIGZpbGUuXG4vLyBjbGFuZy1mb3JtYXQgb2ZmXG5cbi8vIDxpZiBleHByPVwiaXNfbWFjb3N4XCI+XG5mdW5jdGlvbiB0aGlzSXNSZW1vdmVkKCk6IGJvb2xlYW4ge1xuICByZXR1cm4gdHJ1ZTtcbn1cbi8vIDwvaWY+XG5cbi8vIEVudW1zIGFyZW4ndCBuYXRpdmVseSBhdmFpbGFibGUgaW4gSlMsIHRoaXMgd2lsbCBlbnN1cmUgYSByZXdyaXR0ZW4gVFNcbi8vIHNvdXJjZW1hcC5cbmVudW0gRXhhbXBsZUVudW0ge1xuICBTT01FX0VYQU1QTEUgPSAwLFxuICBPVEhFUl9FWEFNUExFID0gMSxcbn1cblxuYWJzdHJhY3QgY2xhc3MgU3BlY2lhbFR5cGVTY3JpcHRQcm9wZXJ0aWVzIHtcbiAgLyogUHJvdGVjdGVkIHZhcmlhYmxlcyBhcmUgdHlwZXNjcmlwdCBvbmx5IHNwZWNpZmllcnMgKi9cbiAgcHJvdGVjdGVkIHByb3RlY3RlZFZhbHVlOiBudW1iZXIgPSAwO1xuXG4gIGFic3RyYWN0IG1ldGhvZCgpOiBudW1iZXI7XG59XG5cbmNsYXNzIERlcml2ZWQgZXh0ZW5kcyBTcGVjaWFsVHlwZVNjcmlwdFByb3BlcnRpZXMge1xuICAvKiBTbyBhcmUgcHJpdmF0ZSB2YXJpYWJsZXMgKi9cbiAgcHJpdmF0ZSBwcml2YXRlVmFsdWU6IG51bWJlciA9IDA7XG5cbiAgbWV0aG9kKCkge1xuICAgIGNvbnNvbGUubG9nKHRoaXMucHJpdmF0ZVZhbHVlKTtcbiAgICByZXR1cm4gdGhpcy5wcm90ZWN0ZWRWYWx1ZTtcbiAgfVxufVxuIl19
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3JpZ2luYWxfZmlsZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInByZXByb2Nlc3NlZC9vaXJpZ2luYWxfZmlsZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsNERBQTREO0FBQzVELHlFQUF5RTtBQUN6RSw2QkFBNkI7QUFDN0IsbUJBQW1CO0FBRW5CLDJCQUEyQjtBQUUzQix5RUFBeUU7QUFDekUsYUFBYTtBQUNiLElBQUssV0FHSjtBQUhELFdBQUssV0FBVztJQUNkLDZEQUFnQixDQUFBO0lBQ2hCLCtEQUFpQixDQUFBO0FBQ25CLENBQUMsRUFISSxXQUFXLEtBQVgsV0FBVyxRQUdmO0FBRUQsTUFBZSwyQkFBMkI7SUFBMUM7UUFDRSx3REFBd0Q7UUFDOUMsbUJBQWMsR0FBVyxDQUFDLENBQUM7SUFHdkMsQ0FBQztDQUFBO0FBRUQsTUFBTSxPQUFRLFNBQVEsMkJBQTJCO0lBQWpEOztRQUNFLDhCQUE4QjtRQUN0QixpQkFBWSxHQUFXLENBQUMsQ0FBQztJQU1uQyxDQUFDO0lBSkMsTUFBTTtRQUNKLE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxDQUFDO1FBQy9CLE9BQU8sSUFBSSxDQUFDLGNBQWMsQ0FBQztJQUM3QixDQUFDO0NBQ0Y7QUFFRCwwaERBQTBoRCJ9
\ No newline at end of file
diff --git a/tools/code_coverage/merge_js_source_maps/test/merge_js_source_maps_test.py b/tools/code_coverage/merge_js_source_maps/test/merge_js_source_maps_test.py
new file mode 100755
index 0000000..6ccad61
--- /dev/null
+++ b/tools/code_coverage/merge_js_source_maps/test/merge_js_source_maps_test.py
@@ -0,0 +1,154 @@
+#!/usr/bin/env vpython3
+# Copyright 2022 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import base64
+import json
+import os
+import shutil
+import sys
+import tempfile
+import unittest
+
+from parameterized import parameterized
+from pathlib import Path
+
+_HERE_DIR = Path(__file__).parent.resolve()
+_SOURCE_MAP_MERGER = (_HERE_DIR.parent / 'merge_js_source_maps.js').resolve()
+
+# TODO(crbug/1337530): Move common sourcemap build rules and tests into a more
+# generic location.
+_SOURCE_MAP_TRANSLATOR = (_HERE_DIR.parent.parent / 'create_js_source_maps' /
+                          'test' / 'translate_source_map.js').resolve()
+
+_NODE_PATH = (_HERE_DIR.parent.parent.parent.parent / 'third_party' /
+              'node').resolve()
+sys.path.append(str(_NODE_PATH))
+import node
+
+_SOURCE_MAPPING_DATA_URL_PREFIX = \
+    '//# sourceMappingURL=data:application/json;base64,'
+
+
+class MergeSourceMapsTest(unittest.TestCase):
+  def setUp(self):
+    self._out_folder = None
+
+  def tearDown(self):
+    if self._out_folder:
+      shutil.rmtree(self._out_folder)
+
+  def _translate(self, source_map, line, column):
+    """ Translates from post-transform to pre-transform using a source map.
+
+    Translates a line and column in some hypothetical processed JavaScript
+    back into the hypothetical original line and column using the indicated
+    source map. Returns the pre-processed line and column.
+    """
+    stdout = node.RunNode([
+        str(_SOURCE_MAP_TRANSLATOR), "--source_map", source_map, "--line",
+        str(line), "--column",
+        str(column)
+    ])
+    result = json.loads(stdout)
+    return result['line'], result['column']
+
+  def testMergingTwoSourcemaps(self):
+    """ Test that merging 2 inline sourcemaps results in line numbers and
+    columns that refer to positions in the original file.
+
+    The file `original_file.ts` contains a mix of GRiT <if expr> that get
+    removed and some TS specific properties that get rewritten to JS. These have
+    been ran through both `create_js_source_maps` build rule AND tsc to provide
+    a generated file with 2 inline sourcemaps.
+    """
+    assert not self._out_folder
+    self._out_folder = tempfile.mkdtemp(dir=_HERE_DIR)
+    original_file_name = 'original_file.ts'
+    input_file_name = (_HERE_DIR / 'generated_file_pre_merge.js').resolve()
+    output_file_name = (Path(self._out_folder) / "merged_maps.out").resolve()
+    node.RunNode([
+        str(_SOURCE_MAP_MERGER),
+        '--sources',
+        str(input_file_name),
+        '--outputs',
+        str(output_file_name),
+    ])
+
+    source_map = None
+    with open(output_file_name, encoding='utf-8') as f:
+      merged_source_map_lines = f.readlines()
+      for line in merged_source_map_lines:
+        stripped_line = line.strip()
+        if stripped_line.startswith(_SOURCE_MAPPING_DATA_URL_PREFIX):
+          source_map = base64.b64decode(
+              stripped_line.replace(_SOURCE_MAPPING_DATA_URL_PREFIX,
+                                    '')).decode('utf-8')
+          break
+      else:
+        self.fail('Failed to identify a merged sourcemap')
+
+    # The sources key must refer to the original file name.
+    self.assertEqual(original_file_name, json.loads(source_map)['sources'][0])
+
+    # Check various mappings.
+    # The first line should not have an equivalent mapping:
+    # "use strict";
+    line, column = self._translate(source_map, 1, 1)
+    self.assertEqual(line, None)
+
+    # The IIFE representing the enum should map to the line, this large jump in
+    # line numbers represents the GRiT <if expr> that has been removed.
+    line, column = self._translate(source_map, 7, 1)
+    self.assertEqual(line, 12)
+
+    # Certain things remain the same, such as console.log and these should just
+    # get their line / column repointed to the new location.
+    line, column = self._translate(source_map, 27, 8)
+    self.assertEqual(line, 31)
+    self.assertEqual(column, 0)
+
+  def testManifestFilesAreRewritten(self):
+    """ Tests that when `manifest-files` are supplied the results are rewritten
+    to reflect the updated files location.
+
+    This is critical to ensure any files that undergo a merge (or move) are
+    appropriately rewritten and subsequent steps can rely on the correct merged
+    file instead of the previous unmerged one.
+    """
+    assert not self._out_folder
+    self._out_folder = tempfile.mkdtemp(dir=_HERE_DIR)
+
+    manifest_file_contents = b'{"base_dir":"tsc/ts_library_out"}'
+    input_fd, manifest_file = tempfile.mkstemp(dir=self._out_folder,
+                                               text=True,
+                                               suffix=".json")
+    os.write(input_fd, manifest_file_contents)
+    os.close(input_fd)
+
+    original_file_name = 'original_file.ts'
+    input_file_name = (_HERE_DIR / 'generated_file_pre_merge.js').resolve()
+    output_file_name = (Path(self._out_folder) / "merged_maps.out").resolve()
+    node.RunNode([
+        str(_SOURCE_MAP_MERGER),
+        '--sources',
+        str(input_file_name),
+        '--outputs',
+        str(output_file_name),
+        '--manifest-files',
+        str(manifest_file),
+    ])
+
+    manifest_file_contents = '{"base_dir":"tsc"}'
+    manifest_file_path = Path(manifest_file)
+    remapped_manifest_file = manifest_file_path.parent.joinpath(
+        str(manifest_file_path.stem) + '__processed' +
+        str(manifest_file_path.suffix))
+    self.assertTrue(remapped_manifest_file.exists())
+    with remapped_manifest_file.open(encoding='utf-8') as f:
+      self.assertEqual(f.read(), manifest_file_contents)
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/tools/code_coverage/merge_js_source_maps/test/original_file.ts b/tools/code_coverage/merge_js_source_maps/test/original_file.ts
new file mode 100644
index 0000000..faf6f2d2
--- /dev/null
+++ b/tools/code_coverage/merge_js_source_maps/test/original_file.ts
@@ -0,0 +1,33 @@
+// 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.
+// clang-format off
+
+// <if expr="is_macosx">
+function thisIsRemoved(): boolean {
+  return true;
+}
+// </if>
+
+// Enums aren't natively available in JS, this will ensure a rewritten TS
+// sourcemap.
+enum ExampleEnum {
+  SOME_EXAMPLE = 0,
+  OTHER_EXAMPLE = 1,
+}
+
+abstract class SpecialTypeScriptProperties {
+  /* Private variables are typescript only specifiers */
+  protected protectedValue: number = 0;
+
+  abstract method(): number;
+}
+
+class Derived extends SpecialTypeScriptProperties {
+  private privateValue: number = 0;
+
+  method() {
+    console.log(this.privateValue);
+    return this.protectedValue;
+  }
+}
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index d42d005..09aac1c 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -652,6 +652,8 @@
       'chromecast-linux-builder-perf': 'cast_binary_size',
       'chromeos-amd64-generic-lacros-builder-perf': 'chromeos_amd64-generic_lacros_official',
       'chromeos-arm-generic-lacros-builder-perf': 'chromeos_arm-generic_lacros_official',
+      'fuchsia-builder-perf-arm64': 'official_goma_fuchsia_arm64_perf',
+      'fuchsia-builder-perf-x64': 'official_goma_fuchsia_x64_perf',
       'linux-builder-perf': 'official_goma_linux_perf',
       'linux-builder-perf-pgo': 'official_goma_linux_perf_pgo',
       'linux-builder-perf-rel': 'official_goma_linux_perf',
@@ -673,7 +675,6 @@
       'android_arm64-cfi-builder-perf-fyi': 'official_goma_minimal_symbols_android_thin_lto_opt_arm64',
       'chromeos-kevin-builder-perf-fyi': 'chromeos_kevin_include_unwind_tables_official',
       'fuchsia-builder-perf-fyi': 'official_goma_fuchsia_arm64_perf',
-      'fuchsia-builder-perf-x64': 'official_goma_fuchsia_x64_perf',
     },
 
     'chromium.reclient.fyi': {
diff --git a/tools/mb/mb_config_expectations/chromium.perf.fyi.json b/tools/mb/mb_config_expectations/chromium.perf.fyi.json
index ea89105b..0476fd4 100644
--- a/tools/mb/mb_config_expectations/chromium.perf.fyi.json
+++ b/tools/mb/mb_config_expectations/chromium.perf.fyi.json
@@ -56,21 +56,5 @@
       "test_isolate_uses_emulator": false,
       "use_goma": true
     }
-  },
-  "fuchsia-builder-perf-x64": {
-    "gn_args": {
-      "ffmpeg_branding": "Chrome",
-      "fuchsia_additional_boot_images": [
-        "//third_party/fuchsia-sdk/images-internal/chromebook-x64-release/"
-      ],
-      "is_chrome_branded": true,
-      "is_official_build": true,
-      "proprietary_codecs": true,
-      "symbol_level": 1,
-      "target_cpu": "x64",
-      "target_os": "fuchsia",
-      "test_isolate_uses_emulator": false,
-      "use_goma": true
-    }
   }
 }
\ No newline at end of file
diff --git a/tools/mb/mb_config_expectations/chromium.perf.json b/tools/mb/mb_config_expectations/chromium.perf.json
index c877f7bc..f193ff06 100644
--- a/tools/mb/mb_config_expectations/chromium.perf.json
+++ b/tools/mb/mb_config_expectations/chromium.perf.json
@@ -124,6 +124,39 @@
       "use_thin_lto": true
     }
   },
+  "fuchsia-builder-perf-arm64": {
+    "gn_args": {
+      "ffmpeg_branding": "Chrome",
+      "fuchsia_additional_boot_images": [
+        "//third_party/fuchsia-sdk/images-internal/astro-release/",
+        "//third_party/fuchsia-sdk/images-internal/sherlock-release/"
+      ],
+      "is_chrome_branded": true,
+      "is_official_build": true,
+      "proprietary_codecs": true,
+      "symbol_level": 1,
+      "target_cpu": "arm64",
+      "target_os": "fuchsia",
+      "test_isolate_uses_emulator": false,
+      "use_goma": true
+    }
+  },
+  "fuchsia-builder-perf-x64": {
+    "gn_args": {
+      "ffmpeg_branding": "Chrome",
+      "fuchsia_additional_boot_images": [
+        "//third_party/fuchsia-sdk/images-internal/chromebook-x64-release/"
+      ],
+      "is_chrome_branded": true,
+      "is_official_build": true,
+      "proprietary_codecs": true,
+      "symbol_level": 1,
+      "target_cpu": "x64",
+      "target_os": "fuchsia",
+      "test_isolate_uses_emulator": false,
+      "use_goma": true
+    }
+  },
   "linux-builder-perf": {
     "gn_args": {
       "chrome_pgo_phase": 0,
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 3fe69a6..37c5f08 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -4366,6 +4366,13 @@
   <int value="39" label="Disconnect host VPN"/>
 </enum>
 
+<enum name="ArcNotificationExpandState">
+  <int value="0" label="Fixed size"/>
+  <int value="1" label="Expandable"/>
+  <int value="2" label="Expanded"/>
+  <int value="3" label="Collapsed"/>
+</enum>
+
 <enum name="ArcNotificationStyle">
   <int value="0" label="No Style"/>
   <int value="1" label="Big Picture Style"/>
diff --git a/tools/metrics/histograms/metadata/apps/histograms.xml b/tools/metrics/histograms/metadata/apps/histograms.xml
index c5c4b28..c60ab00 100644
--- a/tools/metrics/histograms/metadata/apps/histograms.xml
+++ b/tools/metrics/histograms/metadata/apps/histograms.xml
@@ -1653,16 +1653,6 @@
   </summary>
 </histogram>
 
-<histogram name="Apps.AppListSearchCommenced" units="searches"
-    expires_after="2022-12-01">
-  <owner>yulunwu@chromium.org</owner>
-  <owner>tapted@chromium.org</owner>
-  <summary>
-    The number of searches that are started in the app list. This is gathered
-    each time the app list search box transitions from empty to non-empty.
-  </summary>
-</histogram>
-
 <histogram name="Apps.AppListSearchQueryLength" units="characters"
     expires_after="2022-08-01">
   <obsolete>
diff --git a/tools/metrics/histograms/metadata/arc/histograms.xml b/tools/metrics/histograms/metadata/arc/histograms.xml
index 0c9f1d9..166f7e0 100644
--- a/tools/metrics/histograms/metadata/arc/histograms.xml
+++ b/tools/metrics/histograms/metadata/arc/histograms.xml
@@ -1392,6 +1392,20 @@
   </summary>
 </histogram>
 
+<histogram name="Arc.Notifications.ExpandState"
+    enum="ArcNotificationExpandState" expires_after="2022-10-05">
+  <owner>yaoqq@google.com</owner>
+  <owner>arc-framework@google.com</owner>
+  <summary>
+    Records the expand state of an Arc notification, the status of
+    fixed_size/expandable is logged when the notification is created, while the
+    status of expanded/collapsed is logged when the user expands/collapses it.
+    Need to note that if it's a grouped notification we are tracking the state
+    of the summary(top level) notification of the group, since we can't easily
+    acquire the state for the child notifications of the group.
+  </summary>
+</histogram>
+
 <histogram name="Arc.Notifications.InlineReplyEnabled" enum="BooleanEnabled"
     expires_after="2022-10-07">
   <owner>yaoqq@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/browser/histograms.xml b/tools/metrics/histograms/metadata/browser/histograms.xml
index cdf2b66..4ac6ee4 100644
--- a/tools/metrics/histograms/metadata/browser/histograms.xml
+++ b/tools/metrics/histograms/metadata/browser/histograms.xml
@@ -99,6 +99,7 @@
     <variant name=".Audio"/>
     <variant name=".Ent"/>
     <variant name=".General"/>
+    <variant name=".MediaAppPdf"/>
     <variant name=".OnboardingExperience"/>
     <variant name=".Performance"/>
     <variant name=".PersonalizationAvatar"/>
diff --git a/tools/metrics/histograms/metadata/input/histograms.xml b/tools/metrics/histograms/metadata/input/histograms.xml
index 8e09cc1f..2f8abea 100644
--- a/tools/metrics/histograms/metadata/input/histograms.xml
+++ b/tools/metrics/histograms/metadata/input/histograms.xml
@@ -112,7 +112,7 @@
 </histogram>
 
 <histogram name="InputMethod.Assistive.Disabled.Emoji"
-    enum="IMEAssistiveDisabledReason" expires_after="2022-05-01">
+    enum="IMEAssistiveDisabledReason" expires_after="2022-12-04">
   <owner>myy@google.com</owner>
   <owner>essential-inputs-team@google.com</owner>
   <summary>
@@ -277,7 +277,7 @@
 </histogram>
 
 <histogram name="InputMethod.Assistive.TimeToAccept.MultiWord" units="ms"
-    expires_after="2022-06-09">
+    expires_after="2022-12-04">
   <owner>curtismcmullan@google.com</owner>
   <owner>essential-inputs-team@google.com</owner>
   <summary>
@@ -314,7 +314,7 @@
 </histogram>
 
 <histogram name="InputMethod.Assistive.TimeToDismiss.MultiWord" units="ms"
-    expires_after="2022-06-09">
+    expires_after="2022-12-04">
   <owner>curtismcmullan@google.com</owner>
   <owner>essential-inputs-team@google.com</owner>
   <summary>
@@ -338,7 +338,7 @@
 </histogram>
 
 <histogram name="InputMethod.Assistive.UserPref.Emoji" enum="BooleanEnabled"
-    expires_after="2022-05-01">
+    expires_after="2022-12-04">
   <owner>myy@google.com</owner>
   <owner>essential-inputs-team@google.com</owner>
   <summary>
@@ -349,7 +349,7 @@
 </histogram>
 
 <histogram name="InputMethod.Assistive.UserPref.MultiWord"
-    enum="BooleanEnabled" expires_after="2022-06-05">
+    enum="BooleanEnabled" expires_after="2022-12-04">
   <owner>curtismcmullan@google.com</owner>
   <owner>essential-inputs-team@google.com</owner>
   <summary>
@@ -361,7 +361,7 @@
 </histogram>
 
 <histogram name="InputMethod.Assistive.UserPref.PersonalInfo"
-    enum="BooleanEnabled" expires_after="2021-10-04">
+    enum="BooleanEnabled" expires_after="2022-12-04">
   <owner>myy@google.com</owner>
   <owner>essential-inputs-team@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/net/histograms.xml b/tools/metrics/histograms/metadata/net/histograms.xml
index 48edb2e..f2ff850f 100644
--- a/tools/metrics/histograms/metadata/net/histograms.xml
+++ b/tools/metrics/histograms/metadata/net/histograms.xml
@@ -626,6 +626,18 @@
   </summary>
 </histogram>
 
+<histogram name="Net.DNS.DnsConfig.Nsswitch.NisServiceInHosts"
+    enum="BooleanIncluded" expires_after="2022-11-27">
+  <owner>horo@chromium.org</owner>
+  <owner>src/net/OWNERS</owner>
+  <summary>
+    Whether or not NIS service is registered in the &quot;hosts:&quot; database
+    of an nsswitch.conf. Recorded when a resolv.conf file is successfully read
+    by Chrome and the DNS configuration read from the resolv.conf file is
+    considered compatible with Chrome.
+  </summary>
+</histogram>
+
 <histogram name="Net.DNS.DnsConfig.Nsswitch.NumServices" units="#services"
     expires_after="2022-01-15">
   <owner>ericorth@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index 48813a3..2c1c65f 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -5243,7 +5243,7 @@
 </histogram>
 
 <histogram name="Drive.PushNotificationInitiallyEnabled" enum="BooleanEnabled"
-    expires_after="2022-09-18">
+    expires_after="M116">
   <owner>simmonsjosh@google.com</owner>
   <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
@@ -10642,7 +10642,7 @@
 </histogram>
 
 <histogram name="ReadingList.BookmarkBarState.On{Frequency}AddToReadingList"
-    enum="BookmarkBarState" expires_after="2022-08-20">
+    enum="BookmarkBarState" expires_after="2022-11-20">
   <owner>corising@chromium.org</owner>
   <owner>chrome-desktop-ui-sea@google.com</owner>
   <summary>
@@ -10725,7 +10725,7 @@
 </histogram>
 
 <histogram name="ReadingList.SyncStateMatchesBookmarks" enum="BooleanMatched"
-    expires_after="2022-08-20">
+    expires_after="2022-11-20">
   <owner>corising@chromium.org</owner>
   <owner>chrome-desktop-ui-sea@google.com</owner>
   <summary>
@@ -10751,7 +10751,7 @@
 </histogram>
 
 <histogram name="ReadingList.WebUI.InitialEntriesRenderTime" units="ms"
-    expires_after="2022-08-20">
+    expires_after="2022-11-20">
   <owner>corising@chromium.org</owner>
   <owner>chrome-desktop-ui-sea@google.com</owner>
   <summary>
@@ -10798,7 +10798,7 @@
 </histogram>
 
 <histogram name="ReadingList.WindowDisplayedDuration" units="ms"
-    expires_after="2022-08-21">
+    expires_after="2022-11-21">
   <owner>corising@chromium.org</owner>
   <owner>chrome-desktop-ui-sea@google.com</owner>
   <summary>
@@ -10810,7 +10810,7 @@
 </histogram>
 
 <histogram name="ReadingList.{ReadStatus}.Count.{Variation}" units="count"
-    expires_after="2022-08-20">
+    expires_after="2022-11-20">
   <owner>corising@chromium.org</owner>
   <owner>chrome-desktop-ui-sea@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/power/histograms.xml b/tools/metrics/histograms/metadata/power/histograms.xml
index 81f9188a..ba1e3a2cc 100644
--- a/tools/metrics/histograms/metadata/power/histograms.xml
+++ b/tools/metrics/histograms/metadata/power/histograms.xml
@@ -665,23 +665,29 @@
   </token>
 </histogram>
 
-<histogram name="Power.BacklightLevelOnAC" units="%" expires_after="2023-05-12">
+<histogram name="Power.BacklightLevel{PrivacyScreenState}{PowerSource}"
+    units="%" expires_after="2023-05-12">
   <owner>puthik@chromium.org</owner>
+  <owner>mqg@chromium.org</owner>
   <owner>chromeos-platform-power@google.com</owner>
   <summary>
-    The level of the backlight as a percentage when the user is on AC. Sampled
-    every 30 seconds. Chrome OS only.
-  </summary>
-</histogram>
-
-<histogram name="Power.BacklightLevelOnBattery" units="%"
-    expires_after="2023-05-12">
-  <owner>puthik@chromium.org</owner>
-  <owner>chromeos-platform-power@google.com</owner>
-  <summary>
-    The level of the backlight as a percentage when the user is on battery.
+    The level of the backlight as a percentage when the privacy screen is in
+    {PrivacyScreenState} state and the device is powered by {PowerSource}.
     Sampled every 30 seconds. Chrome OS only.
   </summary>
+  <token key="PrivacyScreenState">
+    <variant name=""
+        summary="All Chrome OS, irrespective of whether privacy screens are
+                 present, or privacy screen state."/>
+    <variant name="PrivacyScreenDisabled"
+        summary="Chrome OS with privacy screens only."/>
+    <variant name="PrivacyScreenEnabled"
+        summary="Chrome OS with privacy screens only."/>
+  </token>
+  <token key="PowerSource">
+    <variant name="OnAC"/>
+    <variant name="OnBattery"/>
+  </token>
 </histogram>
 
 <histogram name="Power.BatteryChargeHealth" units="%"
@@ -1031,7 +1037,7 @@
 
 <histogram
     name="Power.DarkMode{prefers-color-scheme}.InferredDarkPageColorScheme"
-    enum="BooleanIsDarkColorScheme" expires_after="2022-09-24">
+    enum="BooleanIsDarkColorScheme" expires_after="2023-09-24">
   <owner>michaelbai@chromium.org</owner>
   <owner>pdr@chromium.org</owner>
   <owner>peter@chromium.org</owner>
diff --git a/ui/accessibility/platform/ax_platform_node_auralinux.cc b/ui/accessibility/platform/ax_platform_node_auralinux.cc
index 94d8843..c4613c1 100644
--- a/ui/accessibility/platform/ax_platform_node_auralinux.cc
+++ b/ui/accessibility/platform/ax_platform_node_auralinux.cc
@@ -331,8 +331,10 @@
   std::vector<std::string> names;
   for (const auto& node_id : ids) {
     if (AXPlatformNode* header = delegate->GetFromNodeID(node_id)) {
-      if (AtkObject* atk_header = header->GetNativeViewAccessible())
-        names.push_back(atk_object_get_name(atk_header));
+      if (AtkObject* atk_header = header->GetNativeViewAccessible()) {
+        if (const gchar* name = atk_object_get_name(atk_header))
+          names.push_back(name);
+      }
     }
   }
 
diff --git a/ui/accessibility/platform/ax_platform_node_base.cc b/ui/accessibility/platform/ax_platform_node_base.cc
index 3de50f05..f842d40 100644
--- a/ui/accessibility/platform/ax_platform_node_base.cc
+++ b/ui/accessibility/platform/ax_platform_node_base.cc
@@ -194,6 +194,7 @@
       name += extra_text;
     }
 
+    DCHECK(base::IsStringUTF8AllowingNoncharacters(name)) << "Invalid UTF8";
     return name;
   }
   return std::string();
diff --git a/ui/accessibility/platform/ax_platform_node_cocoa.mm b/ui/accessibility/platform/ax_platform_node_cocoa.mm
index 426b6ce73..3abaab3 100644
--- a/ui/accessibility/platform/ax_platform_node_cocoa.mm
+++ b/ui/accessibility/platform/ax_platform_node_cocoa.mm
@@ -685,16 +685,22 @@
 }
 
 - (BOOL)isImage {
-  bool isImage =
+  bool has_image_semantics =
       ui::IsImage(_node->GetRole()) &&
-      !_node->GetBoolAttribute(ax::mojom::BoolAttribute::kCanvasHasFallback);
-  DCHECK(!([[self accessibilityRole] isEqualToString:NSAccessibilityImageRole] ^
-           isImage))
-      << "Internal and native roles do not match when determining if this "
-         "object is an image. "
-      << "Chrome role: " << ui::ToString(_node->GetRole())
-      << ", NSAccessibility role: " << [self accessibilityRole];
-  return isImage;
+      !_node->GetBoolAttribute(ax::mojom::BoolAttribute::kCanvasHasFallback) &&
+      !_node->GetChildCount() &&
+      _node->GetNameFrom() != ax::mojom::NameFrom::kAttributeExplicitlyEmpty;
+#if DCHECK_IS_ON()
+  bool is_native_image =
+      [[self accessibilityRole] isEqualToString:NSAccessibilityImageRole];
+  DCHECK_EQ(is_native_image, has_image_semantics)
+      << "\nPresence/lack of native image role do not match the expected "
+         "internal semantics:"
+      << "\n* Chrome role: " << ui::ToString(_node->GetRole())
+      << "\n* NSAccessibility role: " << [self accessibilityRole]
+      << "\n* AXNode: " << *_node;
+#endif
+  return has_image_semantics;
 }
 
 - (NSString*)getName {
diff --git a/ui/display/manager/json_converter.cc b/ui/display/manager/json_converter.cc
index ae123b2..94bb90a 100644
--- a/ui/display/manager/json_converter.cc
+++ b/ui/display/manager/json_converter.cc
@@ -184,12 +184,7 @@
   return AddLegacyValuesFromValue(dict, layout);
 }
 
-bool DisplayLayoutToJson(const DisplayLayout& layout, base::Value* value) {
-  if (!value->is_dict())
-    return false;
-
-  base::Value::Dict& dict = value->GetDict();
-
+void DisplayLayoutToJson(const DisplayLayout& layout, base::Value::Dict& dict) {
   dict.Set(kDefaultUnifiedKey, layout.default_unified);
   dict.Set(kPrimaryIdKey, base::NumberToString(layout.primary_id));
 
@@ -206,8 +201,6 @@
     placement_list.Append(std::move(placement_value));
   }
   dict.Set(kDisplayPlacementKey, std::move(placement_list));
-
-  return true;
 }
 
 }  // namespace display
diff --git a/ui/display/manager/json_converter.h b/ui/display/manager/json_converter.h
index ccf9253..90c8d1c0 100644
--- a/ui/display/manager/json_converter.h
+++ b/ui/display/manager/json_converter.h
@@ -19,8 +19,9 @@
 DISPLAY_MANAGER_EXPORT bool JsonToDisplayLayout(const base::Value& value,
                                                 DisplayLayout* layout);
 
-DISPLAY_MANAGER_EXPORT bool DisplayLayoutToJson(const DisplayLayout& layout,
-                                                base::Value* value);
+// This will modify `dict` in place.
+DISPLAY_MANAGER_EXPORT void DisplayLayoutToJson(const DisplayLayout& layout,
+                                                base::Value::Dict& dict);
 
 }  // namespace display
 
diff --git a/ui/display/manager/json_converter_unittest.cc b/ui/display/manager/json_converter_unittest.cc
index b6aabff3..4b36613a 100644
--- a/ui/display/manager/json_converter_unittest.cc
+++ b/ui/display/manager/json_converter_unittest.cc
@@ -28,8 +28,8 @@
   layout.placement_list[1].position = DisplayPlacement::LEFT;
   layout.placement_list[1].offset = 30;
 
-  base::Value value(base::Value::Type::DICTIONARY);
-  DisplayLayoutToJson(layout, &value);
+  base::Value::Dict value;
+  DisplayLayoutToJson(layout, value);
 
   const char data[] =
       "{\n"
@@ -51,7 +51,7 @@
   ASSERT_TRUE(result.has_value())
       << result.error().message << " at " << result.error().line << ":"
       << result.error().column;
-  EXPECT_EQ(value, *result);
+  EXPECT_EQ(base::Value(std::move(value)), *result);
 
   DisplayLayout read_layout;
   EXPECT_TRUE(JsonToDisplayLayout(*result, &read_layout));
diff --git a/ui/file_manager/file_manager/background/js/drive_sync_handler.js b/ui/file_manager/file_manager/background/js/drive_sync_handler.js
index 0d96dad..83d9b21c 100644
--- a/ui/file_manager/file_manager/background/js/drive_sync_handler.js
+++ b/ui/file_manager/file_manager/background/js/drive_sync_handler.js
@@ -350,6 +350,8 @@
           break;
         case 'no_server_space':
           item.message = strf('SYNC_NO_SERVER_SPACE');
+          item.learnMoreLink = str('GOOGLE_DRIVE_MANAGE_STORAGE_URL');
+
           // This error will reappear every time sync is retried, so we use
           // a fixed ID to avoid spamming the user.
           item.id = DriveSyncHandlerImpl.DRIVE_SYNC_ERROR_PREFIX +
@@ -357,6 +359,8 @@
           break;
         case 'no_server_space_organization':
           item.message = strf('SYNC_NO_SERVER_SPACE_ORGANIZATION');
+          item.learnMoreLink = str('GOOGLE_DRIVE_MANAGE_STORAGE_URL');
+
           // This error will reappear every time sync is retried, so we use
           // a fixed ID to avoid spamming the user.
           item.id = DriveSyncHandlerImpl.DRIVE_SYNC_ERROR_ORGANIZATION_PREFIX +
diff --git a/ui/file_manager/file_manager/common/js/progress_center_common.js b/ui/file_manager/file_manager/common/js/progress_center_common.js
index c56ffdf..1ce46f9 100644
--- a/ui/file_manager/file_manager/common/js/progress_center_common.js
+++ b/ui/file_manager/file_manager/common/js/progress_center_common.js
@@ -132,6 +132,13 @@
      * @type {number}
      */
     this.remainingTime;
+
+    /**
+     * Link to be opened when users click the "Learn more" button.
+     * The "Learn more" button won't be displayed if this is falsy.
+     * @type {?string}
+     */
+    this.learnMoreLink;
   }
 
   /**
diff --git a/ui/file_manager/file_manager/foreground/elements/xf_button.html b/ui/file_manager/file_manager/foreground/elements/xf_button.html
index b0faa56c..f37dc4a 100644
--- a/ui/file_manager/file_manager/foreground/elements/xf_button.html
+++ b/ui/file_manager/file_manager/foreground/elements/xf_button.html
@@ -57,7 +57,7 @@
     position: relative;
   }
 
-  :host(:not([data-category='dismiss'])) {
+  :host(:not([data-category='dismiss']):not([data-category='learn-more'])) {
     width: 36px;
   }
 
@@ -65,9 +65,26 @@
     display: none;
   }
 
-  :host(:not([data-category='dismiss'])) #dismiss {
+  :host([data-category='learn-more']) #icon {
     display: none;
   }
+
+  #dismiss {
+    display: none;
+  }
+
+  #learn-more {
+    display: none;
+  }
+
+  :host([data-category='dismiss']) #dismiss {
+    display: unset;
+  }
+
+  :host([data-category='learn-more']) #learn-more {
+    display: unset;
+  }
 </style>
 <cr-button id='dismiss'>$i18n{DRIVE_WELCOME_DISMISS}</cr-button>
+<cr-button id='learn-more'>$i18n{LEARN_MORE_LABEL}</cr-button>
 <cr-icon-button id='icon'></cr-icon-button>
diff --git a/ui/file_manager/file_manager/foreground/elements/xf_display_panel.html b/ui/file_manager/file_manager/foreground/elements/xf_display_panel.html
index 51bbb53..ee1b2f1 100644
--- a/ui/file_manager/file_manager/foreground/elements/xf_display_panel.html
+++ b/ui/file_manager/file_manager/foreground/elements/xf_display_panel.html
@@ -1,6 +1,6 @@
 <style>
   :host {
-    max-width: 400px;
+    max-width: 504px;
     outline: none;
   }
   #container {
@@ -43,24 +43,24 @@
     75% {
       max-height: calc(192px + 28px);
       opacity: 0;
-      width: 400px;
+      width: 504px;
     }
     100% {
       max-height: calc(192px + 28px);
       opacity: 1;
-      width: 400px;
+      width: 504px;
     }
   }
 
   @keyframes setexpand {
     0% {
       max-height: calc(192px + 28px);
-      max-width: 400px;
+      max-width: 504px;
       opacity: 1;
     }
     25% {
       max-height: calc(192px + 28px);
-      max-width: 400px;
+      max-width: 504px;
       opacity: 0;
     }
     100% {
@@ -71,7 +71,7 @@
   }
   .expanded {
     animation: setcollapse 200ms forwards;
-    width: 400px;
+    width: 504px;
   }
   .collapsed {
     animation: setexpand 200ms forwards;
@@ -83,7 +83,7 @@
     max-height: calc(192px + 28px);
     opacity: 1;
     overflow-y: auto;
-    width: 400px;
+    width: 504px;
   }
   xf-panel-item:not(:only-child) {
     --multi-progress-height: 92px;
diff --git a/ui/file_manager/file_manager/foreground/elements/xf_panel_item.html b/ui/file_manager/file_manager/foreground/elements/xf_panel_item.html
index cbb90ca..4b63ce0 100644
--- a/ui/file_manager/file_manager/foreground/elements/xf_panel_item.html
+++ b/ui/file_manager/file_manager/foreground/elements/xf_panel_item.html
@@ -6,7 +6,7 @@
       display: flex;
       flex-direction: row;
       height: 68px;
-      width: 400px;
+      width: 504px;
   }
 
   .xf-button {
diff --git a/ui/file_manager/file_manager/foreground/elements/xf_panel_item.js b/ui/file_manager/file_manager/foreground/elements/xf_panel_item.js
index 3eceaeb..adf2783 100644
--- a/ui/file_manager/file_manager/foreground/elements/xf_panel_item.js
+++ b/ui/file_manager/file_manager/foreground/elements/xf_panel_item.js
@@ -157,6 +157,12 @@
         secondaryButton.onclick = assert(this.onclick);
         secondaryButton.dataset.category = 'dismiss';
         buttonSpacer.insertAdjacentElement('afterend', secondaryButton);
+        if (this.dataset.learnMoreLink) {
+          primaryButton = document.createElement('xf-button');
+          primaryButton.id = 'primary-action';
+          primaryButton.dataset.category = 'learn-more';
+          buttonSpacer.insertAdjacentElement('afterend', primaryButton);
+        }
         break;
       case this.panelTypeInfo:
         break;
diff --git a/ui/file_manager/file_manager/foreground/js/directory_contents.js b/ui/file_manager/file_manager/foreground/js/directory_contents.js
index 78ef8c7..95d788f 100644
--- a/ui/file_manager/file_manager/foreground/js/directory_contents.js
+++ b/ui/file_manager/file_manager/foreground/js/directory_contents.js
@@ -683,39 +683,16 @@
      * @public {!Array<string>}
      * @const
      */
-    this.prefetchPropertyNames = FileListContext.createPrefetchPropertyNames_();
+    this.prefetchPropertyNames = Array.from(new Set([
+      ...constants.LIST_CONTAINER_METADATA_PREFETCH_PROPERTY_NAMES,
+      ...constants.ACTIONS_MODEL_METADATA_PREFETCH_PROPERTY_NAMES,
+      ...constants.FILE_SELECTION_METADATA_PREFETCH_PROPERTY_NAMES,
+      ...constants.DLP_METADATA_PREFETCH_PROPERTY_NAMES,
+    ]));
 
     /** @public {!VolumeManager} */
     this.volumeManager = volumeManager;
   }
-
-  /**
-   * @return {!Array<string>}
-   * @private
-   */
-  static createPrefetchPropertyNames_() {
-    const set = {};
-    for (let i = 0;
-         i < constants.LIST_CONTAINER_METADATA_PREFETCH_PROPERTY_NAMES.length;
-         i++) {
-      set[constants.LIST_CONTAINER_METADATA_PREFETCH_PROPERTY_NAMES[i]] = true;
-    }
-    for (let i = 0;
-         i < constants.ACTIONS_MODEL_METADATA_PREFETCH_PROPERTY_NAMES.length;
-         i++) {
-      set[constants.ACTIONS_MODEL_METADATA_PREFETCH_PROPERTY_NAMES[i]] = true;
-    }
-    for (let i = 0;
-         i < constants.FILE_SELECTION_METADATA_PREFETCH_PROPERTY_NAMES.length;
-         i++) {
-      set[constants.FILE_SELECTION_METADATA_PREFETCH_PROPERTY_NAMES[i]] = true;
-    }
-    for (let i = 0; i < constants.DLP_METADATA_PREFETCH_PROPERTY_NAMES.length;
-         i++) {
-      set[constants.DLP_METADATA_PREFETCH_PROPERTY_NAMES[i]] = true;
-    }
-    return Object.keys(set);
-  }
 }
 
 /**
diff --git a/ui/file_manager/file_manager/foreground/js/ui/progress_center_panel.js b/ui/file_manager/file_manager/foreground/js/ui/progress_center_panel.js
index 3d13a06d..e57f51f33 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/progress_center_panel.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/progress_center_panel.js
@@ -310,10 +310,11 @@
       panelItem.signalCallback = (signal) => {
         if (signal === 'cancel' && item.cancelCallback) {
           item.cancelCallback();
-        }
-        if (signal === 'dismiss') {
+        } else if (signal === 'dismiss') {
           this.feedbackHost_.removePanelItem(panelItem);
           this.dismissErrorItemCallback(item.id);
+        } else if (signal === 'learn-more') {
+          window.open(item.learnMoreLink, '_blank');
         }
       };
       panelItem.progress = item.progressRateInPercent.toString();
@@ -350,6 +351,9 @@
           this.feedbackHost_.removePanelItem(panelItem);
           break;
         case ProgressItemState.ERROR:
+          if (item.learnMoreLink) {
+            panelItem.dataset.learnMoreLink = item.learnMoreLink;
+          }
           panelItem.panelType = panelItem.panelTypeError;
           panelItem.primaryText = item.message;
           panelItem.secondaryText = '';
diff --git a/ui/webui/resources/cr_elements/cr_icon_button/cr_icon_button.js b/ui/webui/resources/cr_elements/cr_icon_button/cr_icon_button.js
index bf9ca1c..436c998 100644
--- a/ui/webui/resources/cr_elements/cr_icon_button/cr_icon_button.js
+++ b/ui/webui/resources/cr_elements/cr_icon_button/cr_icon_button.js
@@ -40,68 +40,93 @@
  * When using iron-icon's, more than one icon can be specified by setting
  * the |ironIcon| property to a comma-delimited list of keys.
  */
-import {Polymer, html} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import '../shared_vars_css.m.js';
 import '//resources/polymer/v3_0/iron-icon/iron-icon.js';
 
-Polymer({
-  is: 'cr-icon-button',
+import {html, PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-  _template: html`{__html_template__}`,
+export class CrIconButtonElement extends PolymerElement {
+  static get is() {
+    return 'cr-icon-button';
+  }
 
-  properties: {
-    disabled: {
-      type: Boolean,
-      value: false,
-      reflectToAttribute: true,
-      observer: 'disabledChanged_',
-    },
+  static get template() {
+    return html`{__html_template__}`;
+  }
 
+  static get properties() {
+    return {
+      disabled: {
+        type: Boolean,
+        value: false,
+        reflectToAttribute: true,
+        observer: 'disabledChanged_',
+      },
+
+      /**
+       * Use this property in order to configure the "tabindex" attribute.
+       */
+      customTabIndex: {
+        type: Number,
+        observer: 'applyTabIndex_',
+      },
+
+      ironIcon: {
+        type: String,
+        observer: 'onIronIconChanged_',
+        reflectToAttribute: true,
+      },
+
+      /** @private */
+      multipleIcons_: {
+        type: Boolean,
+        reflectToAttribute: true,
+      },
+    };
+  }
+
+  constructor() {
+    super();
     /**
-     * Use this property in order to configure the "tabindex" attribute.
+     * It is possible to activate a tab when the space key is pressed down. When
+     * this element has focus, the keyup event for the space key should not
+     * perform a 'click'. |spaceKeyDown_| tracks when a space pressed and
+     * handled by this element. Space keyup will only result in a 'click' when
+     * |spaceKeyDown_| is true. |spaceKeyDown_| is set to false when element
+     * loses focus.
+     * @private {boolean}
      */
-    customTabIndex: {
-      type: Number,
-      observer: 'applyTabIndex_',
-    },
+    this.spaceKeyDown_ = false;
+  }
 
-    ironIcon: {
-      type: String,
-      observer: 'onIronIconChanged_',
-      reflectToAttribute: true,
-    },
+  /** @override */
+  ready() {
+    super.ready();
+    this.setAttribute('aria-disabled', 'false');
+    if (!this.hasAttribute('role')) {
+      this.setAttribute('role', 'button');
+    }
+    if (!this.hasAttribute('tabindex')) {
+      this.setAttribute('tabindex', '0');
+    }
 
-    /** @private */
-    multipleIcons_: {
-      type: Boolean,
-      reflectToAttribute: true,
-    },
-  },
+    this.addEventListener('blur', this.onBlur_.bind(this));
+    this.addEventListener('click', this.onClick_.bind(this));
+    this.addEventListener(
+        'keydown', e => this.onKeyDown_(/** @type {!KeyboardEvent} */ (e)));
+    this.addEventListener(
+        'keyup', e => this.onKeyUp_(/** @type {!KeyboardEvent} */ (e)));
+  }
 
-  hostAttributes: {
-    'aria-disabled': 'false',
-    role: 'button',
-    tabindex: 0,
-  },
-
-  listeners: {
-    blur: 'onBlur_',
-    click: 'onClick_',
-    keydown: 'onKeyDown_',
-    keyup: 'onKeyUp_',
-  },
-
-  /**
-   * It is possible to activate a tab when the space key is pressed down. When
-   * this element has focus, the keyup event for the space key should not
-   * perform a 'click'. |spaceKeyDown_| tracks when a space pressed and handled
-   * by this element. Space keyup will only result in a 'click' when
-   * |spaceKeyDown_| is true. |spaceKeyDown_| is set to false when element loses
-   * focus.
-   * @private {boolean}
-   */
-  spaceKeyDown_: false,
+  /** @param {string} className */
+  toggleClass(className) {
+    if (this.classList.contains(className)) {
+      this.classList.remove(className);
+    } else {
+      this.classList.add(className);
+    }
+  }
 
   /**
    * @param {boolean} newValue
@@ -117,7 +142,7 @@
     }
     this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');
     this.applyTabIndex_();
-  },
+  }
 
   /**
    * Updates the tabindex HTML attribute to the actual value.
@@ -129,12 +154,12 @@
       value = this.disabled ? -1 : 0;
     }
     this.setAttribute('tabindex', value);
-  },
+  }
 
   /** @private */
   onBlur_() {
     this.spaceKeyDown_ = false;
-  },
+  }
 
   /**
    * @param {!Event} e
@@ -144,7 +169,7 @@
     if (this.disabled) {
       e.stopImmediatePropagation();
     }
-  },
+  }
 
   /** @private */
   onIronIconChanged_() {
@@ -163,7 +188,7 @@
             .forEach(child => child.setAttribute('role', 'none'));
       }
     });
-  },
+  }
 
   /**
    * @param {!KeyboardEvent} e
@@ -185,7 +210,7 @@
     } else if (e.key === ' ') {
       this.spaceKeyDown_ = true;
     }
-  },
+  }
 
   /**
    * @param {!KeyboardEvent} e
@@ -201,5 +226,7 @@
       this.spaceKeyDown_ = false;
       this.click();
     }
-  },
-});
+  }
+}
+
+customElements.define(CrIconButtonElement.is, CrIconButtonElement);
diff --git a/weblayer/browser/browser_controls_navigation_state_handler.cc b/weblayer/browser/browser_controls_navigation_state_handler.cc
index afe0790a..77eb497 100644
--- a/weblayer/browser/browser_controls_navigation_state_handler.cc
+++ b/weblayer/browser/browser_controls_navigation_state_handler.cc
@@ -74,9 +74,7 @@
 void BrowserControlsNavigationStateHandler::DidFinishLoad(
     content::RenderFrameHost* render_frame_host,
     const GURL& validated_url) {
-  const bool is_main_frame =
-      render_frame_host->GetMainFrame() == render_frame_host;
-  if (is_main_frame)
+  if (render_frame_host->IsInPrimaryMainFrame())
     ScheduleStopDelayedForceShow();
 }
 
@@ -84,12 +82,8 @@
     content::RenderFrameHost* render_frame_host,
     const GURL& validated_url,
     int error_code) {
-  const bool is_main_frame =
-      render_frame_host->GetMainFrame() == render_frame_host;
-  if (is_main_frame)
+  if (render_frame_host->IsInPrimaryMainFrame()) {
     ScheduleStopDelayedForceShow();
-  if (render_frame_host->IsActive() &&
-      (render_frame_host == web_contents()->GetPrimaryMainFrame())) {
     UpdateState();
   }
 }
diff --git a/weblayer/browser/browser_controls_navigation_state_handler_browsertest.cc b/weblayer/browser/browser_controls_navigation_state_handler_browsertest.cc
new file mode 100644
index 0000000..10ba2356
--- /dev/null
+++ b/weblayer/browser/browser_controls_navigation_state_handler_browsertest.cc
@@ -0,0 +1,80 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/public/browser/web_contents.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "weblayer/browser/browser_controls_navigation_state_handler.h"
+#include "weblayer/browser/browser_controls_navigation_state_handler_delegate.h"
+#include "weblayer/browser/tab_impl.h"
+#include "weblayer/shell/browser/shell.h"
+#include "weblayer/test/weblayer_browser_test.h"
+#include "weblayer/test/weblayer_browser_test_utils.h"
+
+namespace weblayer {
+
+class BrowserConrolsNavigationStateHandlerBrowserTest
+    : public WebLayerBrowserTest {
+ public:
+  BrowserConrolsNavigationStateHandlerBrowserTest() = default;
+  ~BrowserConrolsNavigationStateHandlerBrowserTest() override = default;
+
+  // WebLayerBrowserTest:
+  void SetUpOnMainThread() override {
+    WebLayerBrowserTest::SetUpOnMainThread();
+    ASSERT_TRUE(embedded_test_server()->Start());
+  }
+
+  content::WebContents* web_contents() {
+    return static_cast<TabImpl*>(shell()->tab())->web_contents();
+  }
+
+ private:
+};
+
+class TestBrowserControlsNavigationStateHandlerDelegate
+    : public BrowserControlsNavigationStateHandlerDelegate {
+ public:
+  // BrowserControlsNavigationStateHandlerDelegate:
+  void OnBrowserControlsStateStateChanged(
+      ControlsVisibilityReason reason,
+      cc::BrowserControlsState state) override {
+    state_ = state;
+    if (quit_callback_)
+      std::move(quit_callback_).Run();
+  }
+  void OnUpdateBrowserControlsStateBecauseOfProcessSwitch(
+      bool did_commit) override {}
+
+  void WaitForStateChanged() {
+    base::RunLoop run_loop;
+    quit_callback_ = run_loop.QuitClosure();
+    run_loop.Run();
+  }
+
+  cc::BrowserControlsState state() { return state_; }
+
+ private:
+  base::OnceClosure quit_callback_;
+  cc::BrowserControlsState state_ = cc::BrowserControlsState::kBoth;
+};
+
+// Tests that BrowserConrolsNavigationStateHandler informs that the status is
+// updated according to navigation progress.
+IN_PROC_BROWSER_TEST_F(BrowserConrolsNavigationStateHandlerBrowserTest, Basic) {
+  TestBrowserControlsNavigationStateHandlerDelegate test_delegate;
+  BrowserControlsNavigationStateHandler
+      browser_controls_navigation_state_handler(web_contents(), &test_delegate);
+  GURL test_url(embedded_test_server()->GetURL("/simple_page.html"));
+  NavigateAndWaitForStart(test_url, shell()->tab());
+  // `test_delegate` should get the status is updated to `kShown` on
+  // DidStartNavigation();
+  EXPECT_EQ(test_delegate.state(), cc::BrowserControlsState::kShown);
+  test_delegate.WaitForStateChanged();
+  // `test_delegate` should get the status is updated to `kBoth` on
+  // DidFinishLoad();
+  EXPECT_EQ(web_contents()->GetLastCommittedURL(), test_url);
+  EXPECT_EQ(test_delegate.state(), cc::BrowserControlsState::kBoth);
+}
+
+}  // namespace weblayer
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/WebLayerNotificationWrapperBuilder.java b/weblayer/browser/java/org/chromium/weblayer_private/WebLayerNotificationWrapperBuilder.java
index b5d8ae6..175d910 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/WebLayerNotificationWrapperBuilder.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/WebLayerNotificationWrapperBuilder.java
@@ -78,27 +78,27 @@
      * resources. This is useful when {@link Icon} is not available.
      */
     private int getFallbackAndroidResource(int appResourceId) {
-        if (appResourceId == R.drawable.ic_play_arrow_white_36dp) {
+        if (appResourceId == R.drawable.ic_play_arrow_white_24dp) {
             return android.R.drawable.ic_media_play;
         }
-        if (appResourceId == R.drawable.ic_pause_white_36dp) {
+        if (appResourceId == R.drawable.ic_pause_white_24dp) {
             return android.R.drawable.ic_media_pause;
         }
-        if (appResourceId == R.drawable.ic_stop_white_36dp) {
+        if (appResourceId == R.drawable.ic_stop_white_24dp) {
             // There's no ic_media_stop. This standin is at least a square. In practice this
             // shouldn't ever come up as stop is only used in (Chrome) cast notifications.
             return android.R.drawable.checkbox_off_background;
         }
-        if (appResourceId == R.drawable.ic_skip_previous_white_36dp) {
+        if (appResourceId == R.drawable.ic_skip_previous_white_24dp) {
             return android.R.drawable.ic_media_previous;
         }
-        if (appResourceId == R.drawable.ic_skip_next_white_36dp) {
+        if (appResourceId == R.drawable.ic_skip_next_white_24dp) {
             return android.R.drawable.ic_media_next;
         }
-        if (appResourceId == R.drawable.ic_fast_forward_white_36dp) {
+        if (appResourceId == R.drawable.ic_fast_forward_white_24dp) {
             return android.R.drawable.ic_media_ff;
         }
-        if (appResourceId == R.drawable.ic_fast_rewind_white_36dp) {
+        if (appResourceId == R.drawable.ic_fast_rewind_white_24dp) {
             return android.R.drawable.ic_media_rew;
         }
         if (appResourceId == R.drawable.audio_playing) {
diff --git a/weblayer/public/java/BUILD.gn b/weblayer/public/java/BUILD.gn
index 91c7716..a842c0c 100644
--- a/weblayer/public/java/BUILD.gn
+++ b/weblayer/public/java/BUILD.gn
@@ -182,8 +182,10 @@
     "org/chromium/browserfragment/interfaces/IBrowserSandboxCallback.aidl",
     "org/chromium/browserfragment/interfaces/IBrowserSandboxService.aidl",
     "org/chromium/browserfragment/interfaces/IRequestNavigationCallback.aidl",
+    "org/chromium/browserfragment/interfaces/ITabCallback.aidl",
     "org/chromium/browserfragment/interfaces/ITabNavigationControllerProxy.aidl",
     "org/chromium/browserfragment/interfaces/ITabObserverDelegate.aidl",
+    "org/chromium/browserfragment/interfaces/ITabParams.aidl",
     "org/chromium/browserfragment/interfaces/ITabProxy.aidl",
   ]
 }
@@ -227,6 +229,7 @@
     "org/chromium/weblayer/BrowserFragmentTabDelegate.java",
     "org/chromium/weblayer/BrowserSandboxService.java",
     "org/chromium/weblayer/TabNavigationControllerProxy.java",
+    "org/chromium/weblayer/TabParams.java",
     "org/chromium/weblayer/TabProxy.java",
   ]
   resources_package = "org.chromium.weblayer"
diff --git a/weblayer/public/java/org/chromium/browserfragment/Tab.java b/weblayer/public/java/org/chromium/browserfragment/Tab.java
index 598820d..b14825d 100644
--- a/weblayer/public/java/org/chromium/browserfragment/Tab.java
+++ b/weblayer/public/java/org/chromium/browserfragment/Tab.java
@@ -8,6 +8,7 @@
 
 import androidx.annotation.NonNull;
 
+import org.chromium.browserfragment.interfaces.ITabParams;
 import org.chromium.browserfragment.interfaces.ITabProxy;
 
 /**
@@ -17,15 +18,20 @@
     private ITabProxy mTabProxy;
     private TabNavigationController mTabNavigationController;
 
-    Tab(ITabProxy tabProxy) {
-        mTabProxy = tabProxy;
+    private String mGuid;
 
-        try {
-            mTabNavigationController =
-                    new TabNavigationController(mTabProxy.getNavigationController());
-        } catch (RemoteException e) {
-            // TODO(swestphal): Raise exception.
-        }
+    Tab(@NonNull ITabParams tabParams) {
+        assert tabParams.tabProxy != null;
+        assert tabParams.tabGuid != null;
+        assert tabParams.navigationControllerProxy != null;
+
+        mTabProxy = tabParams.tabProxy;
+        mGuid = tabParams.tabGuid;
+        mTabNavigationController = new TabNavigationController(tabParams.navigationControllerProxy);
+    }
+
+    public String getGuid() {
+        return mGuid;
     }
 
     /**
@@ -47,4 +53,17 @@
     public TabNavigationController getNavigationController() {
         return mTabNavigationController;
     }
+
+    @Override
+    public int hashCode() {
+        return mGuid.hashCode();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj instanceof Tab) {
+            return this == obj || mGuid.equals(((Tab) obj).getGuid());
+        }
+        return false;
+    }
 }
diff --git a/weblayer/public/java/org/chromium/browserfragment/TabManager.java b/weblayer/public/java/org/chromium/browserfragment/TabManager.java
index c6be8e2..1b57996 100644
--- a/weblayer/public/java/org/chromium/browserfragment/TabManager.java
+++ b/weblayer/public/java/org/chromium/browserfragment/TabManager.java
@@ -6,10 +6,15 @@
 
 import android.os.RemoteException;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.concurrent.futures.CallbackToFutureAdapter;
+
+import com.google.common.util.concurrent.ListenableFuture;
 
 import org.chromium.browserfragment.interfaces.IBrowserFragmentDelegate;
-import org.chromium.browserfragment.interfaces.ITabProxy;
+import org.chromium.browserfragment.interfaces.ITabCallback;
+import org.chromium.browserfragment.interfaces.ITabParams;
 
 /**
  * Class for interaction with Browser Tabs.
@@ -20,24 +25,43 @@
 public class TabManager {
     private IBrowserFragmentDelegate mDelegate;
 
+    private final class TabCallback extends ITabCallback.Stub {
+        private CallbackToFutureAdapter.Completer<Tab> mCompleter;
+
+        TabCallback(CallbackToFutureAdapter.Completer<Tab> completer) {
+            mCompleter = completer;
+        }
+
+        @Override
+        public void onResult(@Nullable ITabParams tabParams) {
+            if (tabParams != null) {
+                mCompleter.set(new Tab(tabParams));
+                return;
+            }
+            mCompleter.set(null);
+        }
+    };
+
     TabManager(IBrowserFragmentDelegate delegate) {
         mDelegate = delegate;
     }
 
     /**
-     * Returns the currently active Tab; null if no Tab is active.
+     * Returns a ListenableFuture for the currently active Tab; The tab can be null if no Tab is
+     * active.
      *
-     * @return the currently active Tab.
+     * @return ListenableFuture for the active Tab.
      */
-    @Nullable
-    public Tab getActiveTab() {
-        try {
-            ITabProxy tabProxy = mDelegate.getActiveTab();
-            if (tabProxy != null) {
-                return new Tab(tabProxy);
+    @NonNull
+    public ListenableFuture<Tab> getActiveTab() {
+        return CallbackToFutureAdapter.getFuture(completer -> {
+            try {
+                mDelegate.getActiveTab(new TabCallback(completer));
+            } catch (RemoteException e) {
+                completer.setException(e);
             }
-        } catch (RemoteException e) {
-        }
-        return null;
+            // Debug string.
+            return "Active Tab Future";
+        });
     }
 }
diff --git a/weblayer/public/java/org/chromium/browserfragment/TabObserverDelegate.java b/weblayer/public/java/org/chromium/browserfragment/TabObserverDelegate.java
index fc3bb3b..7614f96 100644
--- a/weblayer/public/java/org/chromium/browserfragment/TabObserverDelegate.java
+++ b/weblayer/public/java/org/chromium/browserfragment/TabObserverDelegate.java
@@ -12,7 +12,7 @@
 
 import org.chromium.base.ObserverList;
 import org.chromium.browserfragment.interfaces.ITabObserverDelegate;
-import org.chromium.browserfragment.interfaces.ITabProxy;
+import org.chromium.browserfragment.interfaces.ITabParams;
 
 /**
  * TabObserverDelegate notifies TabObservers of Tab-events in weblayer.
@@ -41,9 +41,12 @@
     }
 
     @Override
-    public void notifyActiveTabChanged(@Nullable ITabProxy activeTab) {
+    public void notifyActiveTabChanged(@Nullable ITabParams tabParams) {
         mHandler.post(() -> {
-            Tab tab = new Tab(activeTab);
+            Tab tab = null;
+            if (tabParams != null) {
+                tab = new Tab(tabParams);
+            }
             for (TabObserver observer : mTabObservers) {
                 observer.onActiveTabChanged(tab);
             }
@@ -51,9 +54,9 @@
     }
 
     @Override
-    public void notifyTabAdded(@NonNull ITabProxy tabProxy) {
+    public void notifyTabAdded(@NonNull ITabParams tabParams) {
         mHandler.post(() -> {
-            Tab tab = new Tab(tabProxy);
+            Tab tab = new Tab(tabParams);
             for (TabObserver observer : mTabObservers) {
                 observer.onTabAdded(tab);
             }
@@ -61,9 +64,9 @@
     }
 
     @Override
-    public void notifyTabRemoved(@NonNull ITabProxy tabProxy) {
+    public void notifyTabRemoved(@NonNull ITabParams tabParams) {
         mHandler.post(() -> {
-            Tab tab = new Tab(tabProxy);
+            Tab tab = new Tab(tabParams);
             for (TabObserver observer : mTabObservers) {
                 observer.onTabRemoved(tab);
             }
diff --git a/weblayer/public/java/org/chromium/browserfragment/interfaces/IBrowserFragmentDelegate.aidl b/weblayer/public/java/org/chromium/browserfragment/interfaces/IBrowserFragmentDelegate.aidl
index 017f37e..193e4dc 100644
--- a/weblayer/public/java/org/chromium/browserfragment/interfaces/IBrowserFragmentDelegate.aidl
+++ b/weblayer/public/java/org/chromium/browserfragment/interfaces/IBrowserFragmentDelegate.aidl
@@ -6,10 +6,11 @@
 
 import android.os.Bundle;
 import org.chromium.browserfragment.interfaces.IBrowserFragmentDelegateClient;
+import org.chromium.browserfragment.interfaces.ITabCallback;
 import org.chromium.browserfragment.interfaces.ITabObserverDelegate;
 import org.chromium.browserfragment.interfaces.ITabProxy;
 
-interface IBrowserFragmentDelegate {
+oneway interface IBrowserFragmentDelegate {
     void setClient(in IBrowserFragmentDelegateClient client) = 1;
 
     void attachViewHierarchy(in IBinder hostToken) = 2;
@@ -29,6 +30,6 @@
     void onCleared() = 16;
 
     // Tab operations.
-    ITabProxy getActiveTab() = 14;
+    void getActiveTab(ITabCallback callback) = 14;
     void setTabObserverDelegate(ITabObserverDelegate tabObserverDelegate) = 15;
 }
\ No newline at end of file
diff --git a/weblayer/public/java/org/chromium/browserfragment/interfaces/ITabCallback.aidl b/weblayer/public/java/org/chromium/browserfragment/interfaces/ITabCallback.aidl
new file mode 100644
index 0000000..ee74c6f
--- /dev/null
+++ b/weblayer/public/java/org/chromium/browserfragment/interfaces/ITabCallback.aidl
@@ -0,0 +1,11 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.browserfragment.interfaces;
+
+import org.chromium.browserfragment.interfaces.ITabParams;
+
+oneway interface ITabCallback {
+    void onResult(in ITabParams tabParams) = 1;
+}
diff --git a/weblayer/public/java/org/chromium/browserfragment/interfaces/ITabObserverDelegate.aidl b/weblayer/public/java/org/chromium/browserfragment/interfaces/ITabObserverDelegate.aidl
index f05e875..2a9e9aa 100644
--- a/weblayer/public/java/org/chromium/browserfragment/interfaces/ITabObserverDelegate.aidl
+++ b/weblayer/public/java/org/chromium/browserfragment/interfaces/ITabObserverDelegate.aidl
@@ -4,11 +4,11 @@
 
 package org.chromium.browserfragment.interfaces;
 
-import org.chromium.browserfragment.interfaces.ITabProxy;
+import org.chromium.browserfragment.interfaces.ITabParams;
 
 oneway interface ITabObserverDelegate {
-    void notifyActiveTabChanged(in ITabProxy activeTab) = 1;
-    void notifyTabAdded(in ITabProxy tab) = 2;
-    void notifyTabRemoved(in ITabProxy tab) = 3;
+    void notifyActiveTabChanged(in ITabParams tabParams) = 1;
+    void notifyTabAdded(in ITabParams tabParams) = 2;
+    void notifyTabRemoved(in ITabParams tabParams) = 3;
     void notifyWillDestroyBrowserAndAllTabs() = 4;
 }
\ No newline at end of file
diff --git a/weblayer/public/java/org/chromium/browserfragment/interfaces/ITabParams.aidl b/weblayer/public/java/org/chromium/browserfragment/interfaces/ITabParams.aidl
new file mode 100644
index 0000000..8e78cbce
--- /dev/null
+++ b/weblayer/public/java/org/chromium/browserfragment/interfaces/ITabParams.aidl
@@ -0,0 +1,14 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.browserfragment.interfaces;
+
+import org.chromium.browserfragment.interfaces.ITabProxy;
+import org.chromium.browserfragment.interfaces.ITabNavigationControllerProxy;
+
+parcelable ITabParams {
+    ITabProxy tabProxy;
+    String tabGuid;
+    ITabNavigationControllerProxy navigationControllerProxy;
+}
\ No newline at end of file
diff --git a/weblayer/public/java/org/chromium/browserfragment/interfaces/ITabProxy.aidl b/weblayer/public/java/org/chromium/browserfragment/interfaces/ITabProxy.aidl
index 4ff9e50..6c718ba 100644
--- a/weblayer/public/java/org/chromium/browserfragment/interfaces/ITabProxy.aidl
+++ b/weblayer/public/java/org/chromium/browserfragment/interfaces/ITabProxy.aidl
@@ -6,7 +6,6 @@
 
 import org.chromium.browserfragment.interfaces.ITabNavigationControllerProxy;
 
-interface ITabProxy {
+oneway interface ITabProxy {
   void setActive() = 1;
-  ITabNavigationControllerProxy getNavigationController() = 2;
 }
diff --git a/weblayer/public/java/org/chromium/weblayer/BrowserFragmentDelegate.java b/weblayer/public/java/org/chromium/weblayer/BrowserFragmentDelegate.java
index d930505..0605e3c 100644
--- a/weblayer/public/java/org/chromium/weblayer/BrowserFragmentDelegate.java
+++ b/weblayer/public/java/org/chromium/weblayer/BrowserFragmentDelegate.java
@@ -13,12 +13,11 @@
 import android.view.SurfaceControlViewHost;
 import android.view.WindowManager;
 
-import androidx.annotation.Nullable;
-
 import org.chromium.browserfragment.interfaces.IBrowserFragmentDelegate;
 import org.chromium.browserfragment.interfaces.IBrowserFragmentDelegateClient;
+import org.chromium.browserfragment.interfaces.ITabCallback;
 import org.chromium.browserfragment.interfaces.ITabObserverDelegate;
-import org.chromium.browserfragment.interfaces.ITabProxy;
+import org.chromium.browserfragment.interfaces.ITabParams;
 
 /**
  * This class acts as a proxy between the embedding app's BrowserFragment and
@@ -85,13 +84,19 @@
     }
 
     @Override
-    @Nullable
-    public ITabProxy getActiveTab() {
-        Tab activeTab = mTabDelegate.getActiveTab();
-        if (activeTab != null) {
-            return new TabProxy(activeTab);
-        }
-        return null;
+    public void getActiveTab(ITabCallback tabCallback) {
+        mHandler.post(() -> {
+            Tab activeTab = mFragment.getBrowser().getActiveTab();
+            try {
+                if (activeTab != null) {
+                    ITabParams tabParams = TabParams.buildParcelable(activeTab);
+                    tabCallback.onResult(tabParams);
+                } else {
+                    tabCallback.onResult(null);
+                }
+            } catch (RemoteException e) {
+            }
+        });
     }
 
     @Override
diff --git a/weblayer/public/java/org/chromium/weblayer/BrowserFragmentTabDelegate.java b/weblayer/public/java/org/chromium/weblayer/BrowserFragmentTabDelegate.java
index de0b5e1aa..33d76b9 100644
--- a/weblayer/public/java/org/chromium/weblayer/BrowserFragmentTabDelegate.java
+++ b/weblayer/public/java/org/chromium/weblayer/BrowserFragmentTabDelegate.java
@@ -3,20 +3,20 @@
 // found in the LICENSE file.
 
 package org.chromium.weblayer;
+
 import android.os.RemoteException;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import org.chromium.browserfragment.interfaces.ITabObserverDelegate;
+import org.chromium.browserfragment.interfaces.ITabParams;
 
 /**
  * This class acts as a proxy between the Tab events happening in
  * weblayer and the TabManager in browserfragment.
  */
 class BrowserFragmentTabDelegate extends TabListCallback {
-    private Tab mActiveTab;
-
     private ITabObserverDelegate mTabObserver;
 
     private final NewTabCallback mNewTabCallback = new NewTabCallback() {
@@ -29,9 +29,14 @@
     }
 
     @Override
-    public void onActiveTabChanged(@Nullable Tab activeTab) {
-        mActiveTab = activeTab;
-        maybeRunOnTabObserver(observer -> observer.notifyActiveTabChanged(new TabProxy(activeTab)));
+    public void onActiveTabChanged(@Nullable Tab tab) {
+        maybeRunOnTabObserver(observer -> {
+            ITabParams tabParams = null;
+            if (tab != null) {
+                tabParams = TabParams.buildParcelable(tab);
+            }
+            observer.notifyActiveTabChanged(tabParams);
+        });
     }
 
     @Override
@@ -39,12 +44,18 @@
         // This is a requirement to open new tabs.
         tab.setNewTabCallback(mNewTabCallback);
 
-        maybeRunOnTabObserver(observer -> observer.notifyTabAdded(new TabProxy(tab)));
+        maybeRunOnTabObserver(observer -> {
+            ITabParams tabParams = TabParams.buildParcelable(tab);
+            observer.notifyTabAdded(tabParams);
+        });
     }
 
     @Override
     public void onTabRemoved(@NonNull Tab tab) {
-        maybeRunOnTabObserver(observer -> observer.notifyTabRemoved(new TabProxy(tab)));
+        maybeRunOnTabObserver(observer -> {
+            ITabParams tabParams = TabParams.buildParcelable(tab);
+            observer.notifyTabRemoved(tabParams);
+        });
     }
 
     @Override
@@ -64,9 +75,4 @@
             }
         }
     }
-
-    @Nullable
-    Tab getActiveTab() {
-        return mActiveTab;
-    }
 }
\ No newline at end of file
diff --git a/weblayer/public/java/org/chromium/weblayer/TabParams.java b/weblayer/public/java/org/chromium/weblayer/TabParams.java
new file mode 100644
index 0000000..a647844
--- /dev/null
+++ b/weblayer/public/java/org/chromium/weblayer/TabParams.java
@@ -0,0 +1,24 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.weblayer;
+
+import androidx.annotation.NonNull;
+
+import org.chromium.browserfragment.interfaces.ITabParams;
+
+/**
+ * Parameters for {@link Tab}.
+ */
+class TabParams {
+    static ITabParams buildParcelable(@NonNull Tab tab) {
+        ITabParams parcel = new ITabParams();
+        parcel.tabProxy = new TabProxy(tab);
+        parcel.tabGuid = tab.getGuid();
+        parcel.navigationControllerProxy =
+                new TabNavigationControllerProxy(tab.getNavigationController());
+
+        return parcel;
+    }
+}
\ No newline at end of file
diff --git a/weblayer/public/java/org/chromium/weblayer/TabProxy.java b/weblayer/public/java/org/chromium/weblayer/TabProxy.java
index f98e0a2..1a50b47 100644
--- a/weblayer/public/java/org/chromium/weblayer/TabProxy.java
+++ b/weblayer/public/java/org/chromium/weblayer/TabProxy.java
@@ -7,7 +7,6 @@
 import android.os.Handler;
 import android.os.Looper;
 
-import org.chromium.browserfragment.interfaces.ITabNavigationControllerProxy;
 import org.chromium.browserfragment.interfaces.ITabProxy;
 
 /**
@@ -17,17 +16,12 @@
 class TabProxy extends ITabProxy.Stub {
     private Handler mHandler = new Handler(Looper.getMainLooper());
 
-    private final ITabNavigationControllerProxy mTabNavigationControllerProxy;
-
     private int mTabId;
     private String mGuid;
 
     TabProxy(Tab tab) {
         mTabId = tab.getId();
         mGuid = tab.getGuid();
-
-        mTabNavigationControllerProxy =
-                new TabNavigationControllerProxy(tab.getNavigationController());
     }
 
     private Tab getTab() {
@@ -41,9 +35,4 @@
             tab.getBrowser().setActiveTab(tab);
         });
     }
-
-    @Override
-    public ITabNavigationControllerProxy getNavigationController() {
-        return mTabNavigationControllerProxy;
-    }
 }
\ No newline at end of file
diff --git a/weblayer/shell/android/browserfragment_shell_apk/src/org/chromium/browserfragment/shell/BrowserFragmentShellActivity.java b/weblayer/shell/android/browserfragment_shell_apk/src/org/chromium/browserfragment/shell/BrowserFragmentShellActivity.java
index 72a0db4..fb45452 100644
--- a/weblayer/shell/android/browserfragment_shell_apk/src/org/chromium/browserfragment/shell/BrowserFragmentShellActivity.java
+++ b/weblayer/shell/android/browserfragment_shell_apk/src/org/chromium/browserfragment/shell/BrowserFragmentShellActivity.java
@@ -11,6 +11,7 @@
 import androidx.annotation.Nullable;
 import androidx.appcompat.app.AppCompatActivity;
 
+import com.google.common.util.concurrent.AsyncFunction;
 import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
@@ -29,6 +30,8 @@
 public class BrowserFragmentShellActivity extends AppCompatActivity {
     private static final String TAG = "BrowserFragmentShell";
 
+    private static final String BROWSER_FRAGMENT_TAG = "BROWSER_FRAGMENT_TAG";
+
     private Context mContext;
 
     private TabManager mTabManager;
@@ -82,13 +85,17 @@
         });
 
         ListenableFuture<TabManager> tabManagerFuture = fragment.getTabManager();
+        AsyncFunction<TabManager, Tab> getActiveTabTask = tabManager -> {
+            mTabManager = tabManager;
+            return tabManager.getActiveTab();
+        };
+        ListenableFuture<Tab> activeTabFuture = Futures.transformAsync(
+                tabManagerFuture, getActiveTabTask, mContext.getMainExecutor());
 
-        Futures.addCallback(tabManagerFuture, new FutureCallback<TabManager>() {
+        Futures.addCallback(activeTabFuture, new FutureCallback<Tab>() {
             @Override
-            public void onSuccess(TabManager tabManager) {
-                mTabManager = tabManager;
-                Tab tab = tabManager.getActiveTab();
-                tab.getNavigationController().navigate("https://google.com");
+            public void onSuccess(Tab activeTab) {
+                activeTab.getNavigationController().navigate("https://google.com");
             }
             @Override
             public void onFailure(Throwable thrown) {}
@@ -97,33 +104,42 @@
         getSupportFragmentManager()
                 .beginTransaction()
                 .setReorderingAllowed(true)
-                .add(R.id.fragment_container_view, fragment)
+                .add(R.id.fragment_container_view, fragment, BROWSER_FRAGMENT_TAG)
                 .commit();
     }
 
     @Override
     public void onBackPressed() {
-        if (mTabManager == null) {
-            // BrowserFragment not yet initialized.
+        BrowserFragment fragment = (BrowserFragment) getSupportFragmentManager().findFragmentByTag(
+                BROWSER_FRAGMENT_TAG);
+        if (fragment == null || mTabManager == null) {
+            // BrowserFragment not initialized.
             super.onBackPressed();
+            return;
         }
-        Tab activeTab = mTabManager.getActiveTab();
-        if (activeTab == null) {
-            // TODO(swestphal): Check if there are any tabs?
-            super.onBackPressed();
-        }
-        TabNavigationController navigationController = activeTab.getNavigationController();
+        ListenableFuture<Tab> activeTabFuture = mTabManager.getActiveTab();
 
-        ListenableFuture<Boolean> canGoBackFuture = navigationController.canGoBack();
-
-        Futures.addCallback(canGoBackFuture, new FutureCallback<Boolean>() {
+        Futures.addCallback(activeTabFuture, new FutureCallback<Tab>() {
             @Override
-            public void onSuccess(Boolean canGoBack) {
-                if (canGoBack) {
-                    navigationController.goBack();
-                } else {
-                    BrowserFragmentShellActivity.super.onBackPressed();
-                }
+            public void onSuccess(Tab activeTab) {
+                TabNavigationController tabNavigationController =
+                        activeTab.getNavigationController();
+
+                Futures.addCallback(
+                        tabNavigationController.canGoBack(), new FutureCallback<Boolean>() {
+                            @Override
+                            public void onSuccess(Boolean canGoBack) {
+                                if (canGoBack) {
+                                    tabNavigationController.goBack();
+                                    return;
+                                }
+                                BrowserFragmentShellActivity.super.onBackPressed();
+                            }
+                            @Override
+                            public void onFailure(Throwable thrown) {
+                                BrowserFragmentShellActivity.super.onBackPressed();
+                            }
+                        }, mContext.getMainExecutor());
             }
             @Override
             public void onFailure(Throwable thrown) {
diff --git a/weblayer/test/BUILD.gn b/weblayer/test/BUILD.gn
index 8f8ca44..a1ab3897 100644
--- a/weblayer/test/BUILD.gn
+++ b/weblayer/test/BUILD.gn
@@ -217,6 +217,7 @@
       "../browser/android/metrics/metrics_test_helper.h",
       "../browser/android/metrics/ukm_browsertest.cc",
       "../browser/autofill_browsertest.cc",
+      "../browser/browser_controls_navigation_state_handler_browsertest.cc",
       "../browser/safe_browsing/client_side_detection_service_browsertest.cc",
       "../browser/safe_browsing/client_side_detection_service_factory_browsertest.cc",
       "../browser/safe_browsing/safe_browsing_browsertest.cc",